我是R的新手,这是我关于stackoverflow的第一个问题。
我正在尝试
示例数据:
id code date_down date_up
1: 1 p 2019-01-01 2019-01-02
2: 1 f 2019-01-02 2019-01-03
3: 2 f 2019-01-02 2019-01-02
4: 2 p 2019-01-03 <NA>
5: 3 p 2019-01-04 <NA>
6: 4 <NA> 2019-01-05 2019-01-05
7: 5 f 2019-01-07 2019-01-08
8: 5 p 2019-01-07 2019-01-08
9: 5 p 2019-01-09 2019-01-09
10: 6 f 2019-01-10 2019-01-10
11: 6 p 2019-01-10 2019-01-10
12: 6 p 2019-01-10 2019-01-11
我想做的是
id
的子集(组)date_up
,code = 'p'
和date-up
(在找到的行中)大于date-down
。我的预期结果应该是:
id code date_down date_up founddate
1: 1 p 2019-01-01 2019-01-02 <NA>
2: 1 f 2019-01-02 2019-01-03 <NA>
3: 2 f 2019-01-02 2019-01-02 <NA>
4: 2 p 2019-01-03 <NA> <NA>
5: 3 p 2019-01-04 <NA> <NA>
6: 4 <NA> 2019-01-05 2019-01-05 <NA>
7: 5 f 2019-01-07 2019-01-08 2019-01-08
8: 5 p 2019-01-07 2019-01-08 2019-01-09
9: 5 p 2019-01-09 2019-01-09 <NA>
10: 6 f 2019-01-10 2019-01-10 2019-01-11
11: 6 p 2019-01-10 2019-01-10 2019-01-11
12: 6 p 2019-01-10 2019-01-11 <NA>
我尝试了多种变体,分别使用.SD
,.N
,使用
DT[, idcount:= seq_leg(.N),by=id]
,但实际上并没有实现。任何帮助表示赞赏。
对data.table的任何良好引用:)非常感谢
编辑:
我已经编辑了提供的原始数据,以给出一个更微妙的示例,其中用第12行的数据更新第10行,因为第12行在id子集中,并且符合限定条件。第11行不符合资格标准,因此该数据不用于更新第10行。还包括我第一次使用dput
!
示例数据为dput
代码:
dt <- structure(list(
id = c(1L, 1L, 2L, 2L, 3L, 4L, 5L, 5L, 5L, 6L, 6L, 6L),
code = c("p", "f", "f", "p", "p", "<NA>", "f", "p", "p", "f", "p", "p"),
date_down = structure(c(17897, 17898, 17898, 17899, 17900, 17901, 17903, 17903, 17905, 17906, 17906, 17906), class = "Date"),
date_up = structure(c(17898, 17899, 17898, NA, NA, 17901, 17904, 17904, 17905, 17906, 17906, 17907), class = "Date")),
class = c("data.table", "data.frame"),
row.names = c(NA, -12L))
setDT(dt) # to reinit the internal self ref pointer (known issue)
答案 0 :(得分:12)
下面,我显示5个可行的data.table
解决方案,这些解决方案是
针对OP的实际数据集(140万条记录)进行性能测试。
所有5个解决方案都使用“非等式”联接(使用不等式进行比较
on
子句中的行)。
每个解决方案只是一个小的渐进式代码更改,因此应该
易于遵循以比较不同的data.table
选项和语法选择。
为此,我使用data.table
语法来解决OP的问题,分为以下步骤:
# Add row numbers to all records in dt (only because you
# have criteria based on comparing sequential rows):
dt[, row := .I]
# Compute result columns ( then standard assignment into dt using <- )
dt$found_date <-
dt[code=='p'][dt, # join dt to the data.table matching your criteria, in this case dt[code=='p']
.( x.date_up ), # columns to select, x. prefix means columns from dt[code=='p']
on = .(id==id, row > row, date_up > date_down), # join criteria: dt[code=='p'] fields on LHS, main dt fields on RHS
mult = "first"] # get only the first match if multiple matches
请注意上面的联接表达式:
i
是您的主要目标。这样,您将从主data.table中获取所有记录。 x
是您要从中查找匹配值的子集(或任何其他data.table)。结果与请求的输出匹配:
dt
id code date_down date_up row found_date
1: 1 p 2019-01-01 2019-01-02 1 <NA>
2: 1 f 2019-01-02 2019-01-03 2 <NA>
3: 2 f 2019-01-02 2019-01-02 3 <NA>
4: 2 p 2019-01-03 <NA> 4 <NA>
5: 3 p 2019-01-04 <NA> 5 <NA>
6: 4 <NA> 2019-01-05 2019-01-05 6 <NA>
7: 5 f 2019-01-07 2019-01-08 7 2019-01-08
8: 5 p 2019-01-07 2019-01-08 8 2019-01-09
9: 5 p 2019-01-09 2019-01-09 9 <NA>
10: 6 f 2019-01-10 2019-01-10 10 2019-01-11
11: 6 p 2019-01-10 2019-01-10 11 2019-01-11
12: 6 p 2019-01-10 2019-01-11 12 <NA>
注意:如果愿意,您可以通过执行row
来删除dt[, row := NULL]
列。
与上述相同的逻辑可以加入并找到结果列,但现在使用“按引用分配” :=
在以下位置创建found_date
dt
:
dt[, row := .I] # add row numbers (as in all the solutions)
# Compute result columns ( then assign by reference into dt using :=
# dt$found_date <-
dt[, found_date := # assign by reference to dt$found_date
dt[code=='p'][dt,
.( x.date_up ),
on = .(id==id, row > row, date_up > date_down),
mult = "first"]]
在解决方案2中,将我们的结果“通过引用”分配给dt的细微变化应该比解决方案1更有效。解决方案1的计算结果完全相同,唯一的区别是解决方案1使用了标准分配{{1} }创建<-
(效率较低)。
类似于解决方案2 ,但现在使用dt$found_date
代替.(.SD)
来引用原始dt,而无需直接命名。
dt
上面的 .SD引用了我们要分配回的原始dt。它对应于data.table的子集,该子集包含在第一个dt[, row := .I] # add row numbers (as in all the solutions)
setkey(dt, id, row, date_down) #set key for dt
# For all rows of dt, create found_date by reference :=
dt[, found_date :=
# dt[code=='p'][dt,
dt[code=='p'][.(.SD), # our subset (or another data.table), joined to .SD (referring to original dt)
.( x.date_up ),
on = .(id==id, row > row, date_up > date_down),
mult = "first"] ]
中选择的行,这是所有行,因为我们没有对其进行过滤。
注意:在解决方案3中,我使用dt[,
来设置密钥。我应该在解决方案1和解决方案2中做到这一点-但是,我不想在@OllieB成功测试它们之后更改这些解决方案。
类似于解决方案3 ,但使用.SD的次数比以前多了。现在,我们的主要data.table名称setkey()
在整个表达式中仅一次出现!
dt
在我们的data.table名称# add row column and setkey() as previous solutions
dt[, found_date :=
# dt[code=='p'][.(.SD),
.SD[code=='p'][.SD, # .SD in place of dt at left! Also, removed .() at right (not sure on this second change)
.(found_date = x.date_up),
on = .(id==id, row > row, date_up > date_down),
mult = "first"]]
以上的更改仅出现一次。我非常喜欢它,因为它可以轻松地在其他地方复制,改编和重用。
还要注意:我以前使用过dt
的地方,现在已经删除了.(SD)
周围的。(),因为它似乎不需要它。但是对于该更改,我不确定它是否具有任何性能优势或它是否是data.table首选语法。如果有人可以对此发表意见以提出建议,我将不胜感激。
与先前的解决方案类似,但在加入时使用.SD
明确将操作的子集分组
by
在最后一个解决方案中,我将其更改为使用# add row column and setkey() as previous solutions
dt[, found_date :=
.SD[code=='p'][.SD,
.(found_date = x.date_up),
# on = .(id==id, row > row, date_up > date_down),
on = .(row > row, date_up > date_down), # removed the id column from here
mult = "first"]
, by = id] # added by = id to group the .SD subsets
子句将by
上的.SD子集显式分组。
注意:与解决方案1-4相比,解决方案5在OllieB的实际数据上的表现不佳。但是,通过测试我自己的模拟数据,我发现解决方案5可以在唯一组数量方面表现良好id
列中的值很低:
-150万条记录中只有6个组,此解决方案的工作速度与其他组一样快。
-在150万条记录中有4万个小组,我看到了与OllieB报告的类似的不良表现。
对于OllieB的实际数据中的145万条记录,根据OllieB的反馈,解决方案1至4的每个“经过”时间都在2.42秒或更短。对于OllieB,“解决方案3”的运行时间最快,为“ elapsed = 1.22”秒。
我个人更喜欢解决方案4,因为语法更简单。
id
子句)在OllieB的真实数据测试上耗时577秒,表现不佳。 data.table版本:1.12.0
R版本3.5.3(2019-03-11)
by
调用[通常]更有效的辅助索引和自动索引。 作为您的问题的一部分,您要求“对data.table的任何良好引用”。我发现以下帮助:
特别是对于这个问题,值得阅读:
重要说明this answer by @Arun解释了“实现on =参数的原因”表明不再需要设置键了:
因此必须弄清楚是否花费了时间 重新排序整个data.table是值得的时间 高速缓存有效的连接/聚合。通常,除非有重复 对同一键进行分组/连接操作 data.table,应该没有明显的差异。
因此,在大多数情况下,无需设置键 还有。我们建议尽可能使用on =,除非设置密钥 您想利用的性能有了显着提高。
这个SO问题似乎是有关不同on
连接的信息中心:How to join (merge) data frames (inner, outer, left, right)?
最后,data.table cheat sheet是一个很好的参考(来自data.table上GitHub入门Wiki上的链接)。
一如既往,我很感谢任何人提出建议,因为这可能会得到进一步改善。
如果可以添加任何内容,请随时发表评论,更正或发布其他解决方案。
答案 1 :(得分:2)
非数据表方式:
> df <- structure(list(
+ id = c(1L, 1L, 2L, 2L, 3L, 4L, 5L, 5L, 5L, 6L, 6L, 6L),
+ code = c("p", "f", "f", "p", "p", "<NA>", "f", "p", "p", "f", "p", "p"),
+ date_down = structure(c(17897, 17898, 17898, 17899, 17900, 17901, 17903, 17903, 17905, 17906, 17906, 17906), class = "Date"),
+ date_up = structure(c(17898, 17899, 17898, NA, NA, 17901, 17904, 17904, 17905, 17906, 17906, 17907), class = "Date")),
+ class = c("data.frame"),
+ row.names = c(NA, -12L))
>
>
> Lista <- lapply(split(df, df$id), function(x){
+ x$founddate <-
+ sapply(c(1:nrow(x)), function(y){
+ na.omit(sapply(y:nrow(x), function(i){
+ ifelse(x[i + 1, "code"] == "p" & x[i + 1, "date_up"] > x[y, "date_down"],
+ x[i + 1, "date_up"], NA)
+ }))[1]
+ })
+ x$founddate <- as.Date(x$founddate, origin = "1970-01-01")
+ return(x)
+ })
>
>
> df <- do.call(rbind.data.frame, Lista)
>
> df
id code date_down date_up founddate
1.1 1 p 2019-01-01 2019-01-02 <NA>
1.2 1 f 2019-01-02 2019-01-03 <NA>
2.3 2 f 2019-01-02 2019-01-02 <NA>
2.4 2 p 2019-01-03 <NA> <NA>
3 3 p 2019-01-04 <NA> <NA>
4 4 <NA> 2019-01-05 2019-01-05 <NA>
5.7 5 f 2019-01-07 2019-01-08 2019-01-08
5.8 5 p 2019-01-07 2019-01-08 2019-01-09
5.9 5 p 2019-01-09 2019-01-09 <NA>
6.10 6 f 2019-01-10 2019-01-10 2019-01-11
6.11 6 p 2019-01-10 2019-01-10 2019-01-11
6.12 6 p 2019-01-10 2019-01-11 <NA>
>
在给定条件下,每行有多个匹配项。建议的答案将获得第一个匹配项,但是可以对其进行修改。
希望有帮助。
答案 2 :(得分:2)
这是一种快速而肮脏的方法,不需要您多加考虑,它会捕获子集中的第一个可行选项,并在不存在的情况下留下NA
。
do(f(.))
调用对由f
语句定义的dt
的每个子集求值预定义函数group_by
。我会将该简单脚本翻译成Rcpp
以供认真使用。
library(dplyr)
f <- function(x){
x <- x %>% mutate(founddate = as.Date(NA))
for(i in 1:nrow(x)){
y <- x[i, "date_down"]
x[i, "founddate"] <-(x[-c(1:i),] %>% filter(code == "p", date_up > y) %>% select(date_up))[1, ]
}
return(x)
}
dt %>% group_by(id) %>% do(f(.))
# A tibble: 12 x 5
# Groups: id [6]
id code date_down date_up founddate
<int> <chr> <date> <date> <date>
1 1 p 2019-01-01 2019-01-02 NA
2 1 f 2019-01-02 2019-01-03 NA
3 2 f 2019-01-02 2019-01-02 NA
4 2 p 2019-01-03 NA NA
5 3 p 2019-01-04 NA NA
6 4 <NA> 2019-01-05 2019-01-05 NA
7 5 f 2019-01-07 2019-01-08 2019-01-08
8 5 p 2019-01-07 2019-01-08 2019-01-09
9 5 p 2019-01-09 2019-01-09 NA
10 6 f 2019-01-10 2019-01-10 2019-01-11
11 6 p 2019-01-10 2019-01-10 2019-01-11
12 6 p 2019-01-10 2019-01-11 NA
您对糟糕的表现的评论不足为奇。如果我知道怎么办,我会个人发消息,但是下面是Rcpp::cppFunction
做同样的事情。
Rcpp::cppFunction('DataFrame fC(DataFrame x) {
int i, j;
int n = x.nrows();
CharacterVector code = x["code"];
DateVector date_up = x["date_up"];
DateVector date_down = x["date_down"];
DateVector founddate = rep(NA_REAL, n);
for(i = 0; i < n; i++){
for(j = i + 1; j < n; j++){
if(code(j) == "p"){
if(date_up(j) > date_down(i)){
founddate(i) = date_up(j);
break;
} else{
continue;
}
} else{
continue;
}
}
}
x.push_back(founddate, "founddate");
return x;
}')
dt %>% group_by(id) %>% do(fC(.))