Resumen de dplyr: Equivalente de ".drop = FALSE" para mantener los grupos con longitud cero en la salida

97

Cuando se utiliza la función de summarisewith , las categorías vacías se eliminan de forma predeterminada. Puede cambiar este comportamiento agregando . Sin embargo, esto no funciona cuando se usa con . ¿Existe otra forma de mantener categorías vacías en el resultado?plyrddply.drop = FALSEsummarisedplyr

Aquí tienes 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. ¿Existe algún dplyrmétodo para lograr el mismo resultado que .drop=FALSEen plyr?

eipi10
fuente

Respuestas:

26

Dado que dplyr 0.8 group_by obtuvo el .dropargumento que hace exactamente lo que solicitó:

df = data.frame(a=rep(1:3,4), b=rep(1:2,6))
df$b = factor(df$b, levels=1:3)

df %>%
  group_by(b, .drop=FALSE) %>%
  summarise(count_a=length(a))

#> # A tibble: 3 x 2
#>   b     count_a
#>   <fct>   <int>
#> 1 1           6
#> 2 2           6
#> 3 3           0

Una nota adicional para acompañar la respuesta de @ Moody_Mudskipper: el uso .drop=FALSEpuede dar resultados potencialmente inesperados cuando una o más variables de agrupación no están codificadas como factores. Vea ejemplos a continuación:

library(dplyr)
data(iris)

# Add an additional level to Species
iris$Species = factor(iris$Species, levels=c(levels(iris$Species), "empty_level"))

# Species is a factor and empty groups are included in the output
iris %>% group_by(Species, .drop=FALSE) %>% tally

#>   Species         n
#> 1 setosa         50
#> 2 versicolor     50
#> 3 virginica      50
#> 4 empty_level     0

# Add character column
iris$group2 = c(rep(c("A","B"), 50), rep(c("B","C"), each=25))

# Empty groups involving combinations of Species and group2 are not included in output
iris %>% group_by(Species, group2, .drop=FALSE) %>% tally

#>   Species     group2     n
#> 1 setosa      A         25
#> 2 setosa      B         25
#> 3 versicolor  A         25
#> 4 versicolor  B         25
#> 5 virginica   B         25
#> 6 virginica   C         25
#> 7 empty_level <NA>       0

# Turn group2 into a factor
iris$group2 = factor(iris$group2)

# Now all possible combinations of Species and group2 are included in the output, 
#  whether present in the data or not
iris %>% group_by(Species, group2, .drop=FALSE) %>% tally

#>    Species     group2     n
#>  1 setosa      A         25
#>  2 setosa      B         25
#>  3 setosa      C          0
#>  4 versicolor  A         25
#>  5 versicolor  B         25
#>  6 versicolor  C          0
#>  7 virginica   A          0
#>  8 virginica   B         25
#>  9 virginica   C         25
#> 10 empty_level A          0
#> 11 empty_level B          0
#> 12 empty_level C          0

Created on 2019-03-13 by the reprex package (v0.2.1)
Moody_Mudskipper
fuente
Agregué una nota adicional a su respuesta. No dude en eliminarlo si no le gusta la edición.
eipi10
He presenté una cuestión acerca de este en github para averiguar si se trata de un fallo, o el comportamiento previsto.
eipi10
@ eipi10 un poco más corto es el uso de count:iris %>% count(Species, group2, .drop=FALSE)
Tjebo
59

El problema aún está abierto, pero mientras tanto, especialmente porque sus datos ya están factorizados, puede usar completefrom "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
A5C1D2H2I1M1N2O1R2T1
fuente
11
Me costó mucho golpear la cabeza contra la pared para resolver esto, así que lo mencionaré aquí ... Si agrupa por 2 variables, y son caracteres en lugar de factores, deberá usarlas ungroup()antes de completar. Si alguna vez nota que completeno está completando, ungroupprobablemente sea necesario.
williamsurles
¿Qué pasa si tiene aún más variables de agrupación? Obtengo una gran cantidad de filas (mucho más que mi marco de datos original) si uso todas las variables de agrupación de mi group_by
TobiO
1
Lo descubrí: tienes que usar el anidamiento :-) Así que pon todas las Variables que no deberían combinarse entre sí complete(variablewithdroppedlevels, nesting(var1,var2,var3))(en realidad está en la ayuda porque completetodavía me tomó un tiempo descubrirlo
TobiO
20

solución dplyr:

Primero haz df agrupado

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 marco de datos 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 viendo recuentos, los NAvalores se cambian a 0.

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

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

Un truco:

Pensé que publicaría un truco terrible que funciona en este caso por el interés. Dudo seriamente que realmente group_by()debas hacer esto, pero muestra cómo genera los atributos como si df$bfuera 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)

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

oob_val <- nrow(by_b)+1

modificar atributos a "truco" summarise():

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

haz el resumen:

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

indexar y reemplazar todas las apariciones de oob_val

res[res == oob_val] <- 0

que da lo previsto:

> res
Source: local data frame [3 x 2]

b count_a
1 1       6
2 2       6
3 3       0
npjc
fuente
11

esto no es exactamente lo que se preguntó en la pregunta, pero al menos para este ejemplo simple, podría 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
talat
fuente