-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathslide.R
More file actions
457 lines (439 loc) · 9.85 KB
/
Copy pathslide.R
File metadata and controls
457 lines (439 loc) · 9.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
#' Slide
#'
#' `slide()` iterates through `.x` using a sliding window, applying `.f` to each
#' sub-window of `.x`.
#'
#' @param .x `[vector]`
#'
#' The vector to iterate over and apply `.f` to.
#'
#' @param .f `[function / formula]`
#'
#' If a __function__, it is used as is.
#'
#' If a __formula__, e.g. `~ .x + 2`, it is converted to a function. There
#' are three ways to refer to the arguments:
#'
#' * For a single argument function, use `.`
#' * For a two argument function, use `.x` and `.y`
#' * For more arguments, use `..1`, `..2`, `..3` etc
#'
#' This syntax allows you to create very compact anonymous functions.
#'
#' @param ... Additional arguments passed on to the mapped function.
#'
#' @param .step `[positive integer(1)]`
#'
#' The number of elements to shift the window forward between function calls.
#'
#' @param .complete `[logical(1)]`
#'
#' Should the function be evaluated on complete windows only? If `FALSE`,
#' the default, then partial computations will be allowed.
#'
#' @param .ptype `[vector(0) / NULL]`
#'
#' A prototype corresponding to the type of the output.
#'
#' If `NULL`, the default, the output type is determined by computing the
#' common type across the results of the calls to `.f`.
#'
#' If supplied, the result of each call to `.f` will be cast to that type,
#' and the final output will have that type.
#'
#' If `getOption("vctrs.no_guessing")` is `TRUE`, the `.ptype` must be
#' supplied. This is a way to make production code demand fixed types.
#'
#' @template param-before-after-slide
#'
#' @details
#'
#' Unlike `lapply()` or `purrr::map()`, which construct calls like
#'
#' ```
#' .f(.x[[i]], ...)
#' ```
#'
#' the equivalent with `slide()` looks like
#'
#' ```
#' .f(vctrs::vec_slice(.x, i), ...)
#' ```
#'
#' which is approximately
#'
#' ```
#' .f(.x[i], ...)
#' ```
#'
#' except in the case of data frames or arrays, which are iterated
#' over row-wise.
#'
#' If `.x` has names, then the output will preserve those names.
#'
#' Using [vctrs::vec_cast()], the output of `.f` will be automatically cast
#' to the type required by the variant of `slide_*()` being used.
#'
#' @return
#' A vector fulfilling the following invariants:
#'
#' \subsection{`slide()`}{
#'
#' * `vec_size(slide(.x)) == vec_size(.x)`
#'
#' * `vec_ptype(slide(.x)) == list()`
#'
#' }
#'
#' \subsection{`slide_vec()` and `slide_*()` variants}{
#'
#' * `vec_size(slide_vec(.x)) == vec_size(.x)`
#'
#' * `vec_size(slide_vec(.x)[[1]]) == 1L`
#'
#' * `vec_ptype(slide_vec(.x, .ptype = ptype)) == ptype`
#'
#' }
#'
#' @examples
#' # The defaults work similarly to `map()`
#' slide(1:5, ~.x)
#'
#' # Use `.before`, `.after`, and `.step` to control the window
#' slide(1:5, ~.x, .before = 1)
#'
#' # This can be used for rolling means
#' slide_dbl(rnorm(5), mean, .before = 2)
#'
#' # Or more flexible rolling operations
#' slide(rnorm(5), ~ .x - mean(.x), .before = 2)
#'
#' # `.after` allows you to "align to the left" rather than the right
#' slide(1:5, ~.x, .after = 2)
#'
#' # And a mixture of `.before` and `.after`
#' # allows you complete control over the exact alignment.
#' # Below, "center alignment" is used.
#' slide(1:5, ~.x, .before = 1, .after = 1)
#'
#' # The `.step` controls how the window is shifted along `.x`,
#' # allowing you to "skip" iterations if you only need a less granular result
#' slide(1:10, ~.x, .before = 2, .step = 3)
#'
#' # `.complete` controls whether or not partial results are computed.
#' # By default, they are, but setting `.complete = TRUE` restricts
#' # `slide()` to only evaluate the function where a complete window exists.
#' slide(1:5, ~.x, .before = 2, .after = 1)
#' slide(1:5, ~.x, .before = 2, .after = 1, .complete = TRUE)
#'
#' # ---------------------------------------------------------------------------
#' # Data frames
#'
#' # Data frames are iterated over rowwise
#' mtcars_rowwise <- slide(mtcars, ~.x)
#' mtcars_rowwise[1:3]
#'
#' # This means that any column name is easily accessible
#' slide_dbl(mtcars, ~.x$mpg + .x$cyl)
#'
#' # More advanced rowwise iteration is available as well by using the
#' # other arguments
#' mtcars_rowwise_window <- slide(mtcars, ~.x, .before = 1, .after = 1)
#' mtcars_rowwise_window[1:3]
#'
#' # ---------------------------------------------------------------------------
#' # Cumulative sliding
#'
#' # Using the special cased value, `Inf`, you can ask `slide()` to pin the
#' # start of the sliding window to the first element, effectively creating
#' # a cumulative window
#' slide(1:5, ~.x, .before = Inf)
#'
#' # Same with `.after`, this creates a window where you start with all of the
#' # elements, but decrease the total number over each iteration
#' slide(1:5, ~.x, .after = Inf)
#'
#' # ---------------------------------------------------------------------------
#' # Negative `.before` / `.after`
#'
#' # `.before` is allowed to be negative, allowing you to "look forward" in
#' # your vector. Note that `abs(.before) <= .after` must hold if `.before` is
#' # negative. In this example, we look forward to elements in locations 2 and 3
#' # but place the result in position 1 in the output.
#' slide(1:5, ~.x, .before = -1, .after = 2)
#'
#' # `.after` can be negative as well to "look backwards"
#' slide(1:5, ~.x, .before = 2, .after = -1)
#'
#' # ---------------------------------------------------------------------------
#' # Removing padding
#'
#' # If you are looking for a way to remove the `NA` values from something like
#' # this, then it doesn't exist as a built in option.
#' x <- rnorm(10)
#' slide_dbl(x, mean, .before = 3, .step = 2, .complete = TRUE)
#'
#' # Adding an option to `slide_dbl()` to remove the `NA` values would destroy
#' # its size stability. Instead, you can use a combination of `slide_dfr()`
#' # to get the start/stop indices with `hop_index_vec()`.
#' i <- seq_along(x)
#' idx <- slide_dfr(
#' i,
#' ~data.frame(start = .x[1], stop = .x[length(.x)]),
#' .before = 3,
#' .step = 2,
#' .complete = TRUE
#' )
#'
#' idx
#'
#' hop_index_vec(x, i, idx$start, idx$stop, mean, .ptype = double())
#'
#' @seealso [slide2()], [slide_index()], [hop()]
#' @export
slide <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE
) {
slide_impl(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = list(),
.constrain = FALSE,
.atomic = FALSE
)
}
#' @rdname slide
#' @export
slide_vec <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE,
.ptype = NULL
) {
out <- slide_impl(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = list(),
.constrain = FALSE,
.atomic = TRUE
)
vec_simplify(out, .ptype)
}
slide_vec_direct <- function(
.x,
.f,
...,
.before,
.after,
.step,
.complete,
.ptype,
.slider_error_call = caller_env()
) {
slide_impl(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = .ptype,
.constrain = TRUE,
.atomic = TRUE,
.slider_error_call = .slider_error_call
)
}
#' @rdname slide
#' @export
slide_dbl <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE
) {
slide_vec_direct(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = double()
)
}
#' @rdname slide
#' @export
slide_int <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE
) {
slide_vec_direct(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = integer()
)
}
#' @rdname slide
#' @export
slide_lgl <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE
) {
slide_vec_direct(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = logical()
)
}
#' @rdname slide
#' @export
slide_chr <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE
) {
slide_vec_direct(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete,
.ptype = character()
)
}
#' @inheritParams vctrs::vec_rbind
#' @rdname slide
#' @export
slide_dfr <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE,
.names_to = rlang::zap(),
.name_repair = c("unique", "universal", "check_unique")
) {
out <- slide(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete
)
vec_rbind(!!!out, .names_to = .names_to, .name_repair = .name_repair)
}
#' @inheritParams vctrs::vec_cbind
#' @rdname slide
#' @export
slide_dfc <- function(
.x,
.f,
...,
.before = 0L,
.after = 0L,
.step = 1L,
.complete = FALSE,
.size = NULL,
.name_repair = c("unique", "universal", "check_unique", "minimal")
) {
out <- slide(
.x,
.f,
...,
.before = .before,
.after = .after,
.step = .step,
.complete = .complete
)
vec_cbind(!!!out, .size = .size, .name_repair = .name_repair)
}
# ------------------------------------------------------------------------------
slide_impl <- function(
.x,
.f,
...,
.before,
.after,
.step,
.complete,
.ptype,
.constrain,
.atomic,
.slider_error_call = caller_env()
) {
vec_assert(.x, call = .slider_error_call)
.f <- as_function(.f, call = .slider_error_call)
f_call <- expr(.f(.x, ...))
type <- -1L
params <- list(
type = type,
constrain = .constrain,
atomic = .atomic,
before = .before,
after = .after,
step = .step,
complete = .complete
)
slide_common(
x = .x,
f_call = f_call,
ptype = .ptype,
env = environment(),
params = params
)
}