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


Cuando usas summarise con plyres ddply función, las categorías vacías se eliminan de forma predeterminada. 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 dplyr método para lograr el mismo resultado que .drop=FALSE en plyr?


75
2018-03-20 03:52


origen


Respuestas:


El problema sigue abierto, pero mientras tanto, especialmente debido a que sus datos ya están factorizados, puede usar complete de "tidyr" para obtener lo que podrías 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

44
2018-03-18 19:07



solución dplyr:

Primero haz un grupo de df

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

luego resumimos esos niveles que ocurren contando 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, ya que estamos viendo cuenta el NA los valores 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 datos agrupados con dplyr?

Un truco:

Pensé que publicaría un terrible hack que funciona en este caso por el bien de los intereses. Dudo seriamente que alguna vez lo hagas, pero muestra cómo group_by() genera los atributos como si df$b era 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 "engañar" 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

20
2018-05-24 17:11



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

9
2018-05-05 18:46