Dplyr Summary: 等效于“ . drop = FALSE”,以保持输出中的组长度为零

当使用 summariseplyrddply函数时,默认情况下会删除空类别。可以通过添加 .drop = FALSE来更改此行为。但是,当使用 summarisedplyr时,这不起作用。还有其他方法在结果中保留空类别吗?

这里有一个伪造数据的例子。

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方法可以达到与 plyr中的 .drop=FALSE相同的结果?

52068 次浏览

this is not exactly what was asked in the question, but at least for this simple example, you could get the same result using xtabs, for example:

使用 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

dplyr solution:

First make grouped df

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

then we summarise those levels that occur by counting with 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 为分组数据添加行?

黑客:

I thought I would post a 糟透了 hack that works in this case for interest's sake. I seriously doubt you should ever actually do this but it shows how group_by() generates the atrributes as if df$b was a character vector not a factor with levels. Also, I don't pretend to understand this properly -- but I am hoping this helps me learn -- this is the only reason I'm posting it!

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

这个问题仍然没有解决,但与此同时,特别是因为您的数据已经被分解,您可以使用“ tidyr”中的 complete来获得您可能需要的内容:

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

因为 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

One additional note to go with @Moody_Mudskipper's answer: Using .drop=FALSE can give potentially unexpected results when one or more grouping variables are not coded as factors. See examples below:

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)