Роллинг / перемещение в среднем по группе
Как создать скользящее среднее с помощью сгруппированных данных. Вот данные
set.seed(31)
dd<-matrix(sample(seq(1:20),30,replace=TRUE),ncol=3)
Добавить идентификатор группы и отсортировать по идентификатору группы
du<-sample(seq(1:4),10,replace=TRUE)
d<-cbind(du,dd)
d<-d[order(d[,1]),]
Это дает скользящее среднее, но игнорирует группу bounderis
d_roll_mean <- apply(d[,2:4], 2,
function(x) {
rollapply(zoo(x), 3, mean, partial=TRUE, align='right')
}
)
Это дает результаты ниже
# cbind(d,d_roll_mean)
# [1,] 1 3 3 12 3.000000 3.000000 12.000000
# [2,] 2 10 13 8 6.500000 8.000000 10.000000
# [3,] 2 17 2 17 10.000000 6.000000 12.333333
# [4,] 3 14 6 3 13.666667 7.000000 9.333333
# [5,] 3 6 20 1 12.333333 9.333333 7.000000
# [6,] 3 1 16 19 7.000000 14.000000 7.666667
# [7,] 3 19 2 11 8.666667 12.666667 10.333333
# [8,] 4 12 1 9 10.666667 6.333333 13.000000
# [9,] 4 10 13 12 13.666667 5.333333 10.666667
# [10,] 4 8 20 7 10.000000 11.333333 9.333333
Вот цель, скользящая средняя по границе группы
# Desired
# [1,] 1 3 3 12 3.000000 3.000000 12.000000
# [2,] 2 10 13 8 10.000000 13.000000 8.000000
# [3,] 2 17 2 17 13.500000 7.500000 12.500000
# [4,] 3 14 6 3 14.000000 6.000000 3.000000
# [5,] 3 6 20 1 10.000000 13.000000 2.000000
# [6,] 3 1 16 19 7.000000 14.000000 7.666667
# [7,] 3 19 2 11 8.666667 12.666667 10.333333
# [8,] 4 12 1 9 12.000000 1.000000 9.000000
# [9,] 4 10 13 12 11.000000 7.000000 10.500000
# [10,] 4 8 20 7 10.000000 8.000000 9.333333
Это близко, но генерирует список по факторам вместо матрицы
doApply <- function(x) {
apply(x, 2,
function(y) {
rollapply(zoo(y), 3, mean, partial=TRUE, align='right')
})
}
d2_roll_mean <- by(d[,2:4], d[,1], doApply)
Так что есть несколько ответов на вопрос, вот как они сравниваются во время выполнения
set.seed(31)
nrow=20000
ncol=600
nun=350
nValues = 20
dd<-matrix(sample(seq(1:nValues),nrow*ncol,replace=TRUE),ncol=ncol)
du<-sample(seq(1:nun),nrow,replace=TRUE)
d<-cbind(du,dd)
d<-d[order(d[,1]),]
library(zoo)
doApply <- function(x) {
apply(x, 2,
function(y) {
rollapply(zoo(y), 3, mean, partial=TRUE, align='right')
})
}
library(data.table)
library(caTools)
fun1<-function(d) {by(d[,-1], d[,1], doApply)}
fun2<- function(d){
DT <- data.table(d, key='du')
DT[, lapply(.SD, function(y)
runmean(y, 3, alg='fast',align='right')), by=du]
}
system.time(d2_roll_mean <- fun1(d))
system.time(d2_roll_mean2 <- fun2(d))
Время показывает, что использование таблиц данных примерно в 10 раз быстрее, чем смена.
user system elapsed
fun1 1048.910 0.378 1049.158
fun2 107.296 0.097 107.392
Я не получаю равенства, но при осмотре они кажутся одинаковыми...
d2a<-do.call(rbind,d2_roll_mean)
d2b<-cbind(1,d2a)
d2c<-data.table(d2b)
setnames(d2c,names(d2c),names(d2_roll_mean2))
all.equal(d2c,d2_roll_mean2)
Выход всех равен
[1] "Attributes: < Length mismatch: comparison on first 1 components >"
[2] "Component “du”: Mean relative difference: 175.6631"
Когда вышеуказанный подход был применен к данным, была сгенерирована следующая ошибка
Error in `[<-`(`*tmp*`, (k2 + 1):n, , value = 2) :
subscript out of bounds
Эта ошибка была результатом некоторых факторов, слишком мало строк. Эти строки были удалены, и процесс сработал. Ссылка: Как отбросить факторы, которые имеют меньше чем n членов
2 ответа
Единственное, чего не хватает, это do.call(rbind,d2_roll_mean)
, Добавить исходные данные:
cbind(d,do.call(rbind,d2_roll_mean))
РЕДАКТИРОВАТЬ: я пробежал это через system.time()
для большего примера, и это занимает свое приятное время:
set.seed(31)
dd <- matrix(sample(seq(1:20),20000*500,replace=TRUE),ncol=500)
du <- sample(seq(1:350),20000,replace=TRUE)
d <- cbind(du,dd)
d <- d[order(d[,1]),]
system.time(d2_roll_mean <- by(d[,-1], d[,1], doApply))
User System elapsed
399.60 0.57 409.91
by()
а также apply()
не самые быстрые функции. На самом деле может быть быстрее проходить через столбцы с помощью for
цикл и делать это грубой силой, опираясь на тот факт, что d
сортируется по идентификатору.
С помощью data.table
а также caTools
library(data.table)
library(caTools)
DT <- data.table(d, key='du')
DT[, lapply(.SD, function(y)
runmean(y, 3, alg='fast',align='right')), by=du]
Обновить
Если вы хотите создать новые столбцы в существующем наборе данных
nm1 <- paste0('V', 2:4)
nm2 <- paste0("V", 4:6)
DT[, (nm1):=lapply(.SD, as.numeric), .SDcols=nm1][,
(nm2):=lapply(.SD, function(y) runmean(y, 3, alg='fast',
align='right')), by=du]