dplyr fassen zusammen: Entspricht ".drop = FALSE", um Gruppen mit einer Länge von Null in der Ausgabe beizubehalten

97

Bei Verwendung summarisemit plyrder ddplyFunktion 'werden leere Kategorien standardmäßig gelöscht. Sie können dieses Verhalten durch Hinzufügen ändern .drop = FALSE. Dies funktioniert jedoch nicht bei Verwendung summarisemit dplyr. Gibt es eine andere Möglichkeit, leere Kategorien im Ergebnis beizubehalten?

Hier ist ein Beispiel mit gefälschten Daten.

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

Nicht genau das, was ich mir erhofft hatte. Gibt es eine dplyrMethode, um das gleiche Ergebnis wie .drop=FALSEin zu erzielen plyr?

eipi10
quelle

Antworten:

26

Da dplyr 0.8 group_by das .dropArgument gewonnen hat, das genau das tut, wonach Sie gefragt haben:

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

Ein zusätzlicher Hinweis zur Antwort von @ Moody_Mudskipper: Die Verwendung von .drop=FALSEkann zu möglicherweise unerwarteten Ergebnissen führen, wenn eine oder mehrere Gruppierungsvariablen nicht als Faktoren codiert sind. Siehe Beispiele unten:

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
quelle
Ich habe Ihrer Antwort einen zusätzlichen Hinweis hinzugefügt. Bitte zögern Sie nicht zu löschen, wenn Sie die Bearbeitung nicht mögen.
eipi10
Ich habe auf github ein Problem dazu eingereicht, um herauszufinden, ob dies ein Fehler oder das beabsichtigte Verhalten ist.
eipi10
@ eipi10 etwas kürzer ist die Verwendung von count:iris %>% count(Species, group2, .drop=FALSE)
Tjebo
59

Das Problem ist noch offen, aber in der Zwischenzeit, insbesondere da Ihre Daten bereits berücksichtigt sind, können Sie mit complete"tidyr" ermitteln, wonach Sie suchen:

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

Wenn Sie möchten, dass der Ersatzwert Null ist, müssen Sie Folgendes angeben mit 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
quelle
11
Ich habe viel Kopf gegen die Wand geschlagen, um das herauszufinden, also werde ich es hier erwähnen ... Wenn Sie nach 2 Variablen gruppieren und es sich eher um Zeichen als um Faktoren handelt, müssen Sie diese verwenden, ungroup()bevor Sie fertig sind. Wenn Sie jemals bemerken, dass Sie completenicht wirklich fertig sind, ungroupwird dies wahrscheinlich benötigt.
Williamsurles
Was ist, wenn Sie noch mehr Gruppierungsvariablen haben? Ich erhalte eine große Anzahl von Zeilen (viel mehr als mein ursprünglicher Datenrahmen), wenn ich alle Gruppierungsvariablen aus meiner group_by
TobiO
1
Ich habe es herausgefunden: Du musst Verschachtelung verwenden :-) Also lege alle Variablen, die nicht auch untereinander kombiniert werden sollten, in complete(variablewithdroppedlevels, nesting(var1,var2,var3))(es ist eigentlich in der Hilfe, dass completeich noch eine Weile
gebraucht habe, um
20

dplyr Lösung:

Machen Sie zuerst gruppierte df

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

dann fassen wir die Ebenen zusammen, die durch Zählen mit auftreten n()

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

Dann führen wir unsere Ergebnisse in einem Datenrahmen zusammen, der alle Faktorstufen enthält:

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

Schließlich werden in diesem Fall die NAWerte auf 0 geändert, da wir uns die Anzahl ansehen.

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

Dies kann auch funktional implementiert werden, siehe Antworten: Hinzufügen von Zeilen zu gruppierten Daten mit dplyr?

Ein Hack:

Ich dachte, ich würde einen schrecklichen Hack posten , der in diesem Fall aus Interesse funktioniert. Ich bezweifle ernsthaft, dass Sie dies jemals tun sollten, aber es zeigt, wie group_by()die Attribute erzeugt werden, als ob df$bein Zeichenvektor kein Faktor mit Ebenen wäre. Ich gebe auch nicht vor, dies richtig zu verstehen - aber ich hoffe, das hilft mir beim Lernen - dies ist der einzige Grund, warum ich es poste!

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

Definieren Sie einen "Out-of-Bound" -Wert, der im Dataset nicht vorhanden sein kann.

oob_val <- nrow(by_b)+1

Ändern Sie die Attribute in "Trick" summarise():

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

Machen Sie die Zusammenfassung:

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

indizieren und ersetzen Sie alle Vorkommen von oob_val

res[res == oob_val] <- 0

was gibt das beabsichtigte:

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

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

Dies ist nicht genau das, was in der Frage gestellt wurde, aber zumindest für dieses einfache Beispiel könnten Sie das gleiche Ergebnis mit xtabs erzielen, zum Beispiel:

mit dplyr:

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

oder kürzer:

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

Ergebnis (in beiden Fällen gleich):

  b Freq
1 1    6
2 2    6
3 3    0
Talat
quelle