tidyr/dplyr中用于添加零计数行的适当习语

51

假设我有一些计数数据,看起来像这样:

library(tidyr)
library(dplyr)

X.raw <- data.frame(
  x = as.factor(c("A", "A", "A", "B", "B", "B")),
  y = as.factor(c("i", "ii", "ii", "i", "i", "i")),
  z = 1:6
)
X.raw
#   x  y z
# 1 A  i 1
# 2 A ii 2
# 3 A ii 3
# 4 B  i 4
# 5 B  i 5
# 6 B  i 6

我希望把内容整理并概括如下:

X.tidy <- X.raw %>% group_by(x, y) %>% summarise(count = sum(z))
X.tidy
# Source: local data frame [3 x 3]
# Groups: x
#
#   x  y count
# 1 A  i     1
# 2 A ii     5
# 3 B  i    15

我知道当x=="B"y=="ii"时,我们观察到计数为零,而不是缺失值。也就是说,野外工作者实际上是在那里的,但由于没有正数计数,因此没有将任何行输入原始数据中。我可以通过执行以下操作来明确添加零计数:

X.fill <- X.tidy %>% spread(y, count, fill = 0) %>% gather(y, count, -x)
X.fill
# Source: local data frame [4 x 3]
# 
#   x  y count
# 1 A  i     1
# 2 B  i    15
# 3 A ii     5
# 4 B ii     0

但这似乎有点绕弯子。有没有更清晰的方法来实现这个?

仅澄清一下:我的代码已经通过使用 spread 然后 gather 做到了我需要做的事情,所以我感兴趣的是在 tidyrdplyr 内寻找更直接的路线。


如果您知道“B” / “ii”的观察计数为零,为什么不在源数据集中呢?您的源数据集目前将该组合表示为缺失。 - A5C1D2H2I1M1N2O1R2T1
2
原始数据集仅包括正数计数。但我们知道所有组合都被调查过了。 - pete
1
你们有另一张表格展示哪些“x”和“y”的组合是有效的吗?否则,你们打算如何区分0NA - A5C1D2H2I1M1N2O1R2T1
没有任何的 NA - pete
6个回答

44

dplyr 0.8版本开始,你可以通过在group_by中设置参数.drop = FALSE来实现:

X.tidy <- X.raw %>% group_by(x, y, .drop = FALSE) %>% summarise(count=sum(z))
X.tidy
# # A tibble: 4 x 3
# # Groups:   x [2]
#   x     y     count
#   <fct> <fct> <int>
# 1 A     i         1
# 2 A     ii        5
# 3 B     i        15
# 4 B     ii        0

这将保留由因子列的所有级别组成的分组,因此如果您有字符列,则可能希望将它们转换(感谢Pake的说明)。


8
如果你到这里了但仍然无法运行:请确保你在 group_by() 中使用的是因子变量而不是字符列。 - Pake
1
@Pake非常感谢您的评论!我一直在疯狂地尝试弄清楚为什么这不起作用。 - Taren Sanders

33

tidyr 中的 complete 函数就是为了应对这种情况而设计的。

根据文档:

这是一个包装器,包装了 expand()left_join()replace_na,对于完成数据的缺失组合非常有用。

你可以有两种方式使用它。首先,在汇总之前,你可以在原始数据集上使用它,"补全"所有 xy 的组合,并用 0 填充 z(你可以使用默认的 NA fill 并在 sum 中使用 na.rm = TRUE)。

X.raw %>% 
    complete(x, y, fill = list(z = 0)) %>% 
    group_by(x,y) %>% 
    summarise(count = sum(z))

Source: local data frame [4 x 3]
Groups: x [?]

       x      y count
  <fctr> <fctr> <dbl>
1      A      i     1
2      A     ii     5
3      B      i    15
4      B     ii     0

您还可以在预汇总的数据集上使用complete。请注意,complete会尊重分组。X.tidy已经被分组,因此您可以将其ungroup并通过xy完成数据集,或者只列出每个组中要完成的变量-在本例中,是y

# Complete after ungrouping
X.tidy %>% 
    ungroup %>%
    complete(x, y, fill = list(count = 0))

# Complete within grouping
X.tidy %>% 
    complete(y, fill = list(count = 0))

每个选项的结果都是相同的:

Source: local data frame [4 x 3]

       x      y count
  <fctr> <fctr> <dbl>
1      A      i     1
2      A     ii     5
3      B      i    15
4      B     ii     0

4
您可以使用tidyr的expand函数来生成因子级别的所有组合,然后再使用left_join函数:
X.tidy %>% expand(x, y) %>% left_join(X.tidy)

# Joining by: c("x", "y")
# Source: local data frame [4 x 3]
# 
#   x  y count
# 1 A  i     1
# 2 A ii     5
# 3 B  i    15
# 4 B ii    NA

那么你可以将值保留为NA或者用0或任何其他值替换它们。

这种方式并不是完全解决问题的方法,但比使用spread & gather更快速,更友好于RAM。


2
此外,请查看 tidyr 开发版中的 complete(0.2.0.9000),它是 expandleft_joinreplace_na 的便捷包装器。 - aosmith
谢谢 @aosmith,这正是我所需要的。如果您将其写成答案,我会接受它。 - pete

3

plyr具备您需要的功能,但是dplyr目前还没有,所以您需要一些额外的代码来包含零计数组,如@momeara所示。还可以参见此问题。在plyr::ddply中,您只需添加.drop=FALSE即可保留最终结果中的零计数组。例如:

library(plyr)

X.tidy = ddply(X.raw, .(x,y), summarise, count=sum(z), .drop=FALSE)

X.tidy
  x  y count
1 A  i     1
2 A ii     5
3 B  i    15
4 B ii     0

你的意思是第二行应该使用ddply而不是dplyr吗? - momeara
是的。谢谢你发现了这个问题!我已经修复了它。 - eipi10

2

您可以明确地创建所有可能的组合,然后将其与整洁的摘要连接起来:

x.fill <- expand.grid(x=unique(x.tidy$x), x=unique(x.tidy$y)) %>%
    left_join(x.tidy, by=("x", "y")) %>%
    mutate(count = ifelse(is.na(count), 0, count)) # replace null values with 0's

0

您还可以使用data.table包及其Cross Join CJ()函数来实现。

require(data.table)

X = data.table(X.raw)[
  CJ(y = y,
     x = x,
     unique = TRUE), 
  on = .(x, y)
  ][ , .(z = sum(z)), .(x, y) ][ order(x, y) ]
X

# filling the NAs with 0s
setnafill(X, fill = 0, cols = 'z')
X
#    x  y  z
# 1: A  i  1
# 2: A ii  5
# 3: B  i 15
# 4: B ii  0

虽然一开始没有要求,但为了完整性和链接到相关的data.table问题,我在这里添加了一个data.table解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接