Aplicar varias funciones de resumen en varias variables por grupo en una llamada

Tengo el siguiente dataframe

x <- read.table(text = " id1 id2 val1 val2 1 ax 1 9 2 ax 2 4 3 ay 3 5 4 ay 4 9 5 bx 1 7 6 by 4 4 7 bx 3 9 8 by 2 8", header = TRUE) 

Quiero calcular la media de val1 y val2 agrupados por id1 e id2, y simultáneamente contar el número de filas para cada combinación id1-id2. Puedo realizar cada cálculo por separado:

 # calculate mean aggregate(. ~ id1 + id2, data = x, FUN = mean) # count rows aggregate(. ~ id1 + id2, data = x, FUN = length) 

Para hacer ambos cálculos en una llamada, lo intenté

 do.call("rbind", aggregate(. ~ id1 + id2, data = x, FUN = function(x) data.frame(m = mean(x), n = length(x)))) 

Sin embargo, obtengo una salida confusa junto con una advertencia:

 # mn # id1 1 2 # id2 1 1 # 1.5 2 # 2 2 # 3.5 2 # 3 2 # 6.5 2 # 8 2 # 7 2 # 6 2 # Warning message: # In rbind(id1 = c(1L, 2L, 1L, 2L), id2 = c(1L, 1L, 2L, 2L), val1 = list( : # number of columns of result is not a multiple of vector length (arg 1) 

Podría usar el paquete plyr, pero mi conjunto de datos es bastante grande y plyr es muy lento (casi inutilizable) cuando crece el tamaño del conjunto de datos.

¿Cómo puedo usar funciones aggregate u otras para realizar varios cálculos en una llamada?

Puede hacerlo todo en un solo paso y obtener un etiquetado adecuado:

 > aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) # id1 id2 val1.mn val1.n val2.mn val2.n # 1 ax 1.5 2.0 6.5 2.0 # 2 bx 2.0 2.0 8.0 2.0 # 3 ay 3.5 2.0 7.0 2.0 # 4 by 3.0 2.0 6.0 2.0 

Esto crea un dataframe con dos columnas de id y dos columnas de matriz:

 str( aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) 'data.frame': 4 obs. of 4 variables: $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2 $ val1: num [1:4, 1:2] 1.5 2 3.5 3 2 2 2 2 ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "mn" "n" $ val2: num [1:4, 1:2] 6.5 8 7 6 2 2 2 2 ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "mn" "n" 

Como señala @lord.garbage a continuación, esto se puede convertir a un dataframe con columnas “simples” mediante do.call(data.frame, ...)

 str( do.call(data.frame, aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) ) 'data.frame': 4 obs. of 6 variables: $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2 $ val1.mn: num 1.5 2 3.5 3 $ val1.n : num 2 2 2 2 $ val2.mn: num 6.5 8 7 6 $ val2.n : num 2 2 2 2 

Esta es la syntax para múltiples variables en el LHS:

 aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) 

Teniendo esto en cuenta en la pregunta:

Podría usar el paquete plyr, pero mi conjunto de datos es bastante grande y plyr es muy lento (casi inutilizable) cuando crece el tamaño del conjunto de datos.

Luego, en data.table ( 1.9.4+ ) puedes probar:

 > DT id1 id2 val1 val2 1: ax 1 9 2: ax 2 4 3: ay 3 5 4: ay 4 9 5: bx 1 7 6: by 4 4 7: bx 3 9 8: by 2 8 > DT[ , .(mean(val1), mean(val2), .N), by = .(id1, id2)] # simplest id1 id2 V1 V2 N 1: ax 1.5 6.5 2 2: ay 3.5 7.0 2 3: bx 2.0 8.0 2 4: by 3.0 6.0 2 > DT[ , .(val1.m = mean(val1), val2.m = mean(val2), count = .N), by = .(id1, id2)] # named id1 id2 val1.m val2.m count 1: ax 1.5 6.5 2 2: ay 3.5 7.0 2 3: bx 2.0 8.0 2 4: by 3.0 6.0 2 > DT[ , c(lapply(.SD, mean), count = .N), by = .(id1, id2)] # mean over all columns id1 id2 val1 val2 count 1: ax 1.5 6.5 2 2: ay 3.5 7.0 2 3: bx 2.0 8.0 2 4: by 3.0 6.0 2 

Para los tiempos que comparan el aggregate (usado en la pregunta y las otras 3 respuestas) con data.table vea este benchmark (los casos agg y agg.x ).

Puede agregar una columna de count , agregar con sum y luego volver a escalar para obtener la mean :

 x$count <- 1 agg <- aggregate(. ~ id1 + id2, data = x,FUN = sum) agg # id1 id2 val1 val2 count # 1 ax 3 13 2 # 2 bx 4 16 2 # 3 ay 7 14 2 # 4 by 6 12 2 agg[c("val1", "val2")] <- agg[c("val1", "val2")] / agg$count agg # id1 id2 val1 val2 count # 1 ax 1.5 6.5 2 # 2 bx 2.0 8.0 2 # 3 ay 3.5 7.0 2 # 4 by 3.0 6.0 2 

Tiene la ventaja de preservar los nombres de sus columnas y crear una sola columna de count .

Tal vez quieres fusionarte ?

 x.mean <- aggregate(. ~ id1+id2, p, mean) x.len <- aggregate(. ~ id1+id2, p, length) merge(x.mean, x.len, by = c("id1", "id2")) id1 id2 val1.x val2.x val1.y val2.y 1 ax 1.5 6.5 2 2 2 ay 3.5 7.0 2 2 3 bx 2.0 8.0 2 2 4 by 3.0 6.0 2 2 

Usando el paquete dplyr puedes lograr esto usando summarise_all . Con esta función de resumen puede aplicar otras funciones (en este caso, mean n() ) a cada una de las columnas que no se agrupan:

 x %>% group_by(id1, id2) %>% summarise_all(funs(mean, n())) 

lo que da:

  id1 id2 val1_mean val2_mean val1_n val2_n 1 ax 1.5 6.5 2 2 2 ay 3.5 7.0 2 2 3 bx 2.0 8.0 2 2 4 by 3.0 6.0 2 2 

Si no desea aplicar la (s) función (es) a todas las columnas que no se agrupan, especifique las columnas a las que se deben aplicar o excluyendo el elemento no deseado con un signo menos usando la función summarise_at() :

 # inclusion x %>% group_by(id1, id2) %>% summarise_at(vars(val1, val2), funs(mean, n())) # exclusion x %>% group_by(id1, id2) %>% summarise_at(vars(-val2), funs(mean, n())) 

También puede usar plyr::each() para introducir funciones múltiples:

 aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = plyr::each(avg = mean, n = length))