2010-09-10 3 views
35

У меня есть симуляция, которая имеет огромный агрегат и совмещает шаг прямо посередине. Я прототипировал этот процесс, используя функцию plyr ddply(), которая отлично работает для огромного процента моих потребностей. Но мне нужно, чтобы этот шаг агрегации был быстрее, поскольку я должен запускать моделирование 10K. Я уже масштабирую симуляции параллельно, но если бы этот один шаг был быстрее, я мог бы значительно уменьшить количество нужных мне узлов.R: ускорение операций «group by»

Вот разумное упрощение того, что я пытаюсь сделать:

library(Hmisc) 

# Set up some example data 
year <- sample(1970:2008, 1e6, rep=T) 
state <- sample(1:50, 1e6, rep=T) 
group1 <- sample(1:6, 1e6, rep=T) 
group2 <- sample(1:3, 1e6, rep=T) 
myFact <- rnorm(100, 15, 1e6) 
weights <- rnorm(1e6) 
myDF <- data.frame(year, state, group1, group2, myFact, weights) 

# this is the step I want to make faster 
system.time(aggregateDF <- ddply(myDF, c("year", "state", "group1", "group2"), 
        function(df) wtd.mean(df$myFact, weights=df$weights) 
           ) 
      ) 

Все советы и предложения приветствуются!

+1

Относительно производительности, но проверка 'weighted.mean' в базе – hadley

+1

О, это удобно. Вы можете видеть, что я узнал R по поиску Google для того, что мне нужно сделать;) –

ответ

37

Вместо обычного кадра данных R, вы можете использовать неизменный кадр данных, который возвращает указатели на оригинал, когда подмножество и может быть намного быстрее:

idf <- idata.frame(myDF) 
system.time(aggregateDF <- ddply(idf, c("year", "state", "group1", "group2"), 
    function(df) wtd.mean(df$myFact, weights=df$weights))) 

# user system elapsed 
# 18.032 0.416 19.250 

Если бы я был написать функция plyr настроена именно к этой ситуации, я бы что-то вроде этого:

system.time({ 
    ids <- id(myDF[c("year", "state", "group1", "group2")], drop = TRUE) 
    data <- as.matrix(myDF[c("myFact", "weights")]) 
    indices <- plyr:::split_indices(seq_len(nrow(data)), ids, n = attr(ids, "n")) 

    fun <- function(rows) { 
    weighted.mean(data[rows, 1], data[rows, 2]) 
    } 
    values <- vapply(indices, fun, numeric(1)) 

    labels <- myDF[match(seq_len(attr(ids, "n")), ids), 
    c("year", "state", "group1", "group2")] 
    aggregateDF <- cbind(labels, values) 
}) 

# user system elapsed 
# 2.04 0.29 2.33 

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

+3

'idata.frame' был добавлен в plyr 1.0. – hadley

+0

Я столкнулся с созданием индексов и т. Д. С помощью data.table и почти полностью отказался от этой идеи. Я надеялся на 50% улучшение. Это намного превышает мои ожидания. –

+0

, имея небольшую проблему, заставляя это работать правильно ... Но я учусь, когда я ухожу ... Я поменял данные на myDF, но не уверен, где проблема. –

7

Вы используете последнюю версию plyr (обратите внимание: это еще не дошло до всех зеркал CRAN)? Если это так, вы можете просто запустить это параллельно.

Вот llply пример, но то же самое должно применяться к ddply:

x <- seq_len(20) 
    wait <- function(i) Sys.sleep(0.1) 
    system.time(llply(x, wait)) 
    # user system elapsed 
    # 0.007 0.005 2.005 

    library(doMC) 
    registerDoMC(2) 
    system.time(llply(x, wait, .parallel = TRUE)) 
    # user system elapsed 
    # 0.020 0.011 1.038 

Edit:

Ну, другие зацикливания подходы хуже, так что это, вероятно, потребует либо (а) C/C++ или (b) более фундаментальное переосмысление того, как вы это делаете. Я даже не пытался использовать by(), потому что это очень медленно в моем опыте.

groups <- unique(myDF[,c("year", "state", "group1", "group2")]) 
system.time(
aggregateDF <- do.call("rbind", lapply(1:nrow(groups), function(i) { 
    df.tmp <- myDF[myDF$year==groups[i,"year"] & myDF$state==groups[i,"state"] & myDF$group1==groups[i,"group1"] & myDF$group2==groups[i,"group2"],] 
    cbind(groups[i,], wtd.mean(df.tmp$myFact, weights=df.tmp$weights)) 
})) 
) 

aggregateDF <- data.frame() 
system.time(
for(i in 1:nrow(groups)) { 
    df.tmp <- myDF[myDF$year==groups[i,"year"] & myDF$state==groups[i,"state"] & myDF$group1==groups[i,"group1"] & myDF$group2==groups[i,"group2"],] 
    aggregateDF <- rbind(aggregateDF, data.frame(cbind(groups[i,], wtd.mean(df.tmp$myFact, weights=df.tmp$weights)))) 
} 
) 
+0

, который помогает мне в одном случае с машиной, но я уже взорвав это в Hadoop и переписывая каждый узел (больше процессов, чем процессоры). Но я очень рад видеть, что распараллеливание превращает его в plyr! –

8

Я бы профиль с базой R

g <- with(myDF, paste(year, state, group1, group2)) 
x <- with(myDF, c(tapply(weights * myFact, g, sum)/tapply(weights, g, sum))) 
aggregateDF <- myDF[match(names(x), g), c("year", "state", "group1", "group2")] 
aggregateDF$V1 <- x 

На моей машине это занимает 5с сравнить 67sec с оригинальным кодом.

EDIT Просто нашел другую скорость с rowsum функции:

g <- with(myDF, paste(year, state, group1, group2)) 
X <- with(myDF, rowsum(data.frame(a=weights*myFact, b=weights), g)) 
x <- X$a/X$b 
aggregateDF2 <- myDF[match(rownames(X), g), c("year", "state", "group1", "group2")] 
aggregateDF2$V1 <- x 

Он принимает 3сек!

+2

Второй занимает 5 секунд на моем компьютер, поэтому plyr все еще узко избивает базу;) (Плюс он упорядочивает строки правильно) – hadley

+2

Но спасибо за указатель на 'rowsum' - так сложно идти в ногу с множеством функций агрегации в базе R. – hadley

+0

Я знал там Должно быть, это был способ сделать это, но я изо всех сил пытался понять это. У меня, как правило, есть такая борьба с семейством. –

5

Я обычно использую вектор индекса с tapply, когда функция применяется имеет несколько векторной арг:

system.time(tapply(1:nrow(myDF), myDF[c('year', 'state', 'group1', 'group2')], function(s) weighted.mean(myDF$myFact[s], myDF$weights[s]))) 
# user system elapsed 
# 1.36 0.08 1.44 

Я использую простую обертку, которая эквивалентна но скрывает беспорядок:

tmapply(list(myDF$myFact, myDF$weights), myDF[c('year', 'state', 'group1', 'group2')], weighted.mean) 

Отредактировано, что бы включить в форму для комментариев:

tmapply = function(XS, INDEX, FUN, ..., simplify=T) { 
    FUN = match.fun(FUN) 
    if (!is.list(XS)) 
    XS = list(XS) 
    tapply(1:length(XS[[1L]]), INDEX, function(s, ...) 
    do.call(FUN, c(lapply(XS, `[`, s), list(...))), ..., simplify=simplify) 
} 
+0

, что очень изящно, чтобы увидеть, что сделано в базе R. Спасибо! –

+1

Просто добавьте: 'as.data.frame (as.table (RESULTS))' это простой способ создать 'data.frame' с выхода. – Marek

+0

Используется ли это 'tmapply'? https://stat.ethz.ch/pipermail/r-help/2002-October/025773.html – Shane

25

Далее 2x убыстрение и более краткий код:

library(data.table) 
dtb <- data.table(myDF, key="year,state,group1,group2") 
system.time( 
    res <- dtb[, weighted.mean(myFact, weights), by=list(year, state, group1, group2)] 
) 
# user system elapsed 
# 0.950 0.050 1.007 

Моего первый пост, поэтому, пожалуйста, хорошо;)


С data.table v1.9.2, setDT функции экспортируются, который будет конвертировать data.frame до data.tableпо ссылке (в соответствии с data.table parlance - все функции set* изменяют объект по ссылке). Это означает, что нет ненужного копирования и, следовательно, быстро. Вы можете время, но это будет небрежно.

require(data.table) 
system.time({ 
    setDT(myDF) 
    res <- myDF[, weighted.mean(myFact, weights), 
      by=list(year, state, group1, group2)] 
}) 
# user system elapsed 
# 0.970 0.024 1.015 

Это в противоположность 1.264 секунд с раствором OP в выше, где data.table(.) используется для создания dtb.

+0

Хороший пост! Спасибо за ответ. Однако, чтобы соответствовать другим методам, шаг, который создает таблицу данных и индекс, должен находиться внутри шага system.time(). –

+2

Действительно, но он остается самым быстрым. Было бы неплохо иметь опцию ddply для работы с data.tables или использовать data.tables под капотом (я только что открыл data.table, ища решения той же проблемы, но я предпочел бы более ddply-like синтаксис для этого случая). – datasmurf