Суммировать каждые n баллов

У меня есть вектор, и мне нужно просуммировать каждые n числа и вернуть результаты. Вот как я планирую это делать сейчас. Есть лучший способ сделать это?

v = 1:100
n = 10
sidx = seq.int(from=1, to=length(v), by=n)
eidx = c((sidx-1)[2:length(sidx)], length(v))
thesum = sapply(1:length(sidx), function(i) sum(v[sidx[i]:eidx[i]]))

Это дает:

thesum
 [1]  55 155 255 355 455 555 655 755 855 955

person Alex    schedule 07.03.2013    source источник


Ответы (8)


ОБНОВИТЬ:

Если вы хотите суммировать каждые n последовательных чисел, используйте colSums
Если вы хотите суммировать каждое n-е число, используйте rowSums

согласно комментарию Джоша, это будет работать, только если n правильно делит length(v).

rowSums(matrix(v, nrow=n))
 [1] 460 470 480 490 500 510 520 530 540 550

colSums(matrix(v, nrow=n))
 [1]  55 155 255 355 455 555 655 755 855 955

person Ricardo Saporta    schedule 07.03.2013
comment
Работает, только если length(v) делится на n без остатка. В противном случае переработка векторов вас укусит. (См., Например, v <- 1:3; n <- 2; matrix(v, nrow=n).) - person Josh O'Brien; 07.03.2013
comment
Будет работать, только если matrix(..., byrow=TRUE), поэтому @Andrie ответит, где он использует colSums, а не rowSums. - person plannapus; 07.03.2013
comment
@plannapus, было непонятно, хочет ли OP каждое n-последовательное или каждое n-е число. - person Ricardo Saporta; 07.03.2013
comment
Если это каждое nth число, я бы сказал, что ответом будет 550. 10-е, 20-е и т.д .. Не 1, 11 ..., 2, 12 ... и т.д .. - person Arun; 07.03.2013
comment
@ Арун, ну где ты начинаешь считать n чисел? - person Ricardo Saporta; 07.03.2013
comment
Я понимаю каждое n-е число 1: 100 как каждое 10-е число: 10, 20, 30, ..., 100. В сумме получается 550, более ранние ответы Андри и Джоша. - person Arun; 07.03.2013

Обновлять

Старая версия не работает. Здесь новый awnser, который использует rep для создания фактора группирования. Не нужно использовать cut:

n <- 5 
vv <- sample(1:1000,100)
seqs <- seq_along(vv)
tapply(vv,rep(seqs,each=n)[seqs],FUN=sum)

Вы можете использовать tapply

tapply(1:100,cut(1:100,10),FUN=sum)

или получить список

by(1:100,cut(1:100,10),FUN=sum)

ИЗМЕНИТЬ

Если у вас 1:92, вы можете заменить его следующим образом:

cut(1:92,seq(1,92,10),include.lowest=T)
person agstudy    schedule 07.03.2013
comment
Я понимаю, почему вам нравится ответ, но это не сработает для случайного вектора чисел, в котором вы хотите суммировать каждые n элементов, не так ли? - person Max M; 01.11.2016
comment
@MaxM Вы правы. Я обновлю свой ответ, чтобы включить новую версию. - person agstudy; 02.11.2016

Один из способов - преобразовать ваш вектор в матрицу, а затем взять суммы столбцов:

colSums(matrix(v, nrow=n))
[1]  55 155 255 355 455 555 655 755 855 955

Просто будьте осторожны: это неявно предполагает, что ваш входной вектор может быть преобразован в матрицу. Если это невозможно, R будет повторно использовать элементы вашего вектора для завершения матрицы.

person Andrie    schedule 07.03.2013

Я добавлю еще один способ сделать это без каких-либо функций из семейства apply

v <- 1:100
n <- 10

diff(c(0, cumsum(v)[slice.index(v, 1)%%n == 0]))
##  [1]  55 155 255 355 455 555 655 755 855 955
person CHP    schedule 07.03.2013
comment
Просто имейте в виду, что когда, например, v <- 1:99, это не будет включать сумму последних 9 чисел (что может быть, а может и нет). - person Josh O'Brien; 07.03.2013
comment
nv = length(v); i = c(seq_len(nv %/% n) * n, if (nv %% n) nv else NULL), а затем diff(c(0, cumsum(v)[i])), похоже, получают крайние случаи length(v) == 0 и length(v) %% n != 0. - person Martin Morgan; 07.03.2013
comment
slice.index(v, 1) можно было бы заменить просто v, если я не ошибаюсь. - person Rich Scriven; 01.11.2017

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

f0 <- function(v, n) {
    sidx = seq.int(from=1, to=length(v), by=n)
    eidx = c((sidx-1)[2:length(sidx)], length(v))
    sapply(1:length(sidx), function(i) sum(v[sidx[i]:eidx[i]]))
}

f1 <- function(v, n, na.rm=TRUE) {    # 'tapply'
    unname(tapply(v, (seq_along(v)-1) %/% n, sum, na.rm=na.rm))
}

f2 <- function(v, n, na.rm=TRUE) {    # 'matrix'
    nv <- length(v)
    if (nv %% n)
        v[ceiling(nv / n) * n] <- NA
    colSums(matrix(v, n), na.rm=na.rm)
}

f3 <- function(v, n) {                # 'cumsum'
    nv = length(v)
    i <- c(seq_len(nv %/% n) * n, if (nv %% n) nv else NULL)
    diff(c(0L, cumsum(v)[i]))
}

Базовые тестовые примеры могут быть

v = list(1:4, 1:5, c(NA, 2:4), integer())
n = 2

f0 не проходит финальный тест, но, вероятно, это можно исправить

> f0(integer(), n)
Error in sidx[i]:eidx[i] : NA/NaN argument

Подход cumsum f3 подвержен ошибке округления, и наличие NA в начале v "отравляет" более поздние результаты.

> f3(c(NA, 2:4), n)
[1] NA NA

По производительности оригинальное решение неплохое

> library(rbenchmark)
> cols <- c("test", "elapsed", "relative")
> v <- 1:100; n <- 10
> benchmark(f0(v, n), f1(v, n), f2(v, n), f3(v, n),
+           columns=cols)
      test elapsed relative
1 f0(v, n)   0.012     3.00
2 f1(v, n)   0.065    16.25
3 f2(v, n)   0.004     1.00
4 f3(v, n)   0.004     1.00

но матричное решение f2 кажется быстрым и гибким (например, настройка обработки этого конечного фрагмента, содержащего менее n элементов)

> v <- runif(1e6); n <- 10
> benchmark(f0(v, n), f2(v, n), f3(v, n), columns=cols, replications=10)
      test elapsed relative
1 f0(v, n)   5.804   34.141
2 f2(v, n)   0.170    1.000
3 f3(v, n)   0.251    1.476
person Martin Morgan    schedule 07.03.2013

Один из способов - использовать rollapply из zoo:

rollapply(v, width=n, FUN=sum, by=n)
# [1]  55 155 255 355 455 555 655 755 855 955

И в случае, если length(v) не кратно n:

v <- 1:92

rollapply(v, width=n, FUN=sum, by=n, partial=T, align="left")
# [1]  55 155 255 355 455 555 655 755 855 183
person Scarabee    schedule 14.05.2017

Немного поздно на вечеринку, но я пока не вижу rowsum() ответа. rowsum() оказался более эффективным, чем tapply(), и я думаю, что он также был бы очень эффективным по сравнению с некоторыми другими ответами.

rowsum(v, rep(seq_len(length(v)/n), each=n))[,1]
#  1   2   3   4   5   6   7   8   9  10 
# 55 155 255 355 455 555 655 755 855 955

Использование техники группировки @Josh O'Brien, вероятно, еще больше повысит эффективность.

rowsum(v, (seq_along(v)-1) %/% n)[,1]
#  0   1   2   3   4   5   6   7   8   9 
# 55 155 255 355 455 555 655 755 855 955 

Просто оберните unname(), чтобы удалить имена групп.

person Rich Scriven    schedule 31.10.2017

person    schedule
comment
(+1) это также дает правильный результат, даже если v = 1:92 и n = 10. - person Arun; 07.03.2013