在单个R数据表中按组高效地进行locf
我有一个大的、宽的在单个R数据表中按组高效地进行locf,r,dataframe,data.table,dplyr,rcpp,R,Dataframe,Data.table,Dplyr,Rcpp,我有一个大的、宽的数据。表(2000万行)由个人ID键入,但有很多列(~150)有很多空值。每一列都是我希望为每个人结转的记录状态/属性。每个人可能有10到10000次观察,其中大约有500000人。一个人的值不能“出血”到下一个人,因此我的解决方案必须适当地尊重person ID列和组 出于演示目的-以下是一个非常小的示例输入: DT = data.table( id=c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3), aa=c("A", NA, "B", "
数据。表
(2000万行)由个人ID键入,但有很多列(~150)有很多空值。每一列都是我希望为每个人结转的记录状态/属性。每个人可能有10到10000次观察,其中大约有500000人。一个人的值不能“出血”到下一个人,因此我的解决方案必须适当地尊重person ID列和组
出于演示目的-以下是一个非常小的示例输入:
DT = data.table(
id=c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3),
aa=c("A", NA, "B", "C", NA, NA, "D", "E", "F", NA, NA, NA),
bb=c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA),
cc=c(1, NA, NA, NA, NA, 4, NA, 5, 6, NA, 7, NA)
)
看起来是这样的:
id aa bb cc
1: 1 A NA 1
2: 1 NA NA NA
3: 1 B NA NA
4: 1 C NA NA
5: 2 NA NA NA
6: 2 NA NA 4
7: 2 D NA NA
8: 2 E NA 5
9: 3 F NA 6
10: 3 NA NA NA
11: 3 NA NA 7
12: 3 NA NA NA
id aa bb cc
1: 1 A NA 1
2: 1 A NA 1
3: 1 B NA 1
4: 1 C NA 1
5: 2 NA NA NA
6: 2 NA NA 4
7: 2 D NA 4
8: 2 E NA 5
9: 3 F NA 6
10: 3 F NA 6
11: 3 F NA 7
12: 3 F NA 7
我的预期输出如下所示:
id aa bb cc
1: 1 A NA 1
2: 1 NA NA NA
3: 1 B NA NA
4: 1 C NA NA
5: 2 NA NA NA
6: 2 NA NA 4
7: 2 D NA NA
8: 2 E NA 5
9: 3 F NA 6
10: 3 NA NA NA
11: 3 NA NA 7
12: 3 NA NA NA
id aa bb cc
1: 1 A NA 1
2: 1 A NA 1
3: 1 B NA 1
4: 1 C NA 1
5: 2 NA NA NA
6: 2 NA NA 4
7: 2 D NA 4
8: 2 E NA 5
9: 3 F NA 6
10: 3 F NA 6
11: 3 F NA 7
12: 3 F NA 7
我找到了一个data.table
解决方案,它可以正常工作,但在我的大型数据集上速度非常慢:
DT[, na.locf(.SD, na.rm=FALSE), by=id]
我发现使用dplyr的等效解决方案同样慢
GRP = DT %>% group_by(id)
data.table(GRP %>% mutate_each(funs(blah=na.locf(., na.rm=FALSE))))
我希望我可以使用data.table
功能来实现滚动的“self”连接,但我似乎做得不对(我怀疑我需要使用.N
,但我还没有弄清楚)
在这一点上,我想我必须在Rcpp中写一些东西来有效地应用分组locf
我是R的新手,但我对C++不陌生,所以我有信心我能做到。我只是觉得在R中应该有一种使用
数据的有效方法。table
一个非常简单的na.locf
可以通过转发(cummax
)非na
索引((!is.na(x))*沿着(x)
)和相应的子集来构建:
x = c(1, NA, NA, 6, 4, 5, 4, NA, NA, 2)
x[cummax((!is.na(x)) * seq_along(x))]
# [1] 1 1 1 6 4 5 4 4 4 2
这将使用na.rm=TRUE
参数复制na.locf
,要获得na.rm=FALSE
行为,我们只需确保cummax
中的第一个元素是TRUE
:
x = c(NA, NA, 1, NA, 2)
x[cummax(c(TRUE, tail((!is.na(x)) * seq_along(x), -1)))]
#[1] NA NA 1 1 2
在这种情况下,我们不仅需要考虑非NA
索引,还需要考虑(已排序或待排序)“id”列更改值时的索引:
id = c(10, 10, 11, 11, 11, 12, 12, 12, 13, 13)
c(TRUE, id[-1] != id[-length(id)])
# [1] TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE
结合以上内容:
id = c(10, 10, 11, 11, 11, 12, 12, 12, 13, 13)
x = c(1, NA, NA, 6, 4, 5, 4, NA, NA, 2)
x[cummax(((!is.na(x)) | c(TRUE, id[-1] != id[-length(id)])) * seq_along(x))]
# [1] 1 1 NA 6 4 5 4 4 NA 2
注意,这里我们使用或第一个元素TRUE
,即使其等于TRUE
,从而得到na.rm=FALSE
行为
对于这个例子:
id_change = DT[, c(TRUE, id[-1] != id[-.N])]
DT[, lapply(.SD, function(x) x[cummax(((!is.na(x)) | id_change) * .I)])]
# id aa bb cc
# 1: 1 A NA 1
# 2: 1 A NA 1
# 3: 1 B NA 1
# 4: 1 C NA 1
# 5: 2 NA NA NA
# 6: 2 NA NA 4
# 7: 2 D NA 4
# 8: 2 E NA 5
# 9: 3 F NA 6
#10: 3 F NA 6
#11: 3 F NA 7
#12: 3 F NA 7
我很确定DT[,lapply(.SD,na.locf,F),by=id]
会更快。实际上,我从那开始,发现性能更差。滚动自连接看起来很重要,我记得一些问题既有na.locf
也有滚动连接的答案,因此,我认为您可以在当前的so知识库中找到答案。对于有序的“id”,您可能可以使用如下内容:tmp=c(TRUE,DT$id[-1]!=DT$id[-nrow(DT)];DT[,lappy(.SD,函数(x)x[cummax((!is.na(x))| tmp)*seq_len(nrow(DT))])]
?@alexis_laz-哇!太棒了!它可以工作,比data.table解决方案快2个数量级。你能帮我理解代码在做什么吗?另外,你的评论应该被做成一个答案,这样我就可以标记这个问题已经解决了。我对否决票并不太清楚,一些解释对我来说是很有帮助的。很好的答案在我看来——这不仅是一个比普通的na.locf
快得多的版本,而且它还添加了一个修改,以对每个组进行修改(假设已排序的组),没有实际执行by
循环(这将为每个组引入额外的eval
,并会降低速度)。除非我遗漏了什么-这应该是标准的na.locf
实现,而不是rle
那些zoo
做的东西。@eddi:谢谢你的编辑。我想zoo::na.locf
更灵活,不过我相信对于简单的情况,cummax
版本的4-5*长度(x)
扫描应该非常简单。而且,事实证明,在函数中传递每个列指针一次,并且实际上由“by”组应用是非常方便的。我可以补充一点,在我最初的测试集中,有2000万行,第一个建议的lappy
解决方案需要40个小时才能完成。新代码只需4分钟!我怀疑Rcpp能做得更好。@carl.anderson我做了一个快速的回溯测试,你会很容易地得到2-3倍的改进