¿Resumiendo múltiples columnas con dplyr?

Estoy luchando un poco con la syntax dplyr. Tengo un dataframe con diferentes variables y una variable de agrupación. Ahora quiero calcular la media de cada columna dentro de cada grupo, usando dplyr en R.

df % group_by(grp) %>% summarise(mean(a)) 

Esto me da el promedio para la columna “a” para cada grupo indicado por “grp”.

Mi pregunta es: ¿es posible obtener los medios para cada columna dentro de cada grupo a la vez? ¿O tengo que repetir df %>% group_by(grp) %>% summarise(mean(a)) para cada columna?

Lo que me gustaría tener es algo así como

 df %>% group_by(grp) %>% summarise(mean(a:d)) # "mean(a:d)" does not work 

El paquete dplyr contiene summarise_all para este objective:

 df %>% group_by(grp) %>% summarise_all(funs(mean)) #> Source: local data frame [3 x 5] #> #> grp abcd #> (int) (dbl) (dbl) (dbl) (dbl) #> 1 1 3.000000 2.666667 2.666667 3.333333 #> 2 2 2.666667 2.666667 2.500000 2.833333 #> 3 3 4.000000 1.000000 4.000000 3.000000 

Si desea resumir solo ciertas columnas, use las funciones summarise_at o summarise_if .

Alternativamente, el paquete purrrlyr proporciona la misma funcionalidad:

 df %>% slice_rows("grp") %>% dmap(mean) #> Source: local data frame [3 x 5] #> #> grp abcd #> (int) (dbl) (dbl) (dbl) (dbl) #> 1 1 3.000000 2.666667 2.666667 3.333333 #> 2 2 2.666667 2.666667 2.500000 2.833333 #> 3 3 4.000000 1.000000 4.000000 3.000000 

Además, no te olvides de data.table :

 setDT(df)[, lapply(.SD, mean), by = grp] #> grp abcd #> 1: 3 3.714286 3.714286 2.428571 2.428571 #> 2: 1 1.000000 4.000000 5.000000 2.000000 #> 3: 2 4.000000 4.500000 3.000000 3.000000 

Tratemos de comparar el rendimiento.

 library(dplyr) library(purrrlyr) library(data.table) library(benchr) n <- 10000 df <- data.frame( a = sample(1:5, n, replace = TRUE), b = sample(1:5, n, replace = TRUE), c = sample(1:5, n, replace = TRUE), d = sample(1:5, n, replace = TRUE), grp = sample(1:3, n, replace = TRUE) ) dt <- setDT(df) benchmark( dplyr = df %>% group_by(grp) %>% summarise_all(funs(mean)), purrrlyr = df %>% slice_rows("grp") %>% dmap(mean), data.table = dt[, lapply(.SD, mean), by = grp] ) #> Benchmark summary: #> Time units : microseconds #> expr n.eval min lw.qu median mean up.qu max total relative #> dplyr 100 3490 3550 3710 3890 3780 15100 389000 6.98 #> purrrlyr 100 2540 2590 2680 2920 2860 12000 292000 5.04 #> data.table 100 459 500 531 563 571 1380 56300 1.00 

Podemos resumir usando summarize_at , summarize_all y summarize_if en dplyr 0.7.4 . Podemos establecer las múltiples columnas y funciones usando vars y funs argument como el código a continuación. El lado izquierdo de la fórmula funs se asigna al sufijo de vars resumidos. En dplyr 0.7.4 , summarise_each (y mutate_each ) ya está en desuso, por lo que no podemos usar estas funciones.

 options(scipen = 100, dplyr.width = Inf, dplyr.print_max = Inf) library(dplyr) packageVersion("dplyr") # [1] '0.7.4' set.seed(123) df <- data_frame( a = sample(1:5, 10, replace=T), b = sample(1:5, 10, replace=T), c = sample(1:5, 10, replace=T), d = sample(1:5, 10, replace=T), grp = as.character(sample(1:3, 10, replace=T)) # For convenience, specify character type ) df %>% group_by(grp) %>% summarise_each(.vars = letters[1:4], .funs = c(mean="mean")) # `summarise_each()` is deprecated. # Use `summarise_all()`, `summarise_at()` or `summarise_if()` instead. # To map `funs` over a selection of variables, use `summarise_at()` # Error: Strings must match column names. Unknown columns: mean 

Deberías cambiar al siguiente código. Los siguientes códigos tienen el mismo resultado.

 # summarise_at df %>% group_by(grp) %>% summarise_at(.vars = letters[1:4], .funs = c(mean="mean")) df %>% group_by(grp) %>% summarise_at(.vars = names(.)[1:4], .funs = c(mean="mean")) df %>% group_by(grp) %>% summarise_at(.vars = vars(a,b,c,d), .funs = c(mean="mean")) # summarise_all df %>% group_by(grp) %>% summarise_all(.funs = c(mean="mean")) # summarise_if df %>% group_by(grp) %>% summarise_if(.predicate = function(x) is.numeric(x), .funs = funs(mean="mean")) # A tibble: 3 x 5 # grp a_mean b_mean c_mean d_mean #      # 1 1 2.80 3.00 3.6 3.00 # 2 2 4.25 2.75 4.0 3.75 # 3 3 3.00 5.00 1.0 2.00 

También puedes tener múltiples funciones.

 df %>% group_by(grp) %>% summarise_at(.vars = letters[1:2], .funs = c(Mean="mean", Sd="sd")) # A tibble: 3 x 5 # grp a_Mean b_Mean a_Sd b_Sd #      # 1 1 2.80 3.00 1.4832397 1.870829 # 2 2 4.25 2.75 0.9574271 1.258306 # 3 3 3.00 5.00 NA NA 

Simplemente puede pasar más argumentos para summarise :

 df %>% group_by(grp) %>% summarise(mean(a), mean(b), mean(c), mean(d)) 

Fuente: dataframe local [3 x 5]

  grp mean(a) mean(b) mean(c) mean(d) 1 1 2.500000 3.500000 2.000000 3.0 2 2 3.800000 3.200000 3.200000 2.8 3 3 3.666667 3.333333 2.333333 3.0 

Para completar: con dplyr v0.2 ddply con colwise también hará esto:

 > ddply(df, .(grp), colwise(mean)) grp abcd 1 1 4.333333 4.00 1.000000 2.000000 2 2 2.000000 2.75 2.750000 2.750000 3 3 3.000000 4.00 4.333333 3.666667 

pero es más lento, al menos en este caso:

 > microbenchmark(ddply(df, .(grp), colwise(mean)), df %>% group_by(grp) %>% summarise_each(funs(mean))) Unit: milliseconds expr min lq mean ddply(df, .(grp), colwise(mean)) 3.278002 3.331744 3.533835 df %>% group_by(grp) %>% summarise_each(funs(mean)) 1.001789 1.031528 1.109337 median uq max neval 3.353633 3.378089 7.592209 100 1.121954 1.133428 2.292216 100 

Todos los ejemplos son geniales, pero creo que agregaría uno más para mostrar cómo trabajar en un formato “ordenado” simplifica las cosas. En este momento, el dataframe está en formato “ancho”, lo que significa que las variables “a” a “d” están representadas en columnas. Para llegar a un formato “ordenado” (o largo), puede usar gather() del paquete tidyr que cambia las variables de las columnas “a” a “d” en filas. Luego usa las group_by() y summarize() para obtener la media de cada grupo. Si desea presentar los datos en un formato amplio, simplemente agregue una llamada adicional a la función spread() .

 library(tidyverse) # Create reproducible df set.seed(101) df <- tibble(a = sample(1:5, 10, replace=T), b = sample(1:5, 10, replace=T), c = sample(1:5, 10, replace=T), d = sample(1:5, 10, replace=T), grp = sample(1:3, 10, replace=T)) # Convert to tidy format using gather df %>% gather(key = variable, value = value, a:d) %>% group_by(grp, variable) %>% summarize(mean = mean(value)) %>% spread(variable, mean) #> Source: local data frame [3 x 5] #> Groups: grp [3] #> #> grp abcd #> *      #> 1 1 3.000000 3.5 3.250000 3.250000 #> 2 2 1.666667 4.0 4.666667 2.666667 #> 3 3 3.333333 3.0 2.333333 2.333333