Skip to content

Commit 01c5c00

Browse files
Merge pull request #1141 from M3nin0/feature/detections-api-cube
detect changes api: improve `sits_dtw` operations in data cubes
2 parents 7da1d25 + 4cfb651 commit 01c5c00

File tree

4 files changed

+115
-46
lines changed

4 files changed

+115
-46
lines changed

R/api_dtw.R

+50-40
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,5 @@
11
# ---- Distances ----
22
#' @title Calculate the DTW distance between temporal patterns and time-series.
3-
#' @name .dtw_distance
4-
#' @description This function calculates the DTW distance between label patterns
5-
#' and real data (e.g., sample data, data cube data). The distance is calculated
6-
#' without a window. It's use is recommended for big datasets.
7-
#' @keywords internal
8-
#' @noRd
9-
.dtw_distance <- function(data, patterns) {
10-
# Prepare input data
11-
data <- as.matrix(.ts_values(data))
12-
# Calculate the DTW distance between `data` and `patterns`
13-
purrr::map_dfc(patterns, function(pattern) {
14-
# Prepare pattern data
15-
pattern_ts <- as.matrix(.ts_values(pattern))
16-
# Calculate distance
17-
stats::setNames(
18-
data.frame(distance = dtw_distance(data, pattern_ts)),
19-
pattern[["label"]][[1]]
20-
)
21-
})
22-
}
23-
#' @title Calculate the DTW distance between temporal patterns and time-series.
243
#' @name .dtw_distance_windowed
254
#' @description This function calculates the DTW distance between label patterns
265
#' and real data (e.g., sample data, data cube data). The distance is calculated
@@ -35,12 +14,7 @@
3514
# Windowed search
3615
distances <- purrr::map_df(windows, function(window) {
3716
# Get time-series in the window
38-
data_in_window <-
39-
dplyr::filter(data,
40-
.data[["Index"]] >= window[["start"]],
41-
.data[["Index"]] <= window[["end"]])
42-
# Remove the time reference column
43-
data_in_window <- as.matrix(.ts_values(data_in_window))
17+
data_in_window <- as.matrix(.ts_values(data[window,]))
4418
# Calculate distance
4519
data.frame(distance = dtw_distance(data_in_window, pattern_ts))
4620
})
@@ -49,31 +23,61 @@
4923
})
5024
}
5125
# ---- Operation mode ----
52-
#' @title Search for events in time series using complete data (no windowing).
53-
#' @name .dtw_complete_ts
54-
#' @description This function searches for events in time series without
55-
#' windowing.
26+
#' @title Search for events in data cube.
27+
#' @name .dtw_cube
28+
#' @description This function searches for events in data cubes.
5629
#' @keywords internal
5730
#' @noRd
58-
.dtw_complete_ts <- function(values, patterns, threshold, ...) {
31+
.dtw_cube <- function(values, patterns, window, threshold, ...) {
32+
# Extract dates
33+
dates <- .ts_index(values[[1]])
34+
dates_min <- .ts_min_date(values[[1]])
35+
dates_max <- .ts_max_date(values[[1]])
36+
# Assume time-series are regularized, then use the period
37+
# as the step of the moving window. As a result, we have
38+
# one step per iteration.
39+
dates_step <- lubridate::as.period(
40+
lubridate::int_diff(.ts_index(values[[1]]))
41+
)
42+
dates_step <- dates_step[[1]]
43+
# Create comparison windows
44+
comparison_windows <- .period_windows(
45+
period = window,
46+
step = dates_step,
47+
start_date = dates_min,
48+
end_date = dates_max
49+
)
50+
# Transform comparison windows to indices to avoid filters
51+
comparison_windows <- purrr::map(comparison_windows, function(window) {
52+
which(
53+
dates >= window[["start"]] & dates <= window[["end"]]
54+
)
55+
})
5956
# Do the change detection for each time-series
6057
purrr::map_vec(values, function(value_row) {
6158
# Search for the patterns
62-
patterns_distances <- .dtw_distance(
59+
patterns_distances <- .dtw_distance_windowed(
6360
data = value_row,
64-
patterns = patterns
61+
patterns = patterns,
62+
windows = comparison_windows
6563
)
6664
# Remove distances out the user-defined threshold
67-
as.numeric(any(patterns_distances <= threshold))
65+
patterns_distances[patterns_distances <= threshold] <- 1
66+
patterns_distances[patterns_distances > threshold] <- 0
67+
# Get the position of the valid values
68+
patterns_distances <- which(patterns_distances == 1)
69+
# Return value
70+
ifelse(length(patterns_distances) > 0, min(patterns_distances), 0)
6871
})
6972
}
70-
#' @title Search for events in time series using windowing.
71-
#' @name .dtw_windowed_ts
72-
#' @description This function searches for events in time series with windowing.
73+
#' @title Search for events in time-series.
74+
#' @name .dtw_ts
75+
#' @description This function searches for events in time-series
7376
#' @keywords internal
7477
#' @noRd
75-
.dtw_windowed_ts <- function(values, patterns, window, threshold) {
78+
.dtw_ts <- function(values, patterns, window, threshold, ...) {
7679
# Extract dates
80+
dates <- .ts_index(values[[1]])
7781
dates_min <- .ts_min_date(values[[1]])
7882
dates_max <- .ts_max_date(values[[1]])
7983
# Assume time-series are regularized, then use the period
@@ -90,13 +94,19 @@
9094
start_date = dates_min,
9195
end_date = dates_max
9296
)
97+
# Transform comparison windows to indices to avoid filters
98+
comparison_windows_idx <- purrr::map(comparison_windows, function(window) {
99+
which(
100+
dates >= window[["start"]] & dates <= window[["end"]]
101+
)
102+
})
93103
# Do the change detection for each time-series
94104
purrr::map(values, function(value_row) {
95105
# Search for the patterns
96106
patterns_distances <- .dtw_distance_windowed(
97107
data = value_row,
98108
patterns = patterns,
99-
windows = comparison_windows
109+
windows = comparison_windows_idx
100110
)
101111
# Remove distances out the user-defined threshold
102112
patterns_distances[patterns_distances > threshold] <- NA

R/api_patterns.R

+11
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@
1818
ts_median
1919
})
2020
}
21+
22+
#' @title Extract labels available in patterns.
23+
#' @name .pattern_labels
24+
#' @keywords internal
25+
#' @noRd
26+
#' @param patterns Samples patterns.
27+
.pattern_labels <- function(patterns) {
28+
purrr::map_vec(patterns, function(pattern) {
29+
unique(pattern[["label"]])
30+
})
31+
}

R/sits_dtw.R

+36-5
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,62 @@
1717
#' where "D", "M" and "Y" stands for days, month and
1818
#' year; e.g., "P16D" for 16 days. This parameter is not
1919
#' used in operations with data cubes.
20+
#' @param start_date Initial date of the interval used to extract the
21+
#' patterns from the samples.
22+
#' @param end_date Final date of the interval used to extract the
23+
#' patterns from the samples.
24+
#' @param patterns Temporal patterns of the each label available in
25+
#' `samples`.
2026
#' @return Change detection method prepared to be passed to
2127
#' \code{\link[sits]{sits_detect_change_method}}
2228
#' @export
29+
#'
2330
sits_dtw <-
2431
function(samples = NULL,
2532
...,
2633
threshold = NULL,
27-
window = NULL) {
34+
start_date = NULL,
35+
end_date = NULL,
36+
window = NULL,
37+
patterns = NULL) {
2838
.check_set_caller("sits_dtw")
2939
train_fun <-
3040
function(samples) {
3141
# Check parameters
3242
.check_period(window)
3343
.check_null_parameter(threshold)
44+
.check_date_parameter(start_date, allow_null = TRUE)
45+
.check_date_parameter(end_date, allow_null = TRUE)
3446
# Sample labels
3547
labels <- .samples_labels(samples)
36-
# Get samples patterns (temporal median)
48+
# Generate predictors
3749
train_samples <- .predictors(samples)
38-
patterns <- .pattern_temporal_median(samples)
50+
# Generate patterns (if not defined by the user)
51+
if (is.null(patterns)) {
52+
# Save samples used to generate temporal patterns
53+
patterns_samples <- samples
54+
# Filter samples if required
55+
if (!is.null(start_date) & !is.null(end_date)) {
56+
patterns_samples <- .samples_filter_interval(
57+
samples = patterns_samples,
58+
start_date = start_date,
59+
end_date = end_date
60+
)
61+
}
62+
# Generate samples patterns (temporal median)
63+
patterns <- .pattern_temporal_median(patterns_samples)
64+
}
65+
# Check patterns
66+
.check_chr_contains(
67+
x = .samples_labels(samples),
68+
contains = .pattern_labels(patterns)
69+
)
3970
# Define detection function
4071
detect_change_fun <- function(values, type) {
4172
# Define the type of the operation
42-
dtw_fun <- .dtw_windowed_ts
73+
dtw_fun <- .dtw_ts
4374
if (type == "cube") {
44-
dtw_fun <- .dtw_complete_ts
75+
dtw_fun <- .dtw_cube
4576
}
4677
# Detect changes
4778
dtw_fun(

man/sits_dtw.Rd

+18-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)