r - 使用满足条件的同一组中的第一个下一行设置列值

标签 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
    

    我想做的是
  • 子集(组)由 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 的任何好的引用:) 非常感谢

    编辑:
    我编辑了提供的原始数据以提供一个更微妙的示例,其中第 10 行使用第 12 行的数据进行更新,因为第 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)
    

    最佳答案

    按组将 data.table 连接到其自身的子集,以从匹配非相等条件的行中获取值。

    概括:

  • 下面我展示了 5 个工作 data.table候选解决方案
    针对 OP 的实际数据集(1.4M 记录)的性能测试。
  • 所有 5 个解决方案都使用“非对等”连接(使用不等式来比较
    用于连接的列)在 on 中条款。
  • 每个解决方案只是一个小的渐进式代码更改,所以它应该是
    易于遵循以比较不同 data.table选项和语法选择。

  • 方法

    工作通过 data.table为此,我将其分解为 OP 问题的以下步骤:
  • 将 dt 加入其自身的一个子集(或另一个 data.table 与此相关)。
  • 从 dt 或子集中选择(并重命名)所需的列。
  • 根据 dt 中的列与子集中的列进行比较来定义连接条件,包括使用“非等”(non-equal)比较。
  • (可选)定义在子集中找到多个匹配记录时是选择第一个匹配项还是最后一个匹配项。

  • 解决方案1:
    # 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在这种情况下是您的主要 dt。通过这种方式,您可以从主 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]如果你喜欢。

    解决方案2:

    连接和查找结果列的逻辑与上述相同,但 现在使用“通过引用分配”:= 创建 found_datedt :
    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 使用标准分配 <-创建 dt$found_date (效率较低)。

    解决方案3:

    喜欢解决方案 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"] ]  
    

    上面的 .SD 引用回我们正在分配回的原始 dt。它对应于包含在第一个 dt[, 中选择的行的 data.table 的子集。这是所有行,因为我们没有过滤它。

    注意:在解决方案 3 中,我使用了 setkey()设置 key 。我应该在解决方案 1 和解决方案 2 中这样做 - 但是,在@OllieB 成功测试它们之后,我不想更改这些解决方案。

    解决方案4:

    喜欢解决方案 3 但 比以前多一次使用 .SD。我们的主要数据表名称 dt现在在我们的整个表达式中只出现一次!
    # 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"]]
    

    随着我们的 data.table 名称上方的更改 dt只出现一次。我非常喜欢它,因为它可以轻松地在其他地方复制、改编和重用。

    另请注意:我以前使用过的地方 .(SD)我现在已经删除了 .SD 周围的 .()因为它似乎不需要它。但是,对于该更改,我不确定它是否具有任何性能优势,或者它是否是 data.table 首选语法。如果有人可以添加评论以就此提出建议,我将不胜感激。

    解决方案5:

    像以前的解决方案,但 利用 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子句在 id 上显式分组 .SD 子集.

    注:与解决方案 1 - 4 相比,解决方案 5 针对 OllieB 的实际数据表现不佳。但是,测试我自己的模拟数据时,我发现解决方案 5 在来自 id 的唯一组的数量上表现良好。列低:
    - 在 150 万条记录中只有 6 个组,此解决方案的运行速度与其他解决方案一样快。
    - 在 150 万条记录中有 4 万个组,我看到了与 OllieB 报告的类似的糟糕表现。

    结果

    解决方案 1 - 4 表现良好:
  • 根据 OllieB 的反馈,对于 OllieB 实际数据中的 145 万条记录,解决方案 1 到 4 中的每一个都是 2.42 秒或更短的“经过”时间。解决方案 3 似乎对 OllieB 的“elapsed=1.22”秒最快。
  • 我个人更喜欢解决方案 4,因为语法更简单。

  • 解决方案5
  • 解决方案 5(使用 by 子句)表现不佳,OllieB 对其真实数据的测试耗时 577 秒。


  • 使用的版本

    数据表版本:1.12.0

    R 版本 3.5.3 (2019-03-11)

    可能的进一步改进:
  • 将日期字段更改为整数可能有助于更有效地加入。请参阅 as.IDate() 将日期转换为 data.tables 中的整数。
  • 可能不再需要 setkey() 步骤:As explained here by @Arun由于 on调用 [经常] 更有效的二级索引和自动索引。

  • 对 data.table 的引用

    作为您问题的一部分,您要求提供“对 data.table 的任何良好引用”。我发现以下内容很有帮助:
  • data.table Getting started Wiki on GitHub是开始的地方。
  • 特别是对于这个问题,值得一读:
  • What does .SD stand for in data.table in R
  • Secondary indices and auto indexing 的 HTML 插图

  • 重要提示this answer by @Arun这解释了“实现 on= 参数的原因”表明可能不再需要设置键:

    It is therefore essential to figure out if the time spent on reordering the entire data.table is worth the time to do a cache-efficient join/aggregation. Usually, unless there are repetitive grouping / join operations being performed on the same keyed data.table, there should not be a noticeable difference.

    In most cases therefore, there shouldn't be a need to set keys any more. We recommend using on= wherever possible, unless setting key has a dramatic improvement in performance that you'd like to exploit.


  • 这个 SO 问题似乎是有关不同 data.table 的信息中心加入:How to join (merge) data frames (inner, outer, left, right)?
  • 最后,data.table cheat sheet是一个很好的引用(来自 GitHub 上 data.table Getting started Wiki 上的链接)。


  • 与往常一样,如果有人提出建议,我将不胜感激,因为也许这可以进一步改进。

    如果您可以添加任何内容,请随时发表评论、更正或发布其他解决方案。

    关于r - 使用满足条件的同一组中的第一个下一行设置列值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55070842/

    相关文章:

    R data.table 合并重复行并连接唯一值

    r - 根据每组的另一个查找表有条件地为一个数据框插入值?

    r - 为什么对象是矢量?

    r - 在 R 中叠加内核分布

    r - 在这种情况下如何使用 str_c 处理 NA

    r - 将行追加到 data.table 的工作方式与 data.frame : How and why? 中的工作方式不同

    r - 从函数发送列名到 ddply

    r - data.table 聚合中的第一个元素

    r - 是否有一个 R 函数可以读取带有\n 作为(列)分隔符的文本文件?

    r - 非对等连接的结果中的顺序是如何确定的?