dplyr summary: équivalent de ".drop = FALSE" pour conserver les groupes de longueur nulle en sortie

97

Lors de l'utilisation summarisede plyrla ddplyfonction avec, les catégories vides sont supprimées par défaut. Vous pouvez modifier ce comportement en ajoutant .drop = FALSE. Cependant, cela ne fonctionne pas lors de l'utilisation summariseavec dplyr. Existe-t-il un autre moyen de conserver des catégories vides dans le résultat?

Voici un exemple avec de fausses données.

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

Pas exactement ce que j'espérais. Existe-t-il une dplyrméthode pour obtenir le même résultat que .drop=FALSEdans plyr?

eipi10
la source

Réponses:

26

Depuis que dplyr 0.8 a group_by gagné l' .dropargument qui fait exactement ce que vous avez demandé:

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

Une note supplémentaire pour aller avec la réponse de @ Moody_Mudskipper: L'utilisation .drop=FALSEpeut donner des résultats potentiellement inattendus lorsqu'une ou plusieurs variables de regroupement ne sont pas codées en tant que facteurs. Voir les exemples ci-dessous:

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
la source
J'ai ajouté une note supplémentaire à votre réponse. N'hésitez pas à supprimer si vous n'aimez pas la modification.
eipi10
J'ai déposé un problème à ce sujet sur github pour savoir s'il s'agit d'un bogue ou du comportement prévu.
eipi10
@ eipi10 légèrement plus court est l'utilisation de count:iris %>% count(Species, group2, .drop=FALSE)
Tjebo
59

Le problème est toujours ouvert, mais en attendant, d'autant plus que vos données sont déjà prises en compte, vous pouvez utiliser à completepartir de "tidyr" pour obtenir ce que vous recherchez:

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 vous souhaitez que la valeur de remplacement soit égale à zéro, vous devez le spécifier avec 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
la source
11
Il m'a fallu beaucoup de coups de tête contre le mur pour comprendre cela, donc je vais le mentionner ici ... Si vous groupez par 2 variables, et ce sont des caractères plutôt que des facteurs, vous devrez les utiliser ungroup()avant de terminer. Si jamais vous remarquez completene pas avoir terminé, ungroupc'est probablement nécessaire.
williamsurles
Et si vous avez encore plus de variables de regroupement? J'obtiens un grand nombre de lignes (bien plus que ma base de données d'origine) si j'utilise toutes les variables de regroupement de mon group_by
TobiO
1
Je l'ai compris: vous devez utiliser l'imbrication :-) Donc, mettez toutes les variables qui ne devraient pas également être combinées entre elles complete(variablewithdroppedlevels, nesting(var1,var2,var3))(c'est en fait dans l'aide car completeil m'a encore fallu un certain temps pour comprendre
TobiO
20

solution dplyr:

Faire d'abord groupé df

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

puis nous résumons les niveaux qui se produisent en comptant avec n()

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

puis nous fusionnons nos résultats dans un bloc de données qui contient tous les niveaux de facteur:

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

enfin, dans ce cas, puisque nous examinons les nombres, les NAvaleurs sont changées à 0.

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

Cela peut également être implémenté de manière fonctionnelle, voir les réponses: Ajouter des lignes aux données groupées avec dplyr?

Un hack:

Je pensais que je publierais un terrible hack qui fonctionne dans ce cas pour l'intérêt. Je doute sérieusement que vous deviez jamais faire cela, mais cela montre comment group_by()génère les attributs comme s'il df$bs'agissait d'un vecteur de caractère et non d'un facteur avec des niveaux. De plus, je ne prétends pas comprendre cela correctement - mais j'espère que cela m'aidera à apprendre - c'est la seule raison pour laquelle je le publie!

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

définir une valeur "hors limites" qui ne peut pas exister dans l'ensemble de données.

oob_val <- nrow(by_b)+1

modifier les attributs en "truc" summarise():

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

faire le résumé:

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

indexer et remplacer toutes les occurrences de oob_val

res[res == oob_val] <- 0

qui donne le prévu:

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

b count_a
1 1       6
2 2       6
3 3       0
npjc
la source
11

ce n'est pas exactement ce qui a été demandé dans la question, mais au moins pour cet exemple simple, vous pouvez obtenir le même résultat en utilisant xtabs, par exemple:

en utilisant dplyr:

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

ou plus court:

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

résultat (égal dans les deux cas):

  b Freq
1 1    6
2 2    6
3 3    0
talat
la source