dplyr резюме: еквівалент “.drop = FALSE” для збереження груп з нульовою довжиною на виході


97

При використанні summariseз plyr«S ddplyфункції, порожні категорії видаляються за замовчуванням. Ви можете змінити цю поведінку, додавши .drop = FALSE. Однак це не працює при використанні summariseз dplyr. Чи є інший спосіб зберегти порожні категорії в результаті?

Ось приклад з підробленими даними.

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

Не зовсім те, на що я сподівався. Чи існує dplyrметод досягнення того ж результату, що і .drop=FALSEв plyr?


Відповіді:


26

Оскільки dplyr 0.8 group_by отримав .dropаргумент, який робить саме те, про що ви просили:

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

Ще одне додаткове зауваження, на яке слід відповісти @ Moody_Mudskipper: відповідь на використання .drop=FALSEможе дати потенційно несподівані результати, коли одна або кілька змінних групування не кодуються як фактори. Див. Приклади нижче:

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)

Я додав додаткову примітку до вашої відповіді. Будь ласка, сміливо видаляйте, якщо вам не подобається редагування.
eipi10,

Я подав питання про це на github, щоб з’ясувати, чи це помилка, чи передбачувана поведінка.
eipi10,

@ eipi10 дещо коротше використання count:iris %>% count(Species, group2, .drop=FALSE)
Tjebo

59

Питання все ще відкрите, але тим часом, тим більше, що ваші дані вже враховані, ви можете скористатися completeз "tidyr", щоб отримати те, що ви могли б шукати:

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

Якщо ви хотіли, щоб значення заміщення було рівним нулю, вам потрібно вказати це за допомогою 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
Мені знадобилося багато ударів головою об стіну, щоб це зрозуміти, тому я згадаю це тут ... Якщо ви згрупуєте по 2 змінних, і це символи, а не фактори, вам доведеться використовувати, ungroup()перш ніж закінчити. Якщо ви коли-небудь помітили, що completeнасправді не ungroupвиконуєте, можливо , це потрібно.
williamsurles

Що робити, якщо у вас є ще більше змінних, що групують? Я отримую величезну кількість рядків (набагато більше, ніж мій оригінальний фрейм даних), якщо використовую всі варіації групування з мого group_by
TobiO,

1
Я розібрався: Вам потрібно скористатися вкладанням :-) Тож помістіть усі Змінні, які також не слід поєднувати між собою complete(variablewithdroppedlevels, nesting(var1,var2,var3))(насправді в допомозі completeмені все-таки знадобився час
TobiO

20

розчин dplyr:

Спочатку зробіть згрупований df

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

тоді ми підсумовуємо ті рівні, які відбуваються шляхом підрахунку за n()

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

тоді ми об’єднуємо наші результати у фрейм даних, який містить усі рівні факторів:

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

нарешті, у цьому випадку, оскільки ми розглядаємо підрахунки, NAзначення змінюються на 0.

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

Це також можна реалізувати функціонально, див. Відповіді: Додавання рядків до згрупованих даних за допомогою dplyr?

Рубати:

Я думав, що опублікую жахливий хак, який працює у цьому випадку заради інтересу. Я серйозно сумніваюся, що ви коли-небудь повинні це робити, але це показує, як group_by()генеруються атрибути, як ніби df$bвектор символів не фактор з рівнями. Крім того, я не прикидаюся, що розумію це належним чином - але я сподіваюся, це допоможе мені навчитися - це єдина причина, я публікую це!

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

визначити значення "поза межами", яке не може існувати в наборі даних.

oob_val <- nrow(by_b)+1

змінити атрибути на "фокус" summarise():

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

зробити резюме:

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

індексувати і замінювати всі випадки oob_val

res[res == oob_val] <- 0

який дає передбачуване:

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

b count_a
1 1       6
2 2       6
3 3       0

11

це не зовсім те, що було задано у питанні, але принаймні для цього простого прикладу ви можете отримати той самий результат, використовуючи xtabs, наприклад:

за допомогою dplyr:

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

або коротше:

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

результат (рівний в обох випадках):

  b Freq
1 1    6
2 2    6
3 3    0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.