Unir los valores agregados al dataframe original

Uno de los patrones de diseño que uso una y otra vez es realizar un “grupo por” o “dividir, aplicar, combinar (SAC)” en un dataframe y luego unir los datos agregados a los datos originales. Esto es útil, por ejemplo, al calcular la desviación de cada condado de la media del estado en un dataframe con muchos estados y condados. Rara vez mi cálculo agregado es solo una media simple, pero es un buen ejemplo. A menudo resuelvo este problema de la siguiente manera:

require(plyr) set.seed(1) ## set up some data group1 <- rep(1:3, 4) group2 <- sample(c("A","B","C"), 12, rep=TRUE) values <- rnorm(12) df <- data.frame(group1, group2, values) ## got some data, so let's aggregate group1Mean <- ddply( df, "group1", function(x) data.frame( meanValue = mean(x$values) ) ) df <- merge( df, group1Mean ) df 

Lo cual produce buenos datos agregados como los siguientes:

 > df group1 group2 values meanValue 1 1 A 0.48743 -0.121033 2 1 A -0.04493 -0.121033 3 1 C -0.62124 -0.121033 4 1 C -0.30539 -0.121033 5 2 A 1.51178 0.004804 6 2 B 0.73832 0.004804 7 2 A -0.01619 0.004804 8 2 B -2.21470 0.004804 9 3 B 1.12493 0.758598 10 3 C 0.38984 0.758598 11 3 B 0.57578 0.758598 12 3 A 0.94384 0.758598 

Esto funciona, pero ¿hay formas alternativas de hacerlo que mejoren la legibilidad, el rendimiento, etc.?

Una línea de código hace el truco:

 new <- ddply( df, "group1", transform, numcolwise(mean)) new group1 group2 values meanValue 1 1 A 0.48742905 -0.121033381 2 1 A -0.04493361 -0.121033381 3 1 C -0.62124058 -0.121033381 4 1 C -0.30538839 -0.121033381 5 2 A 1.51178117 0.004803931 6 2 B 0.73832471 0.004803931 7 2 A -0.01619026 0.004803931 8 2 B -2.21469989 0.004803931 9 3 B 1.12493092 0.758597929 10 3 C 0.38984324 0.758597929 11 3 B 0.57578135 0.758597929 12 3 A 0.94383621 0.758597929 identical(df, new) [1] TRUE 

Creo que ave() es más útil aquí que la llamada plyr que muestra (no estoy lo suficientemente familiarizado con plyr para saber si puede hacer lo que quiere con plyr directamente o no, ¡me sorprendería si no puede!) o las otras alternativas de base R ( aggregate() , tapply() ) .:

 > with(df, ave(values, group1, FUN = mean)) [1] -0.121033381 0.004803931 0.758597929 -0.121033381 0.004803931 [6] 0.758597929 -0.121033381 0.004803931 0.758597929 -0.121033381 [11] 0.004803931 0.758597929 

Puede usar within() o transform() para incrustar este resultado directamente en df :

 > df2 <- within(df, meanValue <- ave(values, group1, FUN = mean)) > head(df2) group1 group2 values meanValue 1 1 A 0.4874291 -0.121033381 2 2 B 0.7383247 0.004803931 3 3 B 0.5757814 0.758597929 4 1 C -0.3053884 -0.121033381 5 2 A 1.5117812 0.004803931 6 3 C 0.3898432 0.758597929 > df3 <- transform(df, meanValue = ave(values, group1, FUN = mean)) > all.equal(df2,df3) [1] TRUE 

Y si el orden es importante:

 > head(df2[order(df2$group1, df2$group2), ]) group1 group2 values meanValue 1 1 A 0.48742905 -0.121033381 10 1 A -0.04493361 -0.121033381 4 1 C -0.30538839 -0.121033381 7 1 C -0.62124058 -0.121033381 5 2 A 1.51178117 0.004803931 11 2 A -0.01619026 0.004803931 

En términos de rendimiento, puede hacer este mismo tipo de operación utilizando el paquete data.table , que se ha integrado en agregación y es muy rápido gracias a los índices y una implementación basada en C. Por ejemplo, dado que df ya existe en su ejemplo:

 biblioteca ("data.table")
 dt <-as.data.table (df)
 setkey (dt, group1)
 dt <-dt [, list (group2, values, meanValue = mean (values)], by = group1]
 dt
       group1 group2 values ​​meanValue
  [1,] 1 A 0.82122120 0.18810771
  [2,] 1 C 0.78213630 0.18810771
  [3,] 1 C 0.61982575 0.18810771
  [4,] 1 A -1.47075238 0.18810771
  [5,] 2 B 0.59390132 0.03354688
  [6,] 2 A 0.07456498 0.03354688
  [7,] 2 B -0.05612874 0.03354688
  [8,] 2 A -0.47815006 0.03354688
  [9,] 3 B 0.91897737 -0.20205707
 [10,] 3 C -1.98935170 -0.20205707
 [11,] 3 B -0.15579551 -0.20205707
 [12,] 3 A 0.41794156 -0.20205707 

No lo he comparado, pero en mi experiencia es mucho más rápido.

Si decides ir por el camino de los datos, que creo que vale la pena explorar si trabajas con grandes conjuntos de datos, realmente necesitas leer los documentos porque hay algunas diferencias con el dataframe que pueden morderte si no estás enterado de ellos. Sin embargo, notablemente data.table generalmente funciona con cualquier función que espere un dataframe, ya que un data.table alegará que su tipo es un dataframe (la tabla de datos hereda del dataframe).

[Feb 2011]


[Ago 2012] Actualización de Mateo:

Lo nuevo en v1.8.2 lanzado a CRAN en julio de 2012 es := por grupo. Esto es muy similar a la respuesta anterior, pero agrega la nueva columna por referencia a dt por lo que no hay copia ni necesidad de un paso de fusión ni de volver a listar las columnas existentes para volver junto con el agregado. No es necesario establecer la setkey primero, y hace frente a grupos no contiguos (es decir, grupos que no están agrupados).

Esto es significativamente más rápido para grandes conjuntos de datos, y tiene una syntax simple y breve:

 dt <- as.data.table(df) dt[, meanValue := mean(values), by = group1] 

¿No puedes simplemente agregar x a la función que pasas a ddply ?

 df <- ddply( df, "group1", function(x) data.frame( x, meanValue = mean(x$values) ) ) 

Una posibilidad dplyr :

 library(dplyr) df %>% group_by(group1) %>% mutate(meanValue = mean(values)) 

Esto devuelve el dataframe en el orden original. Agregue arrange(group1) a la tubería si desea ordenar por “grupo1”.