• R语言
  • 有无简便方法在data.table实现有条件的分组排序后增加序号?

编一个方便复现的数据如下:

library(data.table)
df <- data.table(
  id = c(1234, 1234, 1234, 1234, 5678, 5678, 5678),
  date = c(
    '2006-01-01', '2007-01-01', '2010-01-01', '2021-01-01', '2009-01-01', '2013-01-01', '2015-01-01'),
  rank = c(1, 2, 3, 4, 1, 2, 3),
  is_contain = c(0, 1, 1, 0, 1, 1, 0))

数据集中有四列数据,第一列是id,第二列是date,每个id可以对应多个date;第三列rank是按每个id名下date升序排列后得到的序号,第四列is_contain是一个条件。想要达到的效果是,增加一列,对is_contain=1的数据按id分组后按date升序排序后得到序号,而is_contain=0的数据在新列的值都为0或者其他不是序号的标识符,如下。

     id       date rank is_contain rank_new
1: 1234 2006-01-01    1          0        0
2: 1234 2007-01-01    2          1        1
3: 1234 2010-01-01    3          1        2
4: 1234 2021-01-01    4          0        0
5: 5678 2009-01-01    1          1        1
6: 5678 2013-01-01    2          1        2

我写的代码如下:

df$date <- as.Date(df$date)

df.1 <- df[is_contain == 1, ]
df.1.1 <- df.1[, by = id, rank_new := order(date)]

df.2 <- df[is_contain == 0, ]
df.2$rank_new <- 0

df.new <- rbind(df.1.1, df.2)
head(df.new[order(id,date),])

感觉自己写的有点麻烦,看看各位有无更简便些的解法?

解释一下我觉得麻烦的原因是,真实的业务数据会有大几百万条,拆出新表再合并的话有点占内存。

    分两个组,之后再对is_contain=0的单独调整,我这边用999来标记。目前只能想到这样做,不知道能不能更简单?

    library(data.table)
    df <- data.table(
      id = c(1234, 1234, 1234, 1234, 5678, 5678, 5678),
      date = c(
        '2006-01-01', '2007-01-01', '2010-01-01', '2021-01-01', '2009-01-01', '2013-01-01', '2015-01-01'),
      rank = c(1, 2, 3, 4, 1, 2, 3),
      is_contain = c(0, 1, 1, 0, 1, 1, 0))
    df[,date:=as.Date(date)]
    df[,new_rank:=rank(date),by=.(is_contain,id)][is_contain==0,new_rank:=999][]
    #>      id       date rank is_contain new_rank
    #> 1: 1234 2006-01-01    1          0      999
    #> 2: 1234 2007-01-01    2          1        1
    #> 3: 1234 2010-01-01    3          1        2
    #> 4: 1234 2021-01-01    4          0      999
    #> 5: 5678 2009-01-01    1          1        1
    #> 6: 5678 2013-01-01    2          1        2
    #> 7: 5678 2015-01-01    3          0      999

    <sup>Created on 2021-12-30 by the reprex package (v2.0.1)</sup>

      Cloud2016
      原来的 rank 字段就是写的SQL,只是这一步以及后面的步骤都是在做探索性数据分析,所以就在R里面鼓捣了。真正开发上生产的时候会在数据库里先处理的。
      话说回来,你觉得R和SQL做数据处理的边界在哪里呢?就是你说的“处理完”,撒时候算“完”?

        yuanfan 当面临数以百G的数据时,凡是涉及数据处理的,尽一切可能用 SQL 处理。以下两种情况建议用 R 语言处理。

        1. 在数据可视化或数据展示时,为了调用 ggplot2 或者其他绘图、画表格等 R 包,用 R 做必要的数据调整。
        2. 用 SQL 实现复杂度明显加倍而用 R 处理就是几个统计函数的事情。

        yuanfan 谢谢!要更快的话,可以把by改成keyby。但是会有两个副作用:一是keyby无法保留数据的原始顺序,是按分组顺序来排列数据的;二是keyby会使返回的数据以分组变量作为key

        library(data.table)
        df <- data.table(
          id = c(1234, 1234, 1234, 1234, 5678, 5678, 5678),
          date = c(
            '2006-01-01', '2007-01-01', '2010-01-01', '2021-01-01', '2009-01-01', '2013-01-01', '2015-01-01'),
          rank = c(1, 2, 3, 4, 1, 2, 3),
          is_contain = c(0, 1, 1, 0, 1, 1, 0))
        df[,date:=as.Date(date)]
        head(df)
        #>      id       date rank is_contain
        #> 1: 1234 2006-01-01    1          0
        #> 2: 1234 2007-01-01    2          1
        #> 3: 1234 2010-01-01    3          1
        #> 4: 1234 2021-01-01    4          0
        #> 5: 5678 2009-01-01    1          1
        #> 6: 5678 2013-01-01    2          1
        
        by_result<-df[,new_rank:=rank(date),by=.(is_contain,id)][is_contain==0,new_rank:=999]
        head(by_result)
        #>      id       date rank is_contain new_rank
        #> 1: 1234 2006-01-01    1          0      999
        #> 2: 1234 2007-01-01    2          1        1
        #> 3: 1234 2010-01-01    3          1        2
        #> 4: 1234 2021-01-01    4          0      999
        #> 5: 5678 2009-01-01    1          1        1
        #> 6: 5678 2013-01-01    2          1        2
        key(by_result)
        #> NULL
        # the key is Null and it preserves the original order of observations
        
        keyby_result<-df[,new_rank:=rank(date),keyby=.(is_contain,id)][is_contain==0,new_rank:=999]
        head(keyby_result)
        #>      id       date rank is_contain new_rank
        #> 1: 1234 2006-01-01    1          0      999
        #> 2: 1234 2021-01-01    4          0      999
        #> 3: 5678 2015-01-01    3          0      999
        #> 4: 1234 2007-01-01    2          1        1
        #> 5: 1234 2010-01-01    3          1        2
        #> 6: 5678 2009-01-01    1          1        1
        key(keyby_result)
        #> [1] "is_contain" "id"
        # the key is group and it orders the data by groups
        # setkey(keyby_result,NULL) # remove the key

        <sup>Created on 2021-12-31 by the reprex package (v2.0.1)</sup>

        yuanfan

        library(data.table)
        df <- data.table(
          id = c(1234, 1234, 1234, 1234, 5678, 5678, 5678),
          date = c(
            '2006-01-01', '2007-01-01', '2010-01-01', '2021-01-01', '2009-01-01', '2013-01-01', '2015-01-01'),
          rank = c(1, 2, 3, 4, 1, 2, 3),
          is_contain = c(0, 1, 1, 0, 1, 1, 0))
        
        
        df[order(id,date)][is_contain==1,new_rank:=.SD[,.I], by =.(id)][]

        爪机上回复,不方便贴output了哈