dplyr summarize: Equivalente a “.drop = FALSE” para mantener grupos con longitud cero en salida

Al usar summarise con la función ddply , las categorías vacías se descartan por defecto. Puede cambiar este comportamiento agregando .drop = FALSE . Sin embargo, esto no funciona cuando se usa summarise con dplyr . ¿Hay alguna otra forma de mantener categorías vacías en el resultado?

Aquí hay un ejemplo con datos falsos.

 library(dplyr) df = data.frame(a=rep(1:3,4), b=rep(1:2,6)) # Now add an extra level to df$b that has no corresponding value in df$a df$b = factor(df$b, levels=1:3) # Summarise with plyr, keeping categories with a count of zero plyr::ddply(df, "b", summarise, count_a=length(a), .drop=FALSE) b count_a 1 1 6 2 2 6 3 3 0 # Now try it with dplyr df %.% group_by(b) %.% summarise(count_a=length(a), .drop=FALSE) b count_a .drop 1 1 6 FALSE 2 2 6 FALSE 

No es exactamente lo que esperaba. ¿Hay un método dplyr para lograr el mismo resultado que .drop=FALSE en plyr ?

El problema sigue abierto, pero mientras tanto, especialmente debido a que sus datos ya están incluidos, puede usar los datos complete de “tidyr” para obtener lo que podría estar buscando:

 library(tidyr) df %>% group_by(b) %>% summarise(count_a=length(a)) %>% complete(b) # Source: local data frame [3 x 2] # # b count_a # (fctr) (int) # 1 1 6 # 2 2 6 # 3 3 NA 

Si desea que el valor de reemplazo sea cero, debe especificarlo con fill :

 df %>% group_by(b) %>% summarise(count_a=length(a)) %>% complete(b, fill = list(count_a = 0)) # Source: local data frame [3 x 2] # # b count_a # (fctr) (dbl) # 1 1 6 # 2 2 6 # 3 3 0 

solución dplyr:

Primero haz un grupo de df

 by_b <- tbl_df(df) %>% group_by(b) 

luego resumimos esos niveles que ocurren contando con n()

 res <- by_b %>% summarise( count_a = n() ) 

luego fusionamos nuestros resultados en un dataframe que contiene todos los niveles de factores:

 expanded_res <- left_join(expand.grid(b = levels(df$b)),res) 

finalmente, en este caso, dado que estamos mirando los recuentos, los valores de NA se cambian a 0.

 final_counts <- expanded_res[is.na(expanded_res)] <- 0 

Esto también se puede implementar funcionalmente, ver respuestas: ¿ Agregar filas a los datos agrupados con dplyr?

Un truco:

Pensé que publicaría un truco terrible que funciona en este caso por interés. Dudo seriamente que alguna vez lo hagas, pero muestra cómo group_by() genera los atrributes como si df$b fuera un vector de caracteres, no un factor con niveles. Además, no pretendo entender esto correctamente, pero espero que esto me ayude a aprender: ¡esta es la única razón por la que lo estoy publicando!

 by_b <- tbl_df(df) %>% group_by(b) 

define un valor "fuera de límites" que no puede existir en el conjunto de datos.

 oob_val <- nrow(by_b)+1 

modificar atributos para "truco" summarise() :

 attr(by_b, "indices")[[3]] <- rep(NA,oob_val) attr(by_b, "group_sizes")[3] <- 0 attr(by_b, "labels")[3,] <- 3 

hacer el resumen:

 res <- by_b %>% summarise(count_a = n()) 

indexe y reemplace todas las ocurrencias de oob_val

 res[res == oob_val] <- 0 

que da la intención:

 > res Source: local data frame [3 x 2] b count_a 1 1 6 2 2 6 3 3 0 

esto no es exactamente lo que se preguntó en la pregunta, pero al menos para este simple ejemplo, puede obtener el mismo resultado usando xtabs, por ejemplo:

usando dplyr:

 df %.% xtabs(formula = ~ b) %.% as.data.frame() 

o más corto:

 as.data.frame(xtabs( ~ b, df)) 

resultado (igual en ambos casos):

  b Freq 1 1 6 2 2 6 3 3 0