dplyr summarize: equivalente a ".drop = FALSE" per mantenere i gruppi con lunghezza zero in output


97

Quando si utilizza summarisecon plyrla ddplyfunzione di, le categorie vuote vengono eliminate per impostazione predefinita. Puoi modificare questo comportamento aggiungendo .drop = FALSE. Tuttavia, questo non funziona quando si utilizza summarisecon dplyr. C'è un altro modo per mantenere le categorie vuote nel risultato?

Ecco un esempio con dati falsi.

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

Non esattamente quello che speravo. Esiste un dplyrmetodo per ottenere lo stesso risultato .drop=FALSEdi plyr?


Risposte:


26

Poiché dplyr 0.8 ha group_by ottenuto l' .dropargomento che fa proprio quello che hai chiesto:

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 aggiuntiva per la risposta di @ Moody_Mudskipper: l'utilizzo .drop=FALSEpuò dare risultati potenzialmente inaspettati quando una o più variabili di raggruppamento non sono codificate come fattori. Vedi esempi di seguito:

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)

Ho aggiunto una nota aggiuntiva alla tua risposta. Sentiti libero di eliminare se non ti piace la modifica.
— eipi10

Ho segnalato un problema su questo su GitHub per scoprire se si tratta di un bug o del comportamento previsto.
— eipi10

@ eipi10 leggermente più breve è l'uso di count:iris %>% count(Species, group2, .drop=FALSE)
— Tjebo

59

Il problema è ancora aperto, ma nel frattempo, soprattutto perché i tuoi dati sono già presi in considerazione, puoi utilizzare completeda "tidyr" per ottenere ciò che potresti cercare:

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

Se vuoi che il valore di sostituzione sia zero, devi specificarlo 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

11
Mi ci è voluto un sacco di colpi di testa contro il muro per capirlo, quindi lo menzionerò qui ... Se raggruppate per 2 variabili, e sono caratteri piuttosto che fattori, sarà necessario utilizzare ungroup()prima di completare. Se ti accorgi che completenon lo stai completando, ungroupprobabilmente è necessario.
— williamsurles

E se avessi ancora più variabili di raggruppamento? Ottengo un numero enorme di righe (molto più del mio dataframe originale) se utilizzo tutte le variabili di raggruppamento dal mio group_by
— TobiO

1
L'ho capito: devi usare l'annidamento :-) Quindi metti tutte le variabili che non dovrebbero essere combinate tra di loro in complete(variablewithdroppedlevels, nesting(var1,var2,var3))(in realtà è nell'aiuto perché completemi ci è voluto un po 'per capirlo
— TobiO

20

soluzione dplyr:

Per prima cosa crea df raggruppato

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

quindi riassumiamo quei livelli che si verificano contando con n()

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

quindi uniamo i nostri risultati in un data frame che contiene tutti i livelli di fattore:

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

infine, in questo caso, poiché stiamo esaminando i conteggi, i NAvalori vengono modificati in 0.

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

Questo può anche essere implementato funzionalmente, vedere le risposte: Aggiungere righe ai dati raggruppati con dplyr?

Un trucco:

Ho pensato di pubblicare un terribile hack che funzioni in questo caso per interesse. Dubito seriamente che dovresti mai farlo, ma mostra come group_by()genera gli attributi come se df$bfosse un vettore di caratteri non un fattore con i livelli. Inoltre, non pretendo di capirlo correttamente, ma spero che questo mi aiuti a imparare, questo è l'unico motivo per cui lo pubblico!

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

definire un valore "fuori limite" che non può esistere nel set di dati.

oob_val <- nrow(by_b)+1

modifica gli attributi in "trucco" summarise():

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

fare il riassunto:

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

index e sostituisci tutte le occorrenze di oob_val

res[res == oob_val] <- 0

che dà il previsto:

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

b count_a
1 1       6
2 2       6
3 3       0

11

questo non è esattamente ciò che è stato chiesto nella domanda, ma almeno per questo semplice esempio, potresti ottenere lo stesso risultato usando xtabs, ad esempio:

utilizzando dplyr:

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

o più breve:

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

risultato (uguale in entrambi i casi):

  b Freq
1 1    6
2 2    6
3 3    0
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.