2011-02-17 7 views
17

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 marco de datos y luego unir los datos agregados volver a los datos originales. Esto es útil, por ejemplo, al calcular la desviación de cada condado de la media del estado en un marco de datos con muchos estados y condados. Rara vez mi cálculo agregado es solo una media simple, pero es un buen ejemplo. A menudo resolver este problema de la siguiente manera:Uniendo valores agregados al marco de datos original

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 

que produce buenos datos agregados como el siguiente:

> 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 maneras alternativas de hacer esto, que mejoran la legibilidad, el rendimiento, etc?

+0

Ver http://stackoverflow.com/questions/4998846/applying-an-aggregate-function-over-multiple-different-slices/5000040#5000040 –

Respuesta

18

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 
+0

me había olvidado por completo de 'transform'. Obvio en retrospectiva. Pero gracias por ilustrar 'numcolwise' con el que no estaba familiarizado. –

+0

Eso es una gran expresión idiomática, pero es difícil de hacer cuando algunas variables deben ser sumas y otras significa. – richiemorrisroe

+0

@richiemorrisroe ¿Algo más complicado que cualquier otro idioma? – Andrie

9

¿No puedes añadir x a la función que se pasa a ddply?

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

No me di cuenta de que funcionaría. Hábil. –

13

Creo ave() es más útil aquí que la plyr llama usted muestra (no estoy lo suficientemente familiarizado con plyr para saber si se puede hacer lo que quiera con plyr directamente o no, Me sorprendería si no se 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 utilizar 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 
+0

No me di cuenta de que otras funciones podrían usarse con 'ave()' ... Claramente debería haber leído los documentos antes. –

13

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

 
library("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

I have not benchmarked it, but in my experience it is a lot faster.

If you decide to go down the data.table road, which I think is worth exploring if you work with large data sets, you really need to read the docs because there are some differences from data frame that can bite you if you are unaware of them. However, notably data.table generally does work with any function expecting a data frame,as a data.table will claim its type is data frame (data table inherits from data frame).

[ Feb 2011 ]


[ Aug 2012 ] Update from Matthew :

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

Esta es significantemente más rápido para grandes conjuntos de datos, y tiene un simple y corto sintaxis:

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

Una posibilidad dplyr:

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

Esto devuelve la trama de datos en el orden original. Agregue arrange(group1) a la tubería si desea ordenar por "grupo1".

Cuestiones relacionadas