Applies any R function f on running (Column/sliding) windows defined by k, lag, idx and at.

runner(
  x,
  f = function(x) x,
  k = integer(0),
  lag = integer(1),
  idx = integer(0),
  at = integer(0),
  na_pad = FALSE,
  simplify = TRUE,
  cl = NULL,
  ...
)

# Default S3 method
runner(
  x,
  f = function(x) x,
  k = integer(0),
  lag = integer(1),
  idx = integer(0),
  at = integer(0),
  na_pad = FALSE,
  simplify = TRUE,
  cl = NULL,
  ...
)

# S3 method for class 'data.frame'
runner(
  x,
  f = function(x) x,
  k = attr(x, "k"),
  lag = if (!is.null(attr(x, "lag"))) attr(x, "lag") else integer(1),
  idx = attr(x, "idx"),
  at = attr(x, "at"),
  na_pad = if (!is.null(attr(x, "na_pad"))) attr(x, "na_pad") else FALSE,
  simplify = TRUE,
  cl = NULL,
  ...
)

# S3 method for class 'grouped_df'
runner(
  x,
  f = function(x) x,
  k = attr(x, "k"),
  lag = if (!is.null(attr(x, "lag"))) attr(x, "lag") else integer(1),
  idx = attr(x, "idx"),
  at = attr(x, "at"),
  na_pad = if (!is.null(attr(x, "na_pad"))) attr(x, "na_pad") else FALSE,
  simplify = TRUE,
  cl = NULL,
  ...
)

# S3 method for class 'matrix'
runner(
  x,
  f = function(x) x,
  k = integer(0),
  lag = integer(1),
  idx = integer(0),
  at = integer(0),
  na_pad = FALSE,
  simplify = TRUE,
  cl = NULL,
  ...
)

# S3 method for class 'xts'
runner(
  x,
  f = function(x) x,
  k = integer(0),
  lag = integer(1),
  idx = integer(0),
  at = integer(0),
  na_pad = FALSE,
  simplify = TRUE,
  cl = NULL,
  ...
)

Arguments

x

(vector, data.frame, matrix, xts, grouped_df)
input data.

f

(function)
Function applied to each window. Defaults to identity (function(x) x).

k

(integer or character)
Window size. Single value or vector of length(x). Omit for cumulative windows. Accepts time-interval strings (e.g. "5 days") when idx is set.

lag

(integer or character)
Window shift. Positive shifts back, negative shifts forward. Single value or vector of length(x). Accepts time-interval strings when idx is set.

idx

(integer, Date, POSIXt)
Sorted index of observations. When set, k and lag refer to index distance rather than element count. Must be same length as x.

at

(integer, Date, POSIXt, character)
Indices at which to evaluate windows. Output length equals length(at) instead of length(x). A single time-interval string (e.g. "month") generates a regular sequence over the range of idx.

na_pad

(logical)
If TRUE, return NA for windows that extend beyond the data range.

simplify

(logical or character)
Simplify result like base::sapply(). TRUE returns vector/matrix, "array" may return a higher-dimensional array.

cl

(cluster) experimental
Parallel cluster from parallel::makeCluster().

...

additional arguments passed to f.

Value

vector with aggregated values for each window. Length equals length(x) or length(at) if specified. Type depends on f.

Details

Window types

Sliding window (k)

k sets the number of elements in each window. When k is a single constant, the window slides along the data with a fixed size. The first k-1 windows are shorter since there aren't enough preceding elements.

k = 4

 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 |  |  |  |  |  |  |##|##|##|##|  |  |  |  |  |
 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
                     <-------->
                     window at i=10

# 4-element sliding sum
runner(1:15, k = 4, f = sum)

If k is omitted, windows are cumulative — each window grows from the first element to the current one, similar to cumsum().

k not specified (cumulative)

 i= 1: |#|                    window [1,  1]
 i= 2: |##|                   window [1,  2]
 i= 3: |###|                  window [1,  3]
 i= 4: |####|                 window [1,  4]
    ...
 i=15: |###############|      window [1, 15]

# cumulative sum (k omitted)
runner(1:15, f = sum)

k can also be a vector of length(x) to use a different window size at each position.

Lag (lag)

lag shifts the window backward (positive values) or forward (negative values) relative to the current element. Default is lag = 0. Like k, lag can be a single value or a vector of length(x).

k = 4, lag = 2

 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 |  |  |  |  |##|##|##|##|  |  |  |  |  |  |  |
 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
               <-------->    ^
               window     i=10 (shifted by lag=2)

runner(1:15, k = 4, lag = 2, f = sum)

Index-based windows (idx)

By default, runner treats elements as equally spaced (index increments by 1). Real data often has gaps — missing weekends, holidays, irregular timestamps. Setting idx makes k and lag refer to index distance instead of element count, so the number of elements per window varies with the spacing.

For example, a 5-day window (k = 5) on unevenly-spaced dates will contain different numbers of observations at each step:

k = 5, lag = 1
idx:  4   6   7  13  17  18  18  21  27  31  37  42  44  47  48

 4:  [-2,  3]  NA            (no data in range)
 6:  [ 1,  5]  =             {4}
 7:  [ 2,  6]  ==            {4, 6}
13:  [ 8, 12]  NA            (no data in range)
17:  [12, 16]  =             {13}
18:  [13, 17]  ==            {13, 17}
18:  [13, 17]  ==            {13, 17, 18}
21:  [16, 20]  ===           {17, 18, 18}
27:  [22, 26]  NA            (no data in range)
31:  [26, 30]  =             {27}
37:  [32, 36]  NA            (no data in range)
42:  [37, 41]  =             {37}
44:  [39, 43]  ==            {42, 44}
47:  [42, 46]  ==            {42, 44}
48:  [43, 47]  ===           {44, 47}

k and lag also accept time-interval strings using the same syntax as seq.POSIXt(by = ...), e.g. "5 days", "2 weeks", "month".

idx <- c(4, 6, 7, 13, 17, 18, 18, 21, 27, 31, 37, 42, 44, 47, 48)
runner(idx, k = 5, lag = 1, idx = idx, f = mean)

# equivalent with Date index
runner(idx, k = "5 days", lag = "day", idx = Sys.Date() + idx, f = mean)

Evaluation at specific points (at)

By default, runner returns one result per element of x. Setting at restricts evaluation to specific index positions — the output length equals length(at). This is useful when you only need results at certain dates or milestones, not at every observation.

k = 5, lag = 1, at = c(18, 27, 48, 31)
idx:  4   6   7  13  17  18  18  21  27  31  37  42  44  47  48

at=18:  [13, 17]  ==          {13, 17}
at=27:  [22, 26]  NA          (no data in range)
at=48:  [43, 47]  ===         {44, 47}
at=31:  [26, 30]  =           {27}

runner(1:15, k = 5, lag = 1, idx = idx, at = c(18, 27, 48, 31), f = mean)

at can also be a single time-interval string, which generates a regular sequence over the idx range. For example, at = "4 months" evaluates at every 4-month interval from min(idx) to max(idx).

Time-interval syntax

k, lag and at accept time-interval strings (requires idx to be set) using the same syntax as the by argument in base::seq.POSIXt(): "sec", "min", "hour", "day", "week", "month", "quarter", "year", or "<n> <unit>s" (e.g. "5 days", "-2 weeks"). Both k and lag can also be vectors, allowing different window sizes and shifts at each position.

Parallel computing

Pass a parallel::makeCluster() object via cl to run windows in parallel. Objects referenced inside f (other than its arguments) must be exported with parallel::clusterExport() beforehand. Parallel execution adds overhead and is only beneficial for expensive computations.

Examples


# runner returns windows as is by default
runner(1:10)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 1 2
#> 
#> [[3]]
#> [1] 1 2 3
#> 
#> [[4]]
#> [1] 1 2 3 4
#> 
#> [[5]]
#> [1] 1 2 3 4 5
#> 
#> [[6]]
#> [1] 1 2 3 4 5 6
#> 
#> [[7]]
#> [1] 1 2 3 4 5 6 7
#> 
#> [[8]]
#> [1] 1 2 3 4 5 6 7 8
#> 
#> [[9]]
#> [1] 1 2 3 4 5 6 7 8 9
#> 
#> [[10]]
#>  [1]  1  2  3  4  5  6  7  8  9 10
#> 

# mean on k = 3 elements windows
runner(1:10, f = mean, k = 3)
#>  [1] 1.0 1.5 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0

# mean on k = 3 elements windows with different specification
runner(1:10, k = 3, f = function(x) mean(x, na.rm = TRUE))
#>  [1] 1.0 1.5 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0

# concatenate two columns
runner(
  data.frame(
    a = letters[1:10],
    b = 1:10
  ),
  f = function(x) paste(paste0(x$a, x$b), collapse = "+")
)
#>  [1] "a1"                             "a1+b2"                         
#>  [3] "a1+b2+c3"                       "a1+b2+c3+d4"                   
#>  [5] "a1+b2+c3+d4+e5"                 "a1+b2+c3+d4+e5+f6"             
#>  [7] "a1+b2+c3+d4+e5+f6+g7"           "a1+b2+c3+d4+e5+f6+g7+h8"       
#>  [9] "a1+b2+c3+d4+e5+f6+g7+h8+i9"     "a1+b2+c3+d4+e5+f6+g7+h8+i9+j10"

# concatenate two columns with additional argument
runner(
  data.frame(
    a = letters[1:10],
    b = 1:10
  ),
  f = function(x, xxx) {
    paste(paste0(x$a, xxx, x$b), collapse = " + ")
  },
  xxx = "..."
)
#>  [1] "a...1"                                                                         
#>  [2] "a...1 + b...2"                                                                 
#>  [3] "a...1 + b...2 + c...3"                                                         
#>  [4] "a...1 + b...2 + c...3 + d...4"                                                 
#>  [5] "a...1 + b...2 + c...3 + d...4 + e...5"                                         
#>  [6] "a...1 + b...2 + c...3 + d...4 + e...5 + f...6"                                 
#>  [7] "a...1 + b...2 + c...3 + d...4 + e...5 + f...6 + g...7"                         
#>  [8] "a...1 + b...2 + c...3 + d...4 + e...5 + f...6 + g...7 + h...8"                 
#>  [9] "a...1 + b...2 + c...3 + d...4 + e...5 + f...6 + g...7 + h...8 + i...9"         
#> [10] "a...1 + b...2 + c...3 + d...4 + e...5 + f...6 + g...7 + h...8 + i...9 + j...10"

# number of unique values in each window (varying window size)
runner(letters[1:10],
  k = c(1, 2, 2, 4, 5, 5, 5, 5, 5, 5),
  f = function(x) length(unique(x))
)
#>  [1] 1 2 2 4 5 5 5 5 5 5

# concatenate only on selected windows index
runner(letters[1:10],
  f = function(x) paste(x, collapse = "-"),
  at = c(1, 5, 8)
)
#> [1] "a"               "a-b-c-d-e"       "a-b-c-d-e-f-g-h"

# 5 days mean
idx <- c(4, 6, 7, 13, 17, 18, 18, 21, 27, 31, 37, 42, 44, 47, 48)
runner::runner(
  x = idx,
  k = "5 days",
  lag = 1,
  idx = Sys.Date() + idx,
  f = function(x) mean(x)
)
#>  [1]       NA  4.00000  5.00000       NA 13.00000 15.00000 15.00000 17.66667
#>  [9]       NA 27.00000       NA 37.00000 42.00000 43.00000 45.50000

# 5 days mean at 4-indices
runner::runner(
  x = 1:15,
  k = 5,
  lag = 1,
  idx = idx,
  at = c(18, 27, 48, 31),
  f = mean
)
#> [1]  4.5   NA 13.5  9.0

# runner with data.frame
df <- data.frame(
  a = 1:13,
  b = 1:13 + rnorm(13, sd = 5),
  idx = seq(as.Date("2022-02-22"), as.Date("2023-02-22"), by = "1 month")
)
runner(
  x = df,
  idx = "idx",
  at = "6 months",
  f = function(x) {
    cor(x$a, x$b)
  }
)
#> [1]        NA 0.5074821 0.7702177

# parallel computing
library(parallel)
data <- data.frame(
  a = runif(100),
  b = runif(100),
  idx = cumsum(sample(rpois(100, 5)))
)
const <- 0
cl <- makeCluster(1)
clusterExport(cl, "const", envir = environment())

runner(
  x = data,
  k = 10,
  f = function(x) {
    cor(x$a, x$b) + const
  },
  idx = "idx",
  cl = cl
)
#>   [1]          NA          NA -1.00000000 -0.99710574 -0.98260307  0.65135545
#>   [7]  0.87152732 -1.00000000 -0.99975803  1.00000000 -1.00000000 -1.00000000
#>  [13] -0.84347254 -0.94118564  1.00000000          NA -1.00000000 -0.35038491
#>  [19] -0.26049435  0.15981220 -0.89962234 -1.00000000  1.00000000 -1.00000000
#>  [25]  1.00000000 -0.68940828 -0.85882044 -1.00000000 -1.00000000  0.58074906
#>  [31] -0.86775397 -0.23251508 -0.45610506 -0.49504396 -0.96422538  1.00000000
#>  [37] -1.00000000 -1.00000000  1.00000000  0.67045942  0.32281471 -0.26072010
#>  [43]  0.39816756  0.72549665  0.69994795 -1.00000000 -1.00000000  0.48611613
#>  [49]  0.13342545  0.97622976  1.00000000  1.00000000  1.00000000 -1.00000000
#>  [55]  1.00000000 -1.00000000 -1.00000000 -0.29664241  1.00000000 -1.00000000
#>  [61] -1.00000000 -0.97031468  1.00000000  1.00000000  1.00000000  1.00000000
#>  [67]  0.02923985 -0.74401064 -0.62841711 -0.95499769 -0.03458451  1.00000000
#>  [73] -1.00000000 -1.00000000 -0.99239039 -0.80128888 -0.80450459 -0.81546248
#>  [79] -0.03780289  1.00000000 -1.00000000 -0.41500526  0.32888136 -1.00000000
#>  [85]  1.00000000  1.00000000 -0.43995518 -1.00000000 -1.00000000  1.00000000
#>  [91]  1.00000000  1.00000000  1.00000000  1.00000000  0.68398151  1.00000000
#>  [97] -1.00000000  1.00000000  1.00000000          NA
stopCluster(cl)

# runner with matrix
data <- matrix(data = runif(100, 0, 1), nrow = 20, ncol = 5)
runner(
  x = data,
  f = function(x) {
    tryCatch(
      cor(x),
      error = function(e) NA
    )
  }
)
#>       [,1] [,2]        [,3]        [,4]         [,5]        [,6]        [,7]
#>  [1,]   NA    1  1.00000000  1.00000000  1.000000000  1.00000000  1.00000000
#>  [2,]   NA    1  0.56501046  0.48501747  0.352903529  0.26309989  0.35214652
#>  [3,]   NA   -1  0.14209151 -0.30004551 -0.329346327 -0.29699283 -0.32498736
#>  [4,]   NA    1  0.78008109  0.64534982  0.542463283  0.46211608  0.49916964
#>  [5,]   NA   -1 -0.80337202 -0.70882472 -0.469876500 -0.42896778 -0.49174199
#>  [6,]   NA    1  0.56501046  0.48501747  0.352903529  0.26309989  0.35214652
#>  [7,]   NA    1  1.00000000  1.00000000  1.000000000  1.00000000  1.00000000
#>  [8,]   NA   -1  0.89699524  0.42711476  0.445763254  0.56340018  0.40437947
#>  [9,]   NA    1 -0.07548312 -0.05770721 -0.015884496  0.37926219  0.44647719
#> [10,]   NA   -1  0.03740517  0.02915300  0.250775474 -0.15275134 -0.38179867
#> [11,]   NA   -1  0.14209151 -0.30004551 -0.329346327 -0.29699283 -0.32498736
#> [12,]   NA   -1  0.89699524  0.42711476  0.445763254  0.56340018  0.40437947
#> [13,]   NA    1  1.00000000  1.00000000  1.000000000  1.00000000  1.00000000
#> [14,]   NA   -1 -0.50848709 -0.03041053 -0.006640857  0.20757600  0.15674132
#> [15,]   NA    1  0.47528311  0.10218339  0.163428041 -0.03575711  0.05739739
#> [16,]   NA    1  0.78008109  0.64534982  0.542463283  0.46211608  0.49916964
#> [17,]   NA    1 -0.07548312 -0.05770721 -0.015884496  0.37926219  0.44647719
#> [18,]   NA   -1 -0.50848709 -0.03041053 -0.006640857  0.20757600  0.15674132
#> [19,]   NA    1  1.00000000  1.00000000  1.000000000  1.00000000  1.00000000
#> [20,]   NA   -1 -0.99927272 -0.99527357 -0.034434260 -0.27056851 -0.36035489
#> [21,]   NA   -1 -0.80337202 -0.70882472 -0.469876500 -0.42896778 -0.49174199
#> [22,]   NA   -1  0.03740517  0.02915300  0.250775474 -0.15275134 -0.38179867
#> [23,]   NA    1  0.47528311  0.10218339  0.163428041 -0.03575711  0.05739739
#> [24,]   NA   -1 -0.99927272 -0.99527357 -0.034434260 -0.27056851 -0.36035489
#> [25,]   NA    1  1.00000000  1.00000000  1.000000000  1.00000000  1.00000000
#>             [,8]        [,9]         [,10]        [,11]       [,12]
#>  [1,]  1.0000000  1.00000000  1.000000e+00  1.000000000  1.00000000
#>  [2,]  0.3196817  0.17045850  9.150219e-05  0.139865415  0.23166354
#>  [3,] -0.1207477  0.20493548  2.625496e-01  0.369468178  0.44879964
#>  [4,]  0.5784996  0.68349574  5.426259e-01  0.574674672  0.61864992
#>  [5,] -0.3558083 -0.13510526 -1.685249e-02 -0.019104038 -0.14534561
#>  [6,]  0.3196817  0.17045850  9.150219e-05  0.139865415  0.23166354
#>  [7,]  1.0000000  1.00000000  1.000000e+00  1.000000000  1.00000000
#>  [8,]  0.3453758  0.22964343  1.828537e-01  0.329651436  0.42928727
#>  [9,]  0.3340412  0.23121032  2.278110e-01  0.296851354  0.38113586
#> [10,] -0.3768547 -0.39609429 -4.156059e-01 -0.382365655 -0.47256783
#> [11,] -0.1207477  0.20493548  2.625496e-01  0.369468178  0.44879964
#> [12,]  0.3453758  0.22964343  1.828537e-01  0.329651436  0.42928727
#> [13,]  1.0000000  1.00000000  1.000000e+00  1.000000000  1.00000000
#> [14,]  0.3845756  0.51971276  5.103827e-01  0.547196234  0.61128882
#> [15,]  0.1696688  0.26079361  2.796791e-01  0.247699648  0.02145079
#> [16,]  0.5784996  0.68349574  5.426259e-01  0.574674672  0.61864992
#> [17,]  0.3340412  0.23121032  2.278110e-01  0.296851354  0.38113586
#> [18,]  0.3845756  0.51971276  5.103827e-01  0.547196234  0.61128882
#> [19,]  1.0000000  1.00000000  1.000000e+00  1.000000000  1.00000000
#> [20,] -0.1142165  0.00944566  7.640232e-03  0.005231241 -0.14003288
#> [21,] -0.3558083 -0.13510526 -1.685249e-02 -0.019104038 -0.14534561
#> [22,] -0.3768547 -0.39609429 -4.156059e-01 -0.382365655 -0.47256783
#> [23,]  0.1696688  0.26079361  2.796791e-01  0.247699648  0.02145079
#> [24,] -0.1142165  0.00944566  7.640232e-03  0.005231241 -0.14003288
#> [25,]  1.0000000  1.00000000  1.000000e+00  1.000000000  1.00000000
#>              [,13]        [,14]        [,15]       [,16]       [,17]
#>  [1,]  1.000000000  1.000000000  1.000000000  1.00000000  1.00000000
#>  [2,]  0.125163001  0.123106833  0.115106112  0.11940811  0.03083497
#>  [3,]  0.480509687  0.452726283  0.449674348  0.41564975  0.18627580
#>  [4,]  0.408377521  0.393041092  0.331180263  0.31402514  0.25898383
#>  [5,] -0.180874381 -0.199955662 -0.191972166 -0.19437788 -0.13941943
#>  [6,]  0.125163001  0.123106833  0.115106112  0.11940811  0.03083497
#>  [7,]  1.000000000  1.000000000  1.000000000  1.00000000  1.00000000
#>  [8,]  0.219663372  0.218514458  0.238367849  0.11211551  0.16907227
#>  [9,]  0.515985171  0.515245614  0.238063342  0.14381810  0.15147992
#> [10,] -0.330324480 -0.326315240 -0.198545769 -0.25254440 -0.26249934
#> [11,]  0.480509687  0.452726283  0.449674348  0.41564975  0.18627580
#> [12,]  0.219663372  0.218514458  0.238367849  0.11211551  0.16907227
#> [13,]  1.000000000  1.000000000  1.000000000  1.00000000  1.00000000
#> [14,]  0.296917656  0.301291880  0.193232806  0.25305895  0.25559216
#> [15,] -0.054983098 -0.036957112 -0.002945164  0.06581515  0.02898498
#> [16,]  0.408377521  0.393041092  0.331180263  0.31402514  0.25898383
#> [17,]  0.515985171  0.515245614  0.238063342  0.14381810  0.15147992
#> [18,]  0.296917656  0.301291880  0.193232806  0.25305895  0.25559216
#> [19,]  1.000000000  1.000000000  1.000000000  1.00000000  1.00000000
#> [20,] -0.001257366  0.007479881 -0.166536844 -0.10738211 -0.11164193
#> [21,] -0.180874381 -0.199955662 -0.191972166 -0.19437788 -0.13941943
#> [22,] -0.330324480 -0.326315240 -0.198545769 -0.25254440 -0.26249934
#> [23,] -0.054983098 -0.036957112 -0.002945164  0.06581515  0.02898498
#> [24,] -0.001257366  0.007479881 -0.166536844 -0.10738211 -0.11164193
#> [25,]  1.000000000  1.000000000  1.000000000  1.00000000  1.00000000
#>             [,18]       [,19]        [,20]
#>  [1,]  1.00000000  1.00000000  1.000000000
#>  [2,]  0.04340762  0.05495704  0.008884942
#>  [3,]  0.15837422  0.18578819  0.189796935
#>  [4,]  0.27371257  0.29514250  0.275192864
#>  [5,] -0.10605329 -0.05860514 -0.120088774
#>  [6,]  0.04340762  0.05495704  0.008884942
#>  [7,]  1.00000000  1.00000000  1.000000000
#>  [8,]  0.14329506  0.16426209  0.155155860
#>  [9,]  0.16735319  0.19262976  0.195762287
#> [10,] -0.22679730 -0.18124775 -0.132114057
#> [11,]  0.15837422  0.18578819  0.189796935
#> [12,]  0.14329506  0.16426209  0.155155860
#> [13,]  1.00000000  1.00000000  1.000000000
#> [14,]  0.21300958  0.32571085  0.323781049
#> [15,] -0.01938507  0.07497370  0.062455982
#> [16,]  0.27371257  0.29514250  0.275192864
#> [17,]  0.16735319  0.19262976  0.195762287
#> [18,]  0.21300958  0.32571085  0.323781049
#> [19,]  1.00000000  1.00000000  1.000000000
#> [20,] -0.06507791  0.12930939  0.134049225
#> [21,] -0.10605329 -0.05860514 -0.120088774
#> [22,] -0.22679730 -0.18124775 -0.132114057
#> [23,] -0.01938507  0.07497370  0.062455982
#> [24,] -0.06507791  0.12930939  0.134049225
#> [25,]  1.00000000  1.00000000  1.000000000