Skip to content

Commit a6b5252

Browse files
authored
adaptive && partial support, helper functions
1 parent 03f4bbd commit a6b5252

File tree

9 files changed

+194
-172
lines changed

9 files changed

+194
-172
lines changed

R/froll.R

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,60 @@
1+
# helpers for partial2adaptive
2+
trimn = function(n, len, align) {
3+
n = min(n, len) ## so frollsum(1:2, 3, partial=TRUE) works
4+
if (align=="right")
5+
c(seq.int(n), rep.int(n, len-n))
6+
else
7+
c(rep.int(n, len-n), rev(seq.int(n)))
8+
}
9+
trimnadaptive = function(n, align) {
10+
if (align=="right")
11+
pmin(n, seq_along(n))
12+
else
13+
pmin(n, rev(seq_along(n)))
14+
}
15+
116
# partial2adaptive helper function
217
## tune provided 'n' via partial=TRUE to adaptive=TRUE by prepared adaptive 'n' as shown in ?froll examples
3-
# partial2adaptive(1:4, 2, "right")
4-
# partial2adaptive(1:4, 2:3, "right")
5-
# partial2adaptive(list(1:4, 2:5), 2:3, "right")
6-
# frollsum(1:4, 2, partial=FALSE)
7-
# frollsum(1:4, 2, partial=TRUE)
8-
# frollsum(1:4, 2:3, partial=FALSE)
9-
# frollsum(1:4, 2:3, partial=TRUE)
10-
# frollsum(list(1:4, 2:5), 2:3, partial=FALSE)
11-
# frollsum(list(1:4, 2:5), 2:3, partial=TRUE)
12-
partial2adaptive = function(x, n, align) {
18+
# partial2adaptive(1:4, 2, "right", adaptive=FALSE)
19+
# partial2adaptive(1:4, 2:3, "right", adaptive=FALSE)
20+
# partial2adaptive(list(1:4, 2:5), 2:3, "right", adaptive=FALSE)
21+
# frollsum(1:4, 2, partial=FALSE, adaptive=FALSE)
22+
# frollsum(1:4, 2, partial=TRUE, adaptive=FALSE)
23+
# frollsum(1:4, 2:3, partial=FALSE, adaptive=FALSE)
24+
# frollsum(1:4, 2:3, partial=TRUE, adaptive=FALSE)
25+
# frollsum(list(1:4, 2:5), 2:3, partial=FALSE, adaptive=FALSE)
26+
# frollsum(list(1:4, 2:5), 2:3, partial=TRUE, adaptive=FALSE)
27+
partial2adaptive = function(x, n, align, adaptive) {
1328
if (align=="center")
1429
stopf("'partial' cannot be used together with align='center'")
1530
if (is.list(x) && length(unique(lengths(x)))!=1L)
1631
stopf("'partial' does not support variable length of columns in 'x'")
17-
if (!is.numeric(n))
18-
stopf("n must be an integer vector or a list of integer vectors")
1932
len = if (is.list(x)) length(x[[1L]]) else length(x)
2033
verbose = getOption("datatable.verbose")
21-
if (verbose)
22-
cat("partial2adaptive: froll partial=TRUE trimming 'n' and redirecting to adaptive=TRUE\n")
23-
trimn = function(n, len, align) {
24-
n = min(n, len) ## so frollsum(1:2, 3, partial=TRUE) works
25-
if (align=="right")
26-
c(seq.int(n), rep.int(n, len-n))
27-
else
28-
c(rep.int(n, len-n), rev(seq.int(n)))
29-
}
30-
if (length(n)>1L) {
31-
lapply(n, len, align, FUN=trimn)
34+
if (!adaptive) {
35+
if (is.list(n))
36+
stopf("n must be an integer, list is accepted for adaptive TRUE")
37+
if (!is.numeric(n))
38+
stopf("n must be an integer vector or a list of integer vectors")
39+
if (verbose)
40+
cat("partial2adaptive: froll partial=TRUE trimming 'n' and redirecting to adaptive=TRUE\n")
41+
if (length(n)>1L) {
42+
lapply(n, len, align, FUN=trimn)
43+
} else {
44+
trimn(n, len, align)
45+
}
3246
} else {
33-
trimn(n, len, align)
47+
if (!(is.numeric(n) || (is.list(n) && all(vapply_1b(n, is.numeric)))))
48+
stopf("n must be an integer vector or a list of integer vectors")
49+
if (!is.list(n))
50+
n = list(n)
51+
if (length(unique(lengths(n))) != 1L)
52+
stopf("adaptive window provided in 'n' must not to have different lengths")
53+
if (length(n[[1L]]) != len)
54+
stopf("length of vectors in 'x' must match to length of adaptive window in 'n'")
55+
if (verbose)
56+
cat("partial2adaptive: froll adaptive=TRUE and partial=TRUE trimming 'n'\n")
57+
lapply(n, align, FUN=triman)
3458
}
3559
}
3660

@@ -39,13 +63,9 @@ froll = function(fun, x, n, fill=NA, algo, align=c("right","left","center"), na.
3963
if (isTRUE(give.names))
4064
orig = list(n=n, adaptive=adaptive)
4165
if (isTRUE(partial)) {
42-
if (isTRUE(adaptive))
43-
stopf("'partial' argument cannot be used together with 'adaptive'")
44-
if (is.list(n))
45-
stopf("n must be integer, list is accepted for adaptive TRUE")
4666
if (!length(n))
4767
stopf("n must be non 0 length")
48-
n = partial2adaptive(x, n, align)
68+
n = partial2adaptive(x, n, align, adaptive)
4969
adaptive = TRUE
5070
}
5171
leftadaptive = isTRUE(adaptive) && align=="left"

inst/tests/froll.Rraw

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,7 @@ test(6000.4001, frollmax(1:3, 2), c(NA, 2, 3), output="frollmaxFast: running for
919919
test(6000.4002, frollmax(1:10, 5), c(NA,NA,NA,NA,5,6,7,8,9,10), output="frollmaxFast: nested window max calculation called 0 times")
920920
test(6000.4003, frollmax(10:1, 5), c(NA,NA,NA,NA,10,9,8,7,6,5), output="frollmaxFast: nested window max calculation called 5 times")
921921
test(6000.4004, frollmax(1:3, 2, algo="exact"), c(NA, 2, 3), output="frollmaxExact: running in parallel for input length")
922+
test(6000.4005, frollmax(c(1,2,3,NA,5), 2), c(NA, 2, 3, NA, NA), output="continue with extra care for NFs")
922923
options(datatable.verbose=FALSE)
923924
n = 3
924925
x = c(7,2,3,6,3,2,4,5) # no NA
@@ -1021,16 +1022,32 @@ ans = frollmean(x, n, align="left")
10211022
ans[(length(x)-n-1L):length(x)] = frollmean(x[(length(x)-n-1L):length(x)], n, partial=TRUE, align="left")
10221023
test(6006.023, ans, c(1,1.5,2,2.5,2.75,3))
10231024
ans = list(c(0.50,0.75,1.00,1.50,2.00,2.50), c(0.50,0.75,1.00,1.25,1.75,2.25))
1024-
test(6006.031, frollmean(1:6/2, list(3L,4L), partial=TRUE), error="n must be integer, list is accepted for adaptive TRUE")
1025+
test(6006.031, frollmean(1:6/2, list(3L,4L), partial=TRUE), error="n must be an integer, list is accepted for adaptive TRUE")
10251026
test(6006.032, frollmean(1:6/2, 3:4, partial=TRUE), ans)
10261027
options(datatable.verbose=TRUE)
10271028
test(6006.901, frollmean(x, n, partial=TRUE), c(0.5,0.75,1,1.5,2,2.5), output="froll partial=TRUE trimming 'n' and redirecting to adaptive=TRUE")
1029+
test(6006.902, frollmean(x, rep(n, length(x)), adaptive=TRUE, partial=TRUE), c(0.5,0.75,1,1.5,2,2.5), output="trimming", notOutput="redirecting")
10281030
options(datatable.verbose=FALSE)
1029-
test(6006.902, frollmean(1:4, rep(2L,4L), adaptive=TRUE, partial=TRUE), error="'partial' argument cannot be used together with 'adaptive'")
10301031
test(6006.903, frollmean(1:4, 2L, align="center", partial=TRUE), error="'partial' cannot be used together with align='center'")
10311032
test(6006.904, frollmean(list(1:4, 2:4), n, partial=TRUE), error="'partial' does not support variable length of columns in 'x'")
10321033
test(6006.905, frollmean(x, TRUE, partial=TRUE), error="n must be an integer vector or a list of integer vectors")
1033-
test(6006.906, frollmean(x, list(TRUE), partial=TRUE), error="n must be integer, list is accepted for adaptive TRUE")
1034+
test(6006.906, frollmean(x, list(TRUE), partial=TRUE), error="n must be an integer, list is accepted for adaptive TRUE")
1035+
1036+
## partial adaptive
1037+
test(6006.930, frollmean(1:4, rep(2L,4L), adaptive=TRUE, partial=TRUE), c(1,1.5,2.5,3.5))
1038+
test(6006.9301, frollmean(1:4, list(1:4, 1:3), adaptive=TRUE, partial=TRUE), error="adaptive window provided in 'n' must not to have different lengths")
1039+
test(6006.9302, frollmean(1:4, list(1:3), adaptive=TRUE, partial=TRUE), error="length of vectors in 'x' must match to length of adaptive window in 'n'")
1040+
test(6006.9303, frollmean(1:4, list(rep(2L,4L)), adaptive=TRUE, partial=TRUE), c(1,1.5,2.5,3.5))
1041+
test(6006.9311, frollsum(1:4, 1:4, adaptive=TRUE, partial=TRUE), c(1,3,6,10)) ## all same as index
1042+
test(6006.9312, frollsum(1:4, 1:4, align="left", adaptive=TRUE, partial=TRUE), c(1,5,7,4))
1043+
test(6006.9321, frollsum(1:4, c(2,3,1,1), adaptive=TRUE, partial=TRUE), c(1,3,3,4)) ## leading two bigger than index
1044+
test(6006.9322, frollsum(1:4, c(2,3,1,1), align="left", adaptive=TRUE, partial=TRUE), c(3,9,3,4))
1045+
test(6006.9323, frollsum(1:4, c(6,5,4,2), adaptive=TRUE, partial=TRUE), c(1,3,6,7)) ## leading two bigger than rev index
1046+
test(6006.9324, frollsum(1:4, c(6,5,4,2), align="left", adaptive=TRUE, partial=TRUE), c(10,9,7,4))
1047+
test(6006.9331, frollsum(1:4, c(2,4,5,6), adaptive=TRUE, partial=TRUE), c(1,3,6,10)) ## trailing two bigger than index
1048+
test(6006.9332, frollsum(1:4, c(2,4,5,6), align="left", adaptive=TRUE, partial=TRUE), c(3,9,7,4))
1049+
test(6006.9333, frollsum(1:4, c(1,1,3,2), adaptive=TRUE, partial=TRUE), c(1,2,6,7)) ## trailing two bigger than rev index
1050+
test(6006.9334, frollsum(1:4, c(1,1,3,2), align="left", adaptive=TRUE, partial=TRUE), c(1,2,7,4))
10341051

10351052
## give.names
10361053
test(6006.951, frollsum(1:3, 2, give.names=TRUE), c(NA,3,5))
@@ -1208,7 +1225,7 @@ if (requireNamespace("zoo", quietly=TRUE)) {
12081225
}
12091226
#### adaptive moving average compare
12101227
num = 6009.0
1211-
arollfun = function(FUN, x, n, na.rm=FALSE, align=c("right","left"), fill=NA, nf.rm=FALSE) { ## partial cannot be used with adaptive
1228+
arollfun = function(FUN, x, n, na.rm=FALSE, align=c("right","left"), fill=NA, nf.rm=FALSE, partial=FALSE) {
12121229
# adaptive moving average in R
12131230
stopifnot((nx<-length(x))==length(n))
12141231
align = match.arg(align)
@@ -1218,12 +1235,16 @@ arollfun = function(FUN, x, n, na.rm=FALSE, align=c("right","left"), fill=NA, nf
12181235
if (align=="right") {
12191236
for (i in seq_along(x)) {
12201237
if (i >= n[i])
1221-
ans[i] = f(x[(i-n[i]+1):i], na.rm=na.rm)
1238+
ans[i] = f(x[(i-n[i]+1L):i], na.rm=na.rm)
1239+
else if (partial)
1240+
ans[i] = f(x[1L:i], na.rm=na.rm)
12221241
}
12231242
} else {
12241243
for (i in seq_along(x)) {
12251244
if (i <= nx-n[i]+1)
1226-
ans[i] = f(x[i:(i+n[i]-1)], na.rm=na.rm)
1245+
ans[i] = f(x[i:(i+n[i]-1L)], na.rm=na.rm)
1246+
else if (partial)
1247+
ans[i] = f(x[i:length(x)], na.rm=na.rm)
12271248
}
12281249
}
12291250
ans
@@ -1235,32 +1256,36 @@ afun_compare = function(x, n, funs=c("mean","sum","max"), algos=c("fast","exact"
12351256
for (align in c("right","left")) {
12361257
for (na.rm in c(FALSE, TRUE)) {
12371258
for (fill in c(NA_real_, 0)) {
1238-
for (has.nf in c(NA,TRUE,FALSE)) {
1239-
if (identical(has.nf, FALSE)) {
1240-
if (na.rm)
1241-
next ## errors "not make sense"
1242-
if (any(!is.finite(x)))
1243-
next ## do not test warnings (mean, sum) or incorrect expect results (max)
1244-
}
1245-
for (algo in algos) {
1246-
num <<- num + num.step
1247-
eval(substitute(
1248-
test(.num, ignore.warning="no non-missing arguments",
1249-
arollfun(.fun, x, n, fill=.fill, na.rm=.na.rm, align=.align),
1250-
frollfun(.fun, x, n, fill=.fill, na.rm=.na.rm, algo=.algo, adaptive=TRUE, align=.align, has.nf=.has.nf)),
1251-
list(.num=num, .fun=fun, .fill=fill, .na.rm=na.rm, .algo=algo, .align=align, .has.nf=has.nf)
1252-
))
1259+
for (partial in c(FALSE, TRUE)) {
1260+
for (has.nf in c(NA, TRUE, FALSE)) {
1261+
if (identical(has.nf, FALSE)) {
1262+
if (na.rm) {
1263+
next
1264+
} ## errors "not make sense"
1265+
if (any(!is.finite(x))) {
1266+
next
1267+
} ## do not test warnings (mean, sum) or incorrect expect results (max)
1268+
}
1269+
for (algo in algos) {
1270+
num <<- num + num.step
1271+
eval(substitute(
1272+
test(.num,
1273+
ignore.warning = "no non-missing arguments",
1274+
arollfun(.fun, x, n, fill = .fill, na.rm = .na.rm, align = .align),
1275+
frollfun(.fun, x, n, fill = .fill, na.rm = .na.rm, algo = .algo, adaptive = TRUE, align = .align, has.nf = .has.nf)
1276+
),
1277+
list(.num = num, .fun = fun, .fill = fill, .na.rm = na.rm, .algo = algo, .align = align, .has.nf = has.nf)
1278+
))
1279+
}
12531280
}
12541281
}
1255-
if (base::getRversion() >= "3.4.0") { ## SET_GROWABLE_BIT
1256-
num <<- num + num.step
1257-
eval(substitute(
1258-
test(.num, ignore.warning="no non-missing arguments",
1259-
frollapply(x, n, FUN=match.fun(.fun), fill=.fill, na.rm=.na.rm, adaptive=TRUE, align=.align),
1260-
frollfun(.fun, x, n, fill=.fill, na.rm=.na.rm, adaptive=TRUE, align=.align)),
1261-
list(.num=num, .fun=fun, .fill=fill, .na.rm=na.rm, .align=align)
1262-
))
1263-
}
1282+
num <<- num + num.step
1283+
eval(substitute(
1284+
test(.num, ignore.warning="no non-missing arguments",
1285+
frollapply(x, n, FUN=match.fun(.fun), fill=.fill, na.rm=.na.rm, adaptive=TRUE, align=.align, partial=.partial),
1286+
frollfun(.fun, x, n, fill=.fill, na.rm=.na.rm, adaptive=TRUE, align=.align, partial=.partial)),
1287+
list(.num=num, .fun=fun, .fill=fill, .na.rm=na.rm, .align=align, .partial=partial)
1288+
))
12641289
}
12651290
}
12661291
}
@@ -1359,7 +1384,7 @@ if (!r340) {
13591384
test(6010.2018, frollapply(1:5, rep(3, 5), function(x) head(x, 2), adaptive=TRUE), error="frolladaptiveapply: results from provided FUN are not length 1")
13601385
test(6010.2019, frollapply(1:10, list(1:5), mean, adaptive=TRUE), error="length of integer vector(s) provided as list to 'n' argument must be equal to number of observations provided in 'x'")
13611386
test(6010.2020, frollapply(1:10, 1:5, mean, adaptive=TRUE), error="length of integer vector(s) provided as list to 'n' argument must be equal to number of observations provided in 'x'")
1362-
test(6010.2021, frollapply(1:4, rep(2L,4L), mean, adaptive=TRUE, partial=TRUE), error="'partial' argument cannot be used together with 'adaptive'")
1387+
test(6010.2021, frollapply(1:4, rep(2L,4L), mean, adaptive=TRUE, partial=TRUE), c(1,1.5,2.5,3.5))
13631388
options(datatable.verbose=TRUE)
13641389
test(6010.2029, frollapply(c(1,3,4,2,0), c(3,2,2,3,2), sum, adaptive=TRUE, align="left"), c(8,7,6,NA,NA), output=c("processing from align='right'"))
13651390
x = c(1,2,1,1,1,2,3,2)

man/froll.Rd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
\section{\code{partial} argument}{
123123
\code{partial=TRUE} is used to calculate rolling moments \emph{only} within the input itself. That is, at the boundaries (say, observation 2 for \code{n=4} and \code{align="right"}), we don't consider observations before the first as "missing", but instead shrink the window to be size \code{n=2}.
124124
In practice, this is the same as an \emph{adaptive} window, and could be accomplished, albeit less concisely, with a well-chosen \code{n} and \code{adaptive=TRUE}.
125-
In fact, we implement \code{partial=TRUE} using the same algorithms as \code{adaptive=TRUE}. Therefore \code{partial=TRUE} inherits the limitations of adaptive rolling functions, see above. Adaptive functions use more complex algorithms; if performance is important, \code{partial=TRUE} should be avoided in favour of computing missing observations "by hand" after the rolling function; see examples.
125+
In fact, we implement \code{partial=TRUE} using the same algorithms as \code{adaptive=TRUE}. Therefore \code{partial=TRUE} inherits the limitations of adaptive rolling functions, see above. Adaptive functions use more complex algorithms; if performance is important, \code{partial=TRUE} should be avoided in favour of computing only missing observations separately after the rolling function; see examples.
126126
}
127127
\section{\code{frollapply}}{
128128
\code{frollapply} computes rolling aggregate on arbitrary R functions. \code{adaptive} argument is not supported (to be changed). The input \code{x} (first argument) to the function \code{FUN} is coerced to \emph{numeric} beforehand(to be changed) and \code{FUN} has to return a scalar \emph{numeric} value (to be changed). Checks for that are made only during the first iteration when \code{FUN} is evaluated. Edge cases can be found in examples below. Any R function is supported, but it is not optimized using our own C implementation -- hence, for example, using \code{frollapply} to compute a rolling average is inefficient. It is also always single-threaded because there is no thread-safe API to R's C \code{eval}. Nevertheless we've seen the computation speed up vis-a-vis versions implemented in base R.

src/data.table.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ void internal_error(const char *call_name, const char *format, ...);
284284

285285
// types.c
286286
char *end(char *start);
287-
void ansMsg(ans_t *ans, int n, bool verbose, const char *func);
287+
void ansSetMsg(ans_t *ans, uint8_t status, const char *msg, const char *func);
288+
void ansGetMsgs(ans_t *ans, int n, bool verbose, const char *func);
288289
SEXP testMsgR(SEXP status, SEXP x, SEXP k);
289290

290291
//fifelse.c

0 commit comments

Comments
 (0)