Dplyr суммировать: Эквивалент ".drop = FALSE" для сохранения групп с нулевой длиной в выходе
При использовании summarise
с функцией plyr
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
?
Ответы
Ответ 1
С тех пор как 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)
Ответ 2
Проблема все еще открыта, но тем временем, тем более, что ваши данные уже учтены, вы можете использовать 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
Ответ 3
Решение 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
Ответ 4
это не совсем то, что было задано в вопросе, но, по крайней мере, для этого простого примера вы можете получить тот же результат, используя 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