当前位置: 首页 > 知识库问答 >
问题:

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

松钟展
2023-03-14

我是R的新手,这是我关于堆栈溢出的第一个问题。

我正在努力

  • 要通过引用指定新列,请执行以下操作:
  • 对于每行
  • 使用同一行组中第一行到下一行的值。
  • 这符合一个条件

示例数据:

    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>

我使用<code>尝试了许多变体。SD,。N,创建一个新的列,其中包含DT[,idcount:=seq_leg(.N),by=id]但实际上没有得到任何结果。非常感谢任何帮助。

也有好的参考资料。表:)非常感谢

编辑:我已经编辑了提供的原始数据,以给出一个更微妙的例子,其中第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)

共有3个答案

松雅昶
2023-03-14

这是一种快速而肮脏的方法,不需要您进行太多思考,并捕获子集中的第一个可行选项,如果不存在,则留下一个<code>NA</code>。

do(f(.)) 调用计算由 group_by 语句定义的每个 dt 子集上的预定义函数 f。我会把这个简单的脚本翻译成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::cpp函数来做同样的事情。

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(.))
谭志用
2023-03-14

数据表方式方法:

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

在给定的条件下,每行有多个匹配项。建议的答案得到第一个匹配,但这可以修改。

希望有帮助。

严朝明
2023-03-14

>

  • 下面我展示了5个工作<code>数据。表根据OP的实际数据集(140万条记录)进行性能测试的候选解决方案。

    所有5个解决方案都在< code>on子句中使用“非等价”连接(使用不等式来比较连接的列)。

    每个解决方案只是一个小的渐进式代码更改,因此应该很容易比较不同的< code>data.table选项和语法选择。

    为了解决 data.table 语法,我将其分解为 OP 问题的以下步骤:

      < li >将dt连接到其自身的子集(或另一个数据表)。 < li >从dt或子集中选择(并重命名)您想要的列。 < li >根据dt中的列与子集中的列进行比较来定义联接标准,包括使用“非相等”(不相等)比较。 < li >选择性地定义当在子集中找到多个匹配记录时,应该选择第一个还是最后一个匹配。
    # 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.表中获取所有记录。
    • 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>
    

    注意:如果您愿意,可以通过执行 dt[, 行 := NULL] 来删除列。

    与上面相同的逻辑来连接和查找结果列,但是现在使用“通过引用分配”< code>:=在< code>dt中创建< code>found_date:

    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使用标准分配<代码>

    与解决方案 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。它对应于数据的子集。包含在第一个<code>dt[,

    注意:在解决方案 3 中,我使用 setkey() 来设置密钥。我应该在解决方案1中这样做

    类似于解决方案 3,但使用 .SD 比以前多一次。我们的主 data.table 名称 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首选语法。如果有人能在这一点上添加评论以提供建议,我将不胜感激。

    与以前的解决方案一样,但在加入时使用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 
    

    在最后一个解决方案中,我将其改为使用< code>by子句显式地对。< code>id上的SD子集。

    注意:与解决方案1-4相比,解决方案5在OllieB的实际数据方面表现不佳。然而,通过测试我自己的模拟数据,我发现当id列中的唯一组数较低时,解决方案五可以表现良好:
    -在150万条记录中只有6个组,该解决方案的工作速度与其他解决方案一样快
    -在150万个记录中,有4万个组,我看到了与OllieB报告类似的糟糕表现。

    >

  • 根据 OllieB 的反馈,对于 OllieB 实际数据中的 145 万条记录,解决方案 1 到 4 中的每条记录都经过 2.42 秒或更短的“经过”时间。解决方案 3 对于 OllieB 来说,“已用 = 1.22”秒似乎工作得最快。

    我个人更喜欢解决方案4,因为语法更简单。

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

    数据表格版本:1.12.0

    R版本3.5.3(2019-03-11)

    • 将日期字段更改为整数可能有助于更有效地联接。请参阅。IDate() 将日期转换为数据表中的整数。
    • setkey() 步骤可能不再需要:正如这里所解释的那样,由于[通常]更有效的辅助指标和自动索引,@Arun。

    作为您问题的一部分,您要求“对 data.table 的任何良好引用”。我发现以下内容很有帮助:

    >

  • 数据。GitHub上的表格入门Wiki是开始的地方。

    特别是对于这个问题,值得一读:

    • SD在R中data.table代表什么
    • 辅助索引和自动索引的超文本标记语言小插曲

    重要的是,请注意@Arun的回答,它解释了“实现on=argument的原因”,表明可能不再需要设置键:

    因此,重要的是要找出重新排序整个数据所花费的时间。表值得花时间进行高效缓存连接/聚合。通常,除非对相同的键控数据执行重复的分组/连接操作。表中,应该没有明显的差异。

    因此,在大多数情况下,不需要再设置键了。我们建议尽可能使用on=,除非设置键可以显着提高您想利用的性能。

    >

  • 这个SO问题似乎是关于不同data.table连接的信息中心:如何连接(合并)数据帧(内部、外部、左侧、右侧)?

    最后,数据表备忘单是一个很好的参考(来自在 GitHub 上的“入门维基”上找到的链接)。

    一如既往,我很感激任何人的建议,因为这也许可以进一步改善。

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

  •  类似资料:
    • 我正在尝试创建一个新变量,该变量仅在满足特定条件时才打印一系列列的第一个值。 为了澄清,我的数据库看起来像这样: 我想创建一个变量(main),仅当值不是以C00到C99开头时,才打印第一个var列中的值。如果值确实以该条件开头,那么我想在下一列测试该条件,直到满足条件并打印该值。 因此,对于上表,新创建的变量(main)应如下所示: 我不太确定从哪里开始,但我怀疑这可能涉及突变()和ifelse

    • 问题内容: 我将不胜感激创建此查询的任何帮助。我尝试了好几种方法,但都没有碰到运气。由于我的问题很难表达,因此我将举一个我想做的简单例子。我的数据在结构上类似于以下内容: 如果我有两种特定颜色的ID,我想创建一个查询以返回类型。例如,我想查找所有具有蓝色和红色的ID。然后查询将返回: A和B的返回顺序并不重要。数据集很大,我希望有许多ID可以同时满足这两个条件(也许是50,000个左右)。我要指出

    • 我正在尝试使用一个do while循环来找出用户是想将一只狗还是一只猫检查到Java的养狗系统中。其思想是,当提示时输入“dog”或“cat”,任何输入都将导致错误,并且他们将再次被提示输入文件名。 如果输入了“cat”或“dog”,那么将为程序分配等效的文件(dogs.txt或cats.txt),然后系统将运行并将数据加载到程序中。 以下是当前的变量: 以及导致问题的方法: 下面是运行代码时打印

    • 问题内容: 使用以下模型: 如果我要查找包含至少一篇文章的订单操作,则可以按预期工作: 但是,如果要查找订单中所有商品的订单操作,正确的方法是什么? 引发错误(我理解为什么会这样)。 问题答案: 一个简单的解决方案: 这只是一个查询,但每篇文章都有一个内部联接。对于多篇文章,Willem更巧妙的解决方案应该会表现更好。

    • 我需要在同一个Google Sheets文件中同步所有工作表。我只想同步第一行和第一列(这是冻结的),而其余的内容不应该同步。 我还希望这样,如果我在工作表中插入/删除一行,也会对所有其他同步工作表执行相同的操作。此外,如果进行了其他修改,例如冻结了一行,则应在所有图纸上进行相同的修改。 我看到了这些问题中的代码: 如何在两个Google电子表格中使用=importrange()同步两张工作表 简