Быстрое скользящее среднее + суммирование
В R я пытаюсь сделать очень быстрое скользящее среднее большого вектора (до 400 тыс. Элементов), используя разные ширины окна, затем для каждой ширины окна суммируем данные по максимуму каждого года. Надеюсь, приведенный ниже пример будет понятен. Я пробовал несколько подходов, и самый быстрый до сих пор, кажется, использует roll_mean
из пакета RcppRoll
для бегущей средней и aggregate
для выбора максимума. Обратите внимание, что проблема с памятью вызывает беспокойство: приведенная ниже версия требует очень мало памяти, так как она выполняет одно скользящее среднее и агрегацию одновременно; это предпочтительнее.
#Example data frame of 10k measurements from 2001 to 2014
n <- 100000
df <- data.frame(rawdata=rnorm(n),
year=sort(sample(2001:2014, size=n, replace=TRUE))
)
ww <- 1:120 #Vector of window widths
dfsumm <- as.data.frame(matrix(nrow=14, ncol=121))
dfsumm[,1] <- 2001:2014
colnames(dfsumm) <- c("year", paste0("D=", ww))
system.time(for (i in 1:length(ww)) {
#Do the rolling mean for this ww
df$tmp <- roll_mean(df$rawdata, ww[i], na.rm=TRUE, fill=NA)
#Aggregate maxima for each year
dfsumm[,i+1] <- aggregate(data=df, tmp ~ year, max)[,2]
}) #28s on my machine
dfsumm
Это дает желаемый результат: data.frame
с 15 строками (годы с 2001 по 2015) и 120 столбцами (ширина окна), содержащими максимум для каждого ww и для каждого года.
Тем не менее, это все еще занимает слишком много времени для вычисления (так как я должен вычислить тысячи из них). Я пытался поиграть с другими вариантами, а именно dplyr
а также data.table
, но я не смог найти что-то быстрее из-за недостатка знаний об этих пакетах.
Какой самый быстрый способ сделать это, используя одно ядро (код уже распараллелен в другом месте)?
2 ответа
Управление памятью, то есть выделение и копирование, убивает вас с вашим подходом.
Вот подход data.table, который назначается по ссылке:
library(data.table)
setDT(df)
alloc.col(df, 200) #allocate sufficient columns
#assign rolling means in a loop
for (i in seq_along(ww))
set(df, j = paste0("D", i), value = roll_mean(df[["rawdata"]],
ww[i], na.rm=TRUE, fill=NA))
dfsumm <- df[, lapply(.SD, max, na.rm = TRUE), by = year] #aggregate
Используя новый frollmean
Функция (добавлена в data.table v1.12.0) позволяет сделать следующее
th = setDTthreads(1L)
df[, paste0("D",ww) := frollmean(rawdata, ww, na.rm=TRUE)]
dfsumm <- df[, lapply(.SD, max, na.rm=TRUE), by=year]
setDTthreads(th)
Вам следует подумать о смещении параллелизма вниз, так как этот вариант использования хорошо распараллелен в frollmean
, Также операция группировки использует параллельную обработку.
Одна проблема с производительностью, которую вы создаете, - это использование динамически растущего вектора с помощью cbind
, Вы можете попытаться выделить ожидаемый размер заранее, а затем заполнить его, используя dfsumm[x] <- y
,