如何避免处理大数据集时出现的循环缓慢?

3
考虑以下数据集:
> DATA <- data.frame(Agreement_number = c(1,1,1,1,2,2,2,2),
+                    country = c("Canada","Canada", "USA", "USA", "Canada","Canada", "USA", "USA"), 
+                    action = c("signature", "ratification","signature", "ratification", "signature", "ratification","signature", "ratification"), 
+                    signature_date = c(2000,NA,2000,NA, 2001, NA, 2002, NA),
+                    ratification_date = c(NA, 2001, NA, 2002, NA, 2001, NA, 2002))
> DATA
Agreement_number country       action signature_date ratification_date
              1  Canada    signature           2000                NA
             1  Canada ratification             NA              2001
             1     USA    signature           2000                NA
             1     USA ratification             NA              2002
             2  Canada    signature           2001                NA
             2  Canada ratification             NA              2001
             2     USA    signature           2002                NA
             2     USA ratification             NA              2002

正如您所看到的,一半的行信息是重复的。对于像这样小的数据集,很容易删除重复项。我可以使用coalesce函数(dplyr包),去除"action"列,然后删除所有不相关的行。当然,还有很多其他方法。最终结果应该像这样:

> DATA <- data.frame( Agreement_number = c(1,1,2,2),
+                     country = c("Canada", "USA", "Canada","USA"), 
+                     signature_date = c(2000,2000,2001,2002),
+                     ratification_date = c(2001, 2002, 2001, 2002))
> DATA
Agreement_number country signature_date ratification_date
             1  Canada           2000              2001
             1     USA           2000              2002
             2  Canada           2001              2001
             2     USA           2002              2002

问题在于我的真实数据集要大得多(102000 x 270),变量也更多。真实数据也更加不规则,缺失值也更多。使用coalesce函数速度非常慢。到目前为止,我能够做到的最好的循环仍然需要5-10分钟才能运行。
有没有更快的简单方法可以做到这一点?我觉得R中一定有一些函数可以进行这种操作,但我找不到任何信息。

欢迎来到 Stack Overflow!我对您的问题进行了一些编辑,以提高其清晰度,帮助您获得答案。祝你好运! - Jon
可能是 https://stackoverflow.com/questions/42567075/combining-pivoted-rows-in-r-by-common-value/42567254 的重复问题。 - thelatemail
谢谢大家的回答!实际上我早在我的代码中解决了这个问题,但是你们的回答给了我灵感。 - Benjamin Tremblay-Auger
3个回答

4

我认为你需要使用 dcast。在 data.table 库中的版本自称为“快速”,根据我的经验,它在大型数据集上速度很快。

首先,让我们创建一个列,该列可以是 signature_dateratification_date,具体取决于操作。

library(data.table)
setDT(DATA)[, date := ifelse(action == "ratification", ratification_date, signature_date)]

现在,让我们进行转换,使得操作成为列,值成为日期。
wide <- dcast(DATA, Agreement_number + country ~ action, value.var = 'date')

所以宽度看起来像这样

  Agreement_number country ratification signature
1                1  Canada         2001      2000
2                1     USA         2002      2000
3                2  Canada         2001      2001
4                2     USA         2002      2002

很遗憾,答案存在几个缺陷:(1)dcast() 可以从两个包 reshape2data.table 中获取。相关的 library() 调用缺失。(2)即使已经加载了 data.table,如果将一个 data.frame 传递给 dcast(),它仍然会调用较慢的 reshape2 版本。为了使用更快的版本,需要将 DATA 强制转换为 data.table 类型的对象。这段代码也缺失了。(3)额外列 DATA$date 的计算没有使用高效的 data.table 语法。(4)它没有处理任何额外的列。 - Uwe
谢谢您的建设性批评。对此,最好的解决方案是什么?我应该尝试编辑答案以解决这些缺陷,还是直接删除它,因为现在有更好的答案了? - HarlandMason
请不要删除您的答案。您是第一个使用dcast()方法的人,我的答案是参考它的。请尽可能改进您的答案。 - Uwe
1
学习 data.table,我建议阅读 https://github.com/Rdatatable/data.table/wiki/Getting-started 上的文档和常见问题解答,以及 Frank 的 Quick R Tutorial 的第三章 http://franknarf1.github.io/r-tutorial/_book/。 - Uwe

3

OP提到他的生产数据有100k行x270列,速度是他关心的问题。因此,我建议使用data.table

我知道Harland也建议使用data.tabledcast(),但下面的解决方案是一种不同的方法。它按正确的顺序排列行,并将ratification_date复制到签名行。经过一些清理后,我们得到了所需的结果。

library(data.table)

# coerce to data.table,
# make sure that the actions are ordered properly, not alphabetically
setDT(DATA)[, action := ordered(action, levels = c("signature", "ratification"))]

# order the rows to make sure that signature row and ratification row are
# subsequent for each agreement and country
setorder(DATA, Agreement_number, country, action)

# copy the ratification date from the row below but only within each group
result <- DATA[, ratification_date := shift(ratification_date, type = "lead"), 
                by = c("Agreement_number", "country")][
                  # keep only signature rows, remove action column
                  action == "signature"][, action := NULL]
result
   Agreement_number country signature_date ratification_date dummy1 dummy2
1:                1  Canada           2000              2001      2      D
2:                1     USA           2000              2002      3      A
3:                2  Canada           2001              2001      1      B
4:                2     USA           2002              2002      4      C

数据

楼主提到他的生产数据有270列。为了模拟这个,我添加了两个虚拟列:

set.seed(123L)
DATA <- data.frame(Agreement_number = c(1,1,1,1,2,2,2,2),
country = c("Canada","Canada", "USA", "USA", "Canada","Canada", "USA", "USA"), 
action = c("signature", "ratification","signature", "ratification", "signature", "ratification","signature", "ratification"), 
signature_date = c(2000,NA,2000,NA, 2001, NA, 2002, NA),
ratification_date = c(NA, 2001, NA, 2002, NA, 2001, NA, 2002),
dummy1 = rep(sample(4), each = 2L),
dummy2 = rep(sample(LETTERS[1:4]), each = 2L))

请注意,在进行抽样时,set.seed() 用于产生可重复的结果。

  Agreement_number country       action signature_date ratification_date dummy1 dummy2
1                1  Canada    signature           2000                NA      2      D
2                1  Canada ratification             NA              2001      2      D
3                1     USA    signature           2000                NA      3      A
4                1     USA ratification             NA              2002      3      A
5                2  Canada    signature           2001                NA      1      B
6                2  Canada ratification             NA              2001      1      B
7                2     USA    signature           2002                NA      4      C
8                2     USA ratification             NA              2002      4      C

附录: dcast() 带有额外列

Harland 建议使用 data.tabledcast()。除了他回答中的几个其他缺陷之外,它不能处理 OP 提到的额外列。

下面的 dcast() 方法也将返回额外的列:

library(data.table)

# coerce to data table
setDT(DATA)[, action := ordered(action, levels = c("signature", "ratification"))]

# use already existing column to "coalesce" dates
DATA[action == "ratification", signature_date := ratification_date]
DATA[, ratification_date := NULL]

# dcast from long to wide form, note that ... refers to all other columns
result <- dcast(DATA, Agreement_number + country + ... ~ action, 
                value.var = "signature_date")
result
   Agreement_number country dummy1 dummy2 signature ratification
1:                1  Canada      2      D      2000         2001
2:                1     USA      3      A      2000         2002
3:                2  Canada      1      B      2001         2001
4:                2     USA      4      C      2002         2002
请注意,这种方法将更改列的顺序。

2

这里是另一个使用uwe-block的data.frame的data.table解决方案。它类似于uwe-block的方法,但使用max来合并数据。

# covert data.frame to data.table and factor variables to character variables
library(data.table)
setDT(DATA)[, names(DATA) := lapply(.SD,
                                    function(x) if(is.factor(x)) as.character(x) else x)]

# collapse data set, by agreement and country. Take max of remaining variables.
 DATA[, lapply(.SD, max, na.rm=TRUE), by=.(Agreement_number, country)][,action := NULL][]
lapply函数对不包含在by语句中的变量进行操作,计算去除NA值后的最大值。链条中的下一个链接删除了不需要的动作变量,最后一个(不必要的)链接打印输出结果。

这将返回:

   Agreement_number country signature_date ratification_date dummy1 dummy2
1:                1  Canada           2000              2001      2      D
2:                1     USA           2000              2002      3      A
3:                2  Canada           2001              2001      1      B
4:                2     USA           2002              2002      4      C

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