Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/r/75.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在单个R数据表中按组高效地进行locf_R_Dataframe_Data.table_Dplyr_Rcpp - Fatal编程技术网

在单个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倍的改进