Последовательные / скользящие суммы в векторе в R

Предположим, что в R у меня есть следующий вектор:

[1 2 3 10 20 30]

Как выполнить операцию, при которой по каждому индексу суммируются 3 последовательных элемента, в результате чего получается следующий вектор:

[6 15 33 60]

где первый элемент = 1 + 2 + 3, второй элемент = 2 + 3 + 10 и т.д ...? Спасибо


person user2834313    schedule 05.10.2013    source источник


Ответы (6)


У вас есть вектор, а не массив. Вы можете использовать функцию rollapply из пакета zoo, чтобы получить то, что вам нужно.

> x <- c(1, 2, 3, 10, 20, 30)
> #library(zoo)
> rollapply(x, 3, sum)
[1]  6 15 33 60

Взгляните на ?rollapply, чтобы узнать больше о том, что rollapply делает и как его использовать.

person Jilber Urbina    schedule 05.10.2013
comment
спасибо, это именно то, что я хотел. Отмечу как ответ (сейчас не могу из-за ограничения по времени). Это самый быстрый способ сделать это? Спасибо - person user2834313; 05.10.2013
comment
Пакет zoo теперь также содержит функцию rollsum. rollsum(x, 3) - person JohannesNE; 24.10.2018

Я собрал пакет для обработки таких функций "прокрутки", который предлагает функциональность, аналогичную rollapply zoo, но с Rcpp на бэкэнде. Проверьте RcppRoll на CRAN.

library(microbenchmark)
library(zoo)
library(RcppRoll)

x <- rnorm(1E5)

all.equal( m1 <- rollapply(x, 3, sum), m2 <- roll_sum(x, 3) )

## from flodel
rsum.cumsum <- function(x, n = 3L) {
  tail(cumsum(x) - cumsum(c(rep(0, n), head(x, -n))), -n + 1)
}

microbenchmark(
  unit="ms",
  times=10,
  rollapply(x, 3, sum),
  roll_sum(x, 3),
  rsum.cumsum(x, 3)
)

дает мне

Unit: milliseconds
                 expr         min          lq      median         uq         max neval
 rollapply(x, 3, sum) 1056.646058 1068.867550 1076.550463 1113.71012 1131.230825    10
       roll_sum(x, 3)    0.405992    0.442928    0.457642    0.51770    0.574455    10
    rsum.cumsum(x, 3)    2.610119    2.821823    6.469593   11.33624   53.798711    10

Вы можете найти это полезным, если вас беспокоит скорость.

person Kevin Ushey    schedule 05.10.2013
comment
приятно, +1. Это заставляет меня задаться вопросом: будет ли Rcpp cumsum намного быстрее, чем R? Правильно ли ваши функции обрабатывают NA? - person flodel; 05.10.2013
comment
Для cumsum, вероятно, нет - это уже примитив и, следовательно, вероятно, просто цикл C. По вопросу АН: это хороший момент. Сейчас с ними обращаются непоследовательно. Большинство операций возвращают NA, если один из элементов в окне - NA, хотя sd возвращает NaN. min и max игнорируют NA, в отличие от R. И я думаю, na.option был бы полезным параметром. - person Kevin Ushey; 05.10.2013
comment
@KevinUshey: Отлично, спасибо. Это действительно быстро. - person user2834313; 05.10.2013

Если скорость важна, вы можете использовать фильтр свертки и отрезать концы:

rsum.filter <- function(x, n = 3L) filter(x, rep(1, n))[-c(1, length(x))]

Или, что еще быстрее, запишите это как разницу между двумя совокупными суммами:

rsum.cumsum <- function(x, n = 3L) tail(cumsum(x) - cumsum(c(rep(0, n), head(x, -n))), -n + 1)

Оба используют только базовые функции. Некоторые тесты:

x <- sample(1:1000)

rsum.rollapply <- function(x, n = 3L) rollapply(x, n, sum)
rsum.sapply    <- function(x, n = 3L) sapply(1:(length(x)-n+1),function(i){
                                       sum(x[i:(i+n-1)])})

library(microbenchmark)
microbenchmark(
  rsum.rollapply(x),
  rsum.sapply(x),
  rsum.filter(x),
  rsum.cumsum(x)
)

# Unit: microseconds
#               expr       min        lq    median         uq       max neval
#  rsum.rollapply(x) 12891.315 13267.103 14635.002 17081.5860 28059.998   100
#     rsum.sapply(x)  4287.533  4433.180  4547.126  5148.0205 12967.866   100
#     rsum.filter(x)   170.165   208.661   269.648   290.2465   427.250   100
#     rsum.cumsum(x)    97.539   130.289   142.889   159.3055   449.237   100

Также я полагаю, что все методы будут быстрее, если x и все применяемые веса будут целыми числами, а не числовыми.

person flodel    schedule 05.10.2013

Используя только базовый R, вы можете:

v <- c(1, 2, 3, 10, 20, 30)
grp <- 3

res <- sapply(1:(length(v)-grp+1),function(x){sum(v[x:(x+grp-1)])})

> res
[1]  6 15 33 60

Другой способ, более быстрый, чем sapply (сравнимый с rsum.cumsum @ flodel), заключается в следующем:

res <- rowSums(outer(1:(length(v)-grp+1),1:grp,FUN=function(i,j){v[(j - 1) + i]}))

Вот обновленный тест flodel:

x <- sample(1:1000)

rsum.rollapply <- function(x, n = 3L) rollapply(x, n, sum)
rsum.sapply    <- function(x, n = 3L) sapply(1:(length(x)-n+1),function(i){sum(x[i:(i+n-1)])})
rsum.filter <- function(x, n = 3L) filter(x, rep(1, n))[-c(1, length(x))]
rsum.cumsum <- function(x, n = 3L) tail(cumsum(x) - cumsum(c(rep(0, n), head(x, -n))), -n + 1)
rsum.outer <- function(x, n = 3L) rowSums(outer(1:(length(x)-n+1),1:n,FUN=function(i,j){x[(j - 1) + i]}))


library(microbenchmark)
microbenchmark(
  rsum.rollapply(x),
  rsum.sapply(x),
  rsum.filter(x),
  rsum.cumsum(x),
  rsum.outer(x)
)


# Unit: microseconds
#              expr      min        lq     median         uq       max neval
# rsum.rollapply(x) 9464.495 9929.4480 10223.2040 10752.7960 11808.779   100
#    rsum.sapply(x) 3013.394 3251.1510  3466.9875  4031.6195  7029.333   100
#    rsum.filter(x)  161.278  178.7185   229.7575   242.2375   359.676   100
#    rsum.cumsum(x)   65.280   70.0800    88.1600    95.1995   181.758   100
#     rsum.outer(x)   66.880   73.7600    82.8795    87.0400   131.519   100
person digEmAll    schedule 05.10.2013
comment
Потрясающие! Благодарю. К сожалению, я не могу проголосовать, потому что у меня недостаточно очков. - person user2834313; 05.10.2013
comment
@ user2834313: нет проблем;) - person digEmAll; 05.10.2013
comment
Добавлен новый возможный способ;) - person digEmAll; 06.10.2013

Если вам нужна настоящая скорость, попробуйте

rsum.cumdiff <- function(x, n = 3L) (cs <- cumsum(x))[-(1:(n-1))] - c(0,cs[1:(length(x)-n)])

Все дело в базе R, и обновление микробенчмарка flodel говорит само за себя.

x <- sample(1:1000)

rsum.rollapply <- function(x, n = 3L) rollapply(x, n, sum)
rsum.sapply    <- function(x, n = 3L) sapply(1:(length(x)-n+1),function(i){sum(x[i:(i+n-1)])})
rsum.filter <- function(x, n = 3L) filter(x, rep(1, n))[-c(1, length(x))]
rsum.cumsum <- function(x, n = 3L) tail(cumsum(x) - cumsum(c(rep(0, n), head(x, -n))), -n + 1)
rsum.outer <- function(x, n = 3L) rowSums(outer(1:(length(x)-n+1),1:n,FUN=function(i,j){x[(j - 1) + i]}))
rsum.cumdiff <- function(x, n = 3L) (cs <- cumsum(x))[-(1:(n-1))] - c(0, cs[1:(length(x)-n)])

all.equal(rsum.rollapply(x), rsum.sapply(x))
# [1] TRUE
all.equal(rsum.sapply(x), rsum.filter(x))
# [1] TRUE
all.equal(rsum.filter(x), rsum.outer(x))
# [1] TRUE
all.equal(rsum.outer(x), rsum.cumsum(x))
# [1] TRUE
all.equal(rsum.cumsum(x), rsum.cumdiff(x))
# [1] TRUE

library(microbenchmark)
microbenchmark(
  rsum.rollapply(x),
  rsum.sapply(x),
  rsum.filter(x),
  rsum.cumsum(x),
  rsum.outer(x),
  rsum.cumdiff(x)
)

# Unit: microseconds
#               expr      min        lq       mean    median        uq       max neval
#  rsum.rollapply(x) 3369.211 4104.2415 4630.89799 4391.7560 4767.2710 12002.904   100
#     rsum.sapply(x)  850.425  999.2730 1355.56383 1086.0610 1246.5450  6915.877   100
#     rsum.filter(x)   48.970   67.1525   97.28568   96.2430  113.6975   248.728   100
#     rsum.cumsum(x)   47.515   62.7885   89.12085   82.1825  106.6675   230.303   100
#      rsum.outer(x)   69.819   85.3340  160.30133   92.6070  109.0920  5740.119   100
#    rsum.cumdiff(x)    9.698   12.6070   70.01785   14.3040   17.4555  5346.423   100

## R version 3.5.1 "Feather Spray"
## zoo and microbenchmark compiled under R 3.5.3

Как ни странно, второй раз через микробенчмарк все шустрее:

microbenchmark(
       rsum.rollapply(x),
       rsum.sapply(x),
       rsum.filter(x),
       rsum.cumsum(x),
       rsum.outer(x),
       rsum.cumdiff(x)
   )

# Unit: microseconds
#               expr      min        lq       mean    median        uq      max neval
#  rsum.rollapply(x) 3127.272 3477.5750 3869.38566 3593.4540 3858.9080 7836.603   100
#     rsum.sapply(x)  844.122  914.4245 1059.89841  965.3335 1032.2425 5184.968   100
#     rsum.filter(x)   47.031   60.8490   80.53420   74.1830   90.9100  260.365   100
#     rsum.cumsum(x)   45.092   55.2740   69.90630   64.4855   81.4555  122.668   100
#      rsum.outer(x)   68.850   76.6070   88.49533   82.1825   91.8800  166.304   100
#    rsum.cumdiff(x)    9.213   11.1520   13.18387   12.1225   13.5770   49.456   100

person scoco    schedule 03.02.2020

также можно использовать библиотеку runner

x <- c(1, 2, 3, 10, 20, 30)

runner::sum_run(x, k=3, na_pad = T)
#> [1] NA NA  6 15 33 60

или slider тоже полезно

x <- c(1, 2, 3, 10, 20, 30)

slider::slide_sum(x, before = 2, complete = T)
#> [1] NA NA  6 15 33 60

Создано 14 июня 2021 года пакетом REPEX (v2.0.0)

person AnilGoyal    schedule 14.06.2021