在 R 中优化类似 sumif 的查询

Optimizing a sumif-like query in R

我有一个包含 180 万行的数据框,每行包含一个或多个从 30,000 个标签中选择的标签。我试图找出有多少行包含每个标签的实例。有些项目包含多达 25 个标签,每个标签都在其自己的列中。没有一行包含任何标记的一个以上实例:

ItemNo <- c(1, 2, 3, 4)  
Tag1 <- c("ZZZ", "AAA", "BBB", "YYY")  
Tag2 <- c("YYY2", "ZZZ", "AAA", "XXX")  
Tag3 <- c("", "YYY2", "AAA2", "XXX3")  
Tag4 <- c("", "", "", "AAA")  
Tag5 <- c("", "", "", "ZZZ")  
Tag6 <- c("", "", "", "YYY2")  
Items <- data.frame(ItemNo, Tag1, Tag2, Tag3, Tag4, Tag5, Tag6)  
Items

  ItemNo Tag1 Tag2 Tag3 Tag4 Tag5 Tag6
1      1  ZZZ YYY2       
2      2  AAA  ZZZ YYY2   
3      3  BBB  AAA AAA2   
4      4  YYY  XXX XXX3  AAA  ZZZ YYY2

所有标签都在一个单独的数据集中:

Code <- c("AAA", "BBB", "XXX", "ZZZ", "AAAA", "XXX3", "YYY2")  
COUNT <- c(0, 0, 0, 0, 0, 0, 0)  
tags <- data.frame(Code, COUNT) 
tags 

  Code COUNT  
1  AAA     0  
2  BBB     0  
3  XXX     0  
4  ZZZ     0  
5 AAAA     0  
6 XXX3     0  
7 YYY2     0  

我想以这样的方式结束:

   Code COUNT  
1  AAA     3  
2  BBB     1  
3  XXX     1  
4  ZZZ     3  
5 AAAA     0  
6 XXX3     1  
7 YYY2     3   

我可以使用 for 循环获得良好的结果,这需要大约 3 个小时才能 运行 通过数据集:

for (i in 1:nrow(tags)) {tags[i,2] <- sum(Items[,2:7] ==
as.character(tags[i,1]), na.rm = TRUE)}

是否有更有效或更优雅的方法来计算此数据集中每个标签的实例?

我无法测试您的大型数据集的速度,但我猜这会更快,因为它使用 apply 而不是 for 循环:

Sums <- data.frame(Code = Code,
                   COUNT = unlist(lapply(Code, function(i)
                             sum(apply(Items, 1, function(x) any(i %in% x))))))

COUNT = 线的中心算起,这种方法:

  • 沿行使用 apply 来获得一个逻辑值,指示可能代码向量中的给定项目是否出现在该行中。
  • 将对 apply 的调用包装在对 lapply 的调用中,该调用将该过程应用于代码向量 (Codes) 中的每个项目并对结果求和。
  • 取消列出结果列表,使其成为向量。
  • 将该向量放在数据框中,旁边是显示相关代码的列。

结果:

> Sums
  Code COUNT
1  AAA     3
2  BBB     1
3  XXX     1
4  ZZZ     3
5 AAAA     0
6 XXX3     1
7 YYY2     3

让我们举一个接近您规模的例子:

n = 1e6
ncol = 25
ItemNo <- 1:n
tags = c("", do.call(paste0, expand.grid(LETTERS, LETTERS, LETTERS, stringsAsFactors = FALSE)))
item_tags = sample(tags, size = n * ncol, replace = T)

Items <- cbind.data.frame(ItemNo, matrix(item_tags, ncol = ncol)) 

上面有 25 个标签列,100 万行,有 26^3 + 1 = 17577 个唯一标签。

system.time(table(unlist(Items[-1])))
#   user  system elapsed 
# 15.077   1.001  16.277 

运行 table 除了第一列,未列出的所有内容,在我的笔记本电脑上大约需要 15 秒。输出应该可用:

tt = table(unlist(Items[-1]))
head(tt)
#       AAA  AAB  AAC  AAD  AAE 
# 1421 1451 1456 1479 1440 1449 

转换为数据框以匹配您想要的输出:

dd = data.frame(tt)
head(dd)
#   Var1 Freq
# 1      1421
# 2  AAA 1451
# 3  AAB 1456
# 4  AAC 1479
# 5  AAD 1440
# 6  AAE 1449

请注意,它也会计算空白 - 您可能希望将那些子集化 post-hoc.


编辑: 要获得更快的速度,您可以使用 tabulate 而不是 tabletable 有我们在这种情况下不需要的选项 - tabulate 是精简版。使用 tabulate 而不是 table 在我的计算机上运行不到 10 秒。