делает ли функция by() растущий список

Создает ли функция by список, который увеличивается по одному элементу за раз?

Мне нужно обработать кадр данных с примерно 4 миллионами наблюдений, сгруппированных по столбцу факторов. Ситуация аналогична примеру ниже:

> # Make 4M rows of data
> x = data.frame(col1=1:4000000, col2=10000001:14000000)
> # Make a factor
> x[,"f"] = x[,"col1"] - x[,"col1"] %% 5
>   
> head(x)
  col1     col2 f
1    1 10000001 0
2    2 10000002 0
3    3 10000003 0
4    4 10000004 0
5    5 10000005 5
6    6 10000006 5

Теперь tapply в одном из столбцов занимает разумное количество времени:

> t1 = Sys.time()
> z = tapply(x[, 1], x[, "f"], mean)
> Sys.time() - t1
Time difference of 22.14491 secs

Но если я сделаю это:

z = by(x[, 1], x[, "f"], mean)

Это не заканчивается в одно и то же время (я сдался через минуту).

Конечно, в приведенном выше примере можно было бы использовать tapply, но на самом деле мне нужно обработать несколько столбцов вместе. Как лучше это сделать?


person Anand    schedule 04.12.2012    source источник
comment
Можете ли вы предоставить небольшую (например, 10 строк на N столбцов) выборку ваших данных вместе с алгоритмом, который вы хотите выполнить? Вполне возможно, что подойдет какой-то другой инструмент *apply (некоторые допускают несколько аргументов) или вложенная конструкция tapply(tapply(...)).   -  person Carl Witthoft    schedule 04.12.2012


Ответы (2)


by медленнее, чем tapply, потому что он оборачивает by. Давайте посмотрим на некоторые тесты: tapply в этой ситуации более чем в 3 раза быстрее, чем при использовании by

ОБНОВЛЕНО, чтобы включить замечательную рекомендацию @Roland:

library(rbenchmark)
library(data.table)
dt <- data.table(x,key="f")

using.tapply <- quote(tapply(x[, 1], x[, "f"], mean))
using.by <- quote(by(x[, 1], x[, "f"], mean))
using.dtable <- quote(dt[,mean(col1),by=key(dt)])

times <- benchmark(using.tapply, using.dtable, using.by, replications=10, order="relative")
times[,c("test", "elapsed", "relative")] 

#------------------------#
#         RESULTS        # 
#------------------------#

#       COMPARING tapply VS by     #
#-----------------------------------
#              test elapsed relative
#   1  using.tapply   2.453    1.000
#   2      using.by   8.889    3.624

#   COMPARING data.table VS tapply VS by   #
#------------------------------------------#
#             test elapsed relative
#   2  using.dtable   0.168    1.000
#   1  using.tapply   2.396   14.262
#   3      using.by   8.566   50.988

Если x$f является фактором, потеря эффективности между tapply и by еще больше!

Хотя обратите внимание, что они оба улучшаются по сравнению с нефакторными входными данными, в то время как data.table остается примерно таким же или хуже

x[, "f"] <- as.factor(x[, "f"])
dt <- data.table(x,key="f")
times <- benchmark(using.tapply, using.dtable, using.by, replications=10, order="relative")
times[,c("test", "elapsed", "relative")] 

#               test elapsed relative
#   2   using.dtable   0.175    1.000
#   1   using.tapply   1.803   10.303
#   3       using.by   7.854   44.880



Что касается почему, краткий ответ находится в самой документации.

?by :

Описание

Функция by — это объектно-ориентированная оболочка для tapply, применяемая к фреймам данных.

давайте посмотрим на источник для by (или, точнее, by.data.frame):

by.data.frame
function (data, INDICES, FUN, ..., simplify = TRUE) 
{
    if (!is.list(INDICES)) {
        IND <- vector("list", 1L)
        IND[[1L]] <- INDICES
        names(IND) <- deparse(substitute(INDICES))[1L]
    }
    else IND <- INDICES
    FUNx <- function(x) FUN(data[x, , drop = FALSE], ...)
    nd <- nrow(data)
    ans <- eval(substitute(tapply(seq_len(nd), IND, FUNx, simplify = simplify)), 
        data)
    attr(ans, "call") <- match.call()
    class(ans) <- "by"
    ans
}

Мы сразу видим, что по-прежнему есть вызов tapply плюс множество дополнений (включая вызовы deparse(substitute(.)) и eval(substitute(.)), оба из которых относительно медленные). Поэтому имеет смысл, что ваш tapply будет относительно быстрее, чем аналогичный вызов by.

person Ricardo Saporta    schedule 04.12.2012
comment
Эти функции относительно медленные, но я не думаю, что они несут ответственность за разницу между tapply и by — они могут добавлять миллисекунды, но не секунды. Я подозреваю, что основная причина медлительности заключается в том, что by индексируется во фрейм данных, что намного медленнее, чем индексирование в вектор. - person hadley; 05.12.2012

Что касается лучшего способа сделать это: со строками 4M вы должны использовать data.table.

library(data.table)
dt <- data.table(x,key="f")
dt[,mean(col1),by=key(dt)]

dt[,list(mean1=mean(col1),mean2=mean(col2)),by=key(dt)]
dt[,lapply(.SD,mean),by=key(dt)]
person Roland    schedule 04.12.2012
comment
Отлично, большое спасибо, а также спасибо @ricardo-saporta за результаты тестов. - person Anand; 04.12.2012