在 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
而不是 table
。 table
有我们在这种情况下不需要的选项 - tabulate
是精简版。使用 tabulate
而不是 table
在我的计算机上运行不到 10 秒。
我有一个包含 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
而不是 table
。 table
有我们在这种情况下不需要的选项 - tabulate
是精简版。使用 tabulate
而不是 table
在我的计算机上运行不到 10 秒。