Agrupe por columnas múltiples en dplyr, usando entrada de vector de cadena

Intento transferir mi comprensión de plyr a dplyr, pero no puedo entender cómo agrupar por varias columnas.

# make data with weird column names that can't be hard coded data = data.frame( asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE), a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE), value = rnorm(100) ) # get the columns we want to average within columns = names(data)[-3] # plyr - works ddply(data, columns, summarize, value=mean(value)) # dplyr - raises error data %.% group_by(columns) %.% summarise(Value = mean(value)) #> Error in eval(expr, envir, enclos) : index out of bounds 

¿Qué me falta para traducir el ejemplo plyr en una syntax dplyr-esque?

Edición 2017 : Dplyr se ha actualizado, por lo que una solución más simple está disponible. Ver la respuesta seleccionada actualmente.

Dado que esta pregunta se publicó, dplyr agregó versiones con ámbito de group_by ( documentación aquí ). Esto le permite usar las mismas funciones que usaría con select , así:

 data = data.frame( asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE), a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE), value = rnorm(100) ) # get the columns we want to average within columns = names(data)[-3] library(dplyr) df1 <- data %>% group_by_at(vars(one_of(columns))) %>% summarize(Value = mean(value)) #compare plyr for reference df2 <- plyr::ddply(data, columns, plyr::summarize, value=mean(value)) table(df1 == df2, useNA = 'ifany') ## TRUE ## 27 

El resultado de su pregunta de ejemplo es el esperado (consulte la comparación con plyr anterior y el resultado a continuación):

 # A tibble: 9 x 3 # Groups: asihckhdoydkhxiydfgfTgdsx [?] asihckhdoydkhxiydfgfTgdsx a30mvxigxkghc5cdsvxvyv0ja Value    1 AA 0.04095002 2 AB 0.24943935 3 AC -0.25783892 4 BA 0.15161805 5 BB 0.27189974 6 BC 0.20858897 7 CA 0.19502221 8 CB 0.56837548 9 CC -0.22682998 

Tenga en cuenta que dado que dplyr::summarize solo dplyr::summarize una capa de agrupamiento a la vez, todavía tiene actividad en el tibble resultante (que a veces puede capturar personas por sorpresa más adelante). Si desea estar absolutamente seguro frente a comportamientos de agrupación inesperados, siempre puede agregar %>% ungroup a su canalización después de resumir.

Para escribir el código completo, aquí hay una actualización de la respuesta de Hadley con la nueva syntax:

 library(dplyr) df <- data.frame( asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE), a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE), value = rnorm(100) ) # Columns you want to group by grp_cols <- names(df)[-3] # Convert character vector to list of symbols dots <- lapply(grp_cols, as.symbol) # Perform frequency counts df %>% group_by_(.dots=dots) %>% summarise(n = n()) 

salida:

 Source: local data frame [9 x 3] Groups: asihckhdoydk asihckhdoydk a30mvxigxkgh n 1 AA 10 2 AB 10 3 AC 13 4 BA 14 5 BB 10 6 BC 12 7 CA 9 8 CB 12 9 CC 10 

El soporte para esto en dplyr es actualmente bastante débil, eventualmente creo que la syntax será algo así como:

 df %.% group_by(.groups = c("asdfgfTgdsx", "asdfk30v0ja")) 

Pero eso probablemente no estará allí por un tiempo (porque necesito pensar en todas las consecuencias).

Mientras tanto, puedes usar regroup() , que toma una lista de símbolos:

 library(dplyr) df <- data.frame( asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE), a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE), value = rnorm(100) ) df %.% regroup(list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %.% summarise(n = n()) 

Si tiene un vector de caracteres de nombres de columna, puede convertirlos a la estructura correcta con lapply() y as.symbol() :

 vars <- setdiff(names(df), "value") vars2 <- lapply(vars, as.symbol) df %.% regroup(vars2) %.% summarise(n = n()) 

La especificación de cadenas de columnas en dplyr ahora se admite a través de variantes de las funciones dplyr con nombres que terminan en un guión bajo. Por ejemplo, correspondiente a la función group_by hay una función group_by_ que puede tomar argumentos de cadena. Esta viñeta describe la syntax de estas funciones en detalle.

El siguiente fragmento resuelve limpiamente el problema que @sharoz planteó originalmente (tenga en cuenta la necesidad de escribir el argumento .dots ):

 # Given data and columns from the OP data %>% group_by_(.dots = columns) %>% summarise(Value = mean(value)) 

(Tenga en cuenta que dplyr ahora usa el operador %>% y %.% Está en desuso).

Hasta que dplyr tenga soporte completo para los argumentos de cadena, quizás esta esencia sea útil:

https://gist.github.com/skranz/9681509

Contiene un montón de funciones de envoltura como s_group_by, s_mutate, s_filter, etc. que usan argumentos de cadena. Puede mezclarlos con las funciones dplyr normales. Por ejemplo

 cols = c("cyl","gear") mtcars %.% s_group_by(cols) %.% s_summarise("avdisp=mean(disp), max(disp)") %.% arrange(avdisp) 

Funciona si le pasa los objetos (bueno, no lo es, pero …) en lugar de como un vector de caracteres:

 df %.% group_by(asdfgfTgdsx, asdfk30v0ja) %.% summarise(Value = mean(value)) > df %.% + group_by(asdfgfTgdsx, asdfk30v0ja) %.% + summarise(Value = mean(value)) Source: local data frame [9 x 3] Groups: asdfgfTgdsx asdfgfTgdsx asdfk30v0ja Value 1 AC 0.046538002 2 CB -0.286359899 3 BA -0.305159419 4 CA -0.004741504 5 BB 0.520126476 6 CC 0.086805492 7 BC -0.052613078 8 AA 0.368410146 9 AB 0.088462212 

donde df era tu data .

?group_by dice:

  ...: variables to group by. All tbls accept variable names, some will also accept functons of variables. Duplicated groups will be silently dropped. 

que interpreto que significa no las versiones de los nombres de los personajes, sino cómo se referiría a ellos en foo$bar ; bar no está citada aquí. O cómo te referirías a las variables en una fórmula: foo ~ bar .

@Arun también menciona que puedes hacer:

 df %.% group_by("asdfgfTgdsx", "asdfk30v0ja") %.% summarise(Value = mean(value)) 

Pero no se puede pasar algo que no se ha evaluado no es un nombre de una variable en el objeto de datos.

Supongo que esto se debe a los métodos internos que Hadley está usando para buscar las cosas que pasa a través del ... argumento.

 data = data.frame( my.a = sample(LETTERS[1:3], 100, replace=TRUE), my.b = sample(LETTERS[1:3], 100, replace=TRUE), value = rnorm(100) ) group_by(data,newcol=paste(my.a,my.b,sep="_")) %>% summarise(Value=mean(value)) 

Un caso (pequeño) que falta en las respuestas aquí, que quería hacer explícito, es cuando las variables para agrupar se generan dinámicamente a mitad de camino en una canalización:

 library(wakefield) df_foo = r_series(rnorm, 10, 1000) df_foo %>% # 1. create quantized versions of base variables mutate_each( funs(Quantized = . > 0) ) %>% # 2. group_by the indicator variables group_by_( .dots = grep("Quantized", names(.), value = TRUE) ) %>% # 3. summarize the base variables summarize_each( funs(sum(., na.rm = TRUE)), contains("X_") ) 

Esto básicamente muestra cómo usar grep junto con group_by_(.dots = ...) para lograr esto.