Oll*_*ieB 10 r data.table
我是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
Run Code Online (Sandbox Code Playgroud)
我想做的是
iddate_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>
Run Code Online (Sandbox Code Playgroud)
我尝试了多种变体,使用.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)
Run Code Online (Sandbox Code Playgroud)
kra*_*ads 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
Run Code Online (Sandbox Code Playgroud)
请注意上面的联接表达式:
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>
Run Code Online (Sandbox Code Playgroud)
注意:您可以根据需要删除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"]]
Run Code Online (Sandbox Code Playgroud)
在解决方案2中,将我们的结果“通过引用”分配给dt的细微变化应该比解决方案1更有效。解决方案1的计算结果完全相同,唯一的区别是解决方案1使用标准分配<-创建dt$found_date(效率较低)。
类似于解决方案2,但现在使用.(.SD)代替dt原始dt而不直接命名它。
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"] ]
Run Code Online (Sandbox Code Playgroud)
上面的.SD引用了我们分配给它的原始dt。它对应于data.table的子集,该子集包含在第一个dt[,中选择的行,即所有行,因为我们没有对其进行过滤。
注意:在解决方案3中,我曾经setkey()设置过密钥。我应该在解决方案1和解决方案2中做到这一点 -但是,在@OllieB成功测试它们之后,我不想更改这些解决方案。
与解决方案3类似,但比以前多使用一次.SD。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"]]
Run Code Online (Sandbox Code Playgroud)
更改之后,我们的data.table名称dt仅出现一次。我非常喜欢它,因为它可以轻松地在其他地方复制,改编和重用。
还要注意:我以前使用.(SD)过的地方现在已经删除了。(),.SD 因为它似乎不需要它。但是对于该更改,我不确定它是否具有任何性能优势,还是它是data.table首选语法。如果有人可以添加评论以提出建议,我将不胜感激。
类似于先前的解决方案,但是在加入时利用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
Run Code Online (Sandbox Code Playgroud)
在最后一个解决方案上,我将其更改为使用by子句将.SD子集显式分组到id。
注意:与解决方案1-4相比,解决方案5在OllieB的实际数据上的表现不佳。但是,通过测试我自己的模拟数据,我发现当id列中唯一组的数量很低时,解决方案5的性能很好:
-仅6个1.5M中的小组记录了此解决方案的运行速度,与其他解决方案一样快。
-在150万条记录中有4万个小组,我看到了与OllieB报告的类似的不良表现。
根据OllieB的反馈,对于OllieB的实际数据中的145万条记录,解决方案1至4的所有“经过”时间均为2.42秒或更短。对于OllieB,解决方案3的工作时间最快,为“ elapsed = 1.22”秒。
我个人更喜欢解决方案4,因为语法更简单。
by对于OllieB的真实数据测试,解决方案5(using 子句)的执行效果很差,需要577秒。data.table版本:1.12.0
R版本3.5.3(2019-03-11)
on [通常]效率更高的辅助索引和自动索引。 作为您的问题的一部分,您要求“对data.table的任何良好引用”。我发现以下帮助:
data.table入门GitHub上的Wiki是开始的地方。
特别是对于这个问题,值得一读:
重要的是,请注意@Arun的答案,该答案解释“实现on =参数的原因”表明不再需要设置键:
因此,必须弄清楚重新排序整个数据表所花费的时间是否值得进行高速缓存有效的连接/聚合。通常,除非对同一个键控数据表执行重复的分组/联接操作,否则应该没有明显的区别。
因此,在大多数情况下,无需再设置密钥。我们建议在可能的情况下使用on =,除非您希望利用key来提高性能。
这样的问题似乎是有关不同data.table联接的信息中心:如何联接(合并)数据帧(内部,外部,左侧,右侧)?
最后,data.table备忘单是一个很好的参考(来自data.table上GitHub入门Wiki上的链接)。
一如既往,如果有人提出建议,我将不胜感激,因为也许可以进一步改善。
如果您可以添加任何内容,请随时发表评论,更正或发布其他解决方案。