Skip to content

Commit 9b421c8

Browse files
froll support for unevenly spaced time series (#7291)
* all * extend news * mark experimental * codecov * test number * trailing ws * Apply suggestions from code review Co-authored-by: Michael Chirico <[email protected]> * frolladapt nicer news example * add const as we use INTEGER_RO * code blocks for news file * Apply suggestions from code review Co-authored-by: Michael Chirico <[email protected]> * NEWS improve * match arg name in comments * Apply suggestions from code review Co-authored-by: Michael Chirico <[email protected]> --------- Co-authored-by: Michael Chirico <[email protected]>
1 parent 90f1c1e commit 9b421c8

File tree

11 files changed

+340
-9
lines changed

11 files changed

+340
-9
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export(frollmean)
5656
export(frollsum)
5757
export(frollmax)
5858
export(frollapply)
59+
export(frolladapt)
5960
export(nafill)
6061
export(setnafill)
6162
export(.Last.updated)

NEWS.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
```
3333
Additionally argument names in `frollapply` has been renamed from `x` to `X` and `n` to `N` to avoid conflicts with common argument names that may be passed to `...`, aligning to base R API of `lapply`. `x` and `n` continue to work with a warning, for now.
3434
35+
5. Negative and missing values of `n` argument of adaptive rolling functions trigger an error.
36+
3537
### NOTICE OF INTENDED FUTURE POTENTIAL BREAKING CHANGES
3638
3739
1. `data.table(x=1, <expr>)`, where `<expr>` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, <expr>)` where `<expr>` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 5 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`.
@@ -210,6 +212,40 @@
210212
#[1] TRUE
211213
```
212214

215+
18. New helper `frolladapt` to facilitate applying rolling functions over windows of fixed calendar-time width in irregularly-spaced data sets, thereby bypassing the need to "augment" such data with placeholder rows, [#3241](https://github.com/Rdatatable/data.table/issues/3241). Thanks to @jangorecki for implementation.
216+
```r
217+
idx = as.Date("2025-09-05") + c(0,4,7,8,9,10,12,13,17)
218+
dt = data.table(index=idx, value=seq_along(idx))
219+
dt
220+
# index value
221+
# <Date> <int>
222+
#1: 2025-09-05 1
223+
#2: 2025-09-09 2
224+
#3: 2025-09-12 3
225+
#4: 2025-09-13 4
226+
#5: 2025-09-14 5
227+
#6: 2025-09-15 6
228+
#7: 2025-09-17 7
229+
#8: 2025-09-18 8
230+
#9: 2025-09-22 9
231+
dt[, c("rollmean3","rollmean3days") := list(
232+
frollmean(value, 3),
233+
frollmean(value, frolladapt(index, 3), adaptive=TRUE)
234+
)]
235+
dt
236+
# index value rollmean3 rollmean3days
237+
# <Date> <int> <num> <num>
238+
#1: 2025-09-05 1 NA NA
239+
#2: 2025-09-09 2 NA 2.0
240+
#3: 2025-09-12 3 2 3.0
241+
#4: 2025-09-13 4 3 3.5
242+
#5: 2025-09-14 5 4 4.0
243+
#6: 2025-09-15 6 5 5.0
244+
#7: 2025-09-17 7 6 6.5
245+
#8: 2025-09-18 8 7 7.5
246+
#9: 2025-09-22 9 8 9.0
247+
```
248+
213249
### BUG FIXES
214250

215251
1. `fread()` no longer warns on certain systems on R 4.5.0+ where the file owner can't be resolved, [#6918](https://github.com/Rdatatable/data.table/issues/6918). Thanks @ProfFancyPants for the report and PR.

R/froll.R

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,47 @@ make.roll.names = function(x.len, n.len, n, x.nm, n.nm, fun, adaptive) {
111111
ans
112112
}
113113

114+
# irregularly spaced time series, helper for creating adaptive window size
115+
frolladapt = function(x, n, align="right", partial=FALSE, give.names=FALSE) {
116+
x = unclass(x)
117+
if (!is.numeric(x))
118+
stopf("'x' must be of a numeric type")
119+
if (!is.integer(x))
120+
x = as.integer(x)
121+
if (!is.numeric(n)) {
122+
stopf("'n' must be an integer")
123+
} else {
124+
nms = names(n) ## only for give.names
125+
if (!is.integer(n)) {
126+
if (!fitsInInt32(n))
127+
stopf("'n' must be an integer")
128+
n = as.integer(n)
129+
}
130+
}
131+
if (!length(n))
132+
stopf("'n' must be non 0 length")
133+
if (anyNA(n))
134+
stopf("'n' must not have NAs")
135+
if (!identical(align, "right"))
136+
stopf("'align' other than 'right' has not yet been implemented")
137+
if (!isTRUEorFALSE(partial))
138+
stopf("'%s' must be TRUE or FALSE", "partial")
139+
if (!isTRUEorFALSE(give.names))
140+
stopf("'%s' must be TRUE or FALSE", "give.names")
141+
142+
if (length(n) == 1L) {
143+
ans = .Call(Cfrolladapt, x, n, partial)
144+
} else {
145+
ans = lapply(n, function(.n) .Call(Cfrolladapt, x, .n, partial))
146+
if (give.names) {
147+
if (is.null(nms))
148+
nms = paste0("n", as.character(n))
149+
setattr(ans, "names", nms)
150+
}
151+
}
152+
ans
153+
}
154+
114155
froll = function(fun, x, n, fill=NA, algo=c("fast","exact"), align=c("right","left","center"), na.rm=FALSE, has.nf=NA, adaptive=FALSE, partial=FALSE, give.names=FALSE, hasNA) {
115156
stopifnot(!missing(fun), is.character(fun), length(fun)==1L, !is.na(fun))
116157
if (!missing(hasNA)) {

R/frollapply.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ frollapply = function(X, N, FUN, ..., by.column=TRUE, fill=NA, align=c("right","
147147
nnam = names(N) ## used for give.names
148148
if (!is.integer(N))
149149
N = as.integer(N)
150+
if (anyNA(N))
151+
stopf("'N' must be non-negative integer values (>= 0)")
150152
nn = length(N) ## top level loop for vectorized n
151153
} else {
152154
if (length(unique(len)) > 1L) ## vectorized x requires same nrow for adaptive
@@ -156,6 +158,8 @@ frollapply = function(X, N, FUN, ..., by.column=TRUE, fill=NA, align=c("right","
156158
stopf("length of integer vector(s) provided as list to 'N' argument must be equal to number of observations provided in 'X'")
157159
if (!is.integer(N))
158160
N = as.integer(N)
161+
if (anyNA(N))
162+
stopf("'N' must be non-negative integer values (>= 0)")
159163
nn = 1L
160164
N = list(N)
161165
nnam = character()
@@ -168,6 +172,8 @@ frollapply = function(X, N, FUN, ..., by.column=TRUE, fill=NA, align=c("right","
168172
stopf("'N' must be an integer vector or list of integer vectors")
169173
if (!all(vapply_1b(N, is.integer, use.names=FALSE)))
170174
N = lapply(N, as.integer)
175+
if (any(vapply_1b(N, anyNA, use.names=FALSE)))
176+
stopf("'N' must be non-negative integer values (>= 0)")
171177
nn = length(N)
172178
nnam = names(N)
173179
} else

0 commit comments

Comments
 (0)