【问题标题】:How do I evaluate columns inside data.table with different conditions如何在不同条件下评估 data.table 中的列
【发布时间】:2014-07-10 03:09:34
【问题描述】:

给定data.table如下:

library(data.table)
set.seed(100)
dt <- data.table(a=c(1:3, 1), b = c(1,0,1, 3), c = c(1,2,1,3), x = rnorm(4), y = rnorm(4), d = c(4, 6, 6, 7)) 

dt返回,

   a b c           x          y d
1: 1 1 1 -0.50219235  0.1169713 4
2: 2 0 2  0.13153117  0.3186301 6
3: 3 1 1 -0.07891709 -0.5817907 6
4: 1 3 3  0.88678481  0.7145327 7

“a”、“b”和“c”列中等于 3 的任何数字都为 TRUE

此外,“d”列中等于 6 的任何数字都将为 TRUE

如何使用列名(“a”、“b”、“c”和“d”)在 dt 内部进行评估

所以我的回报是:

       a     b     c           x          y     d
1: FALSE FALSE FALSE -0.50219235  0.1169713 FALSE
2: FALSE FALSE FALSE  0.13153117  0.3186301  TRUE
3:  TRUE FALSE FALSE -0.07891709 -0.5817907  TRUE
4: FALSE  TRUE  TRUE  0.88678481  0.7145327 FALSE

谢谢

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    我想出的方法如下所示:

    dt[, c("a", "b", "c") := lapply(.SD, `==`, 3), 
       .SDcols = c("a", "b", "c")][, d := (d == 6)][]
    #        a     b     c           x          y     d
    # 1: FALSE FALSE FALSE -0.50219235  0.1169713 FALSE
    # 2: FALSE FALSE FALSE  0.13153117  0.3186301  TRUE
    # 3:  TRUE FALSE FALSE -0.07891709 -0.5817907  TRUE
    # 4: FALSE  TRUE  TRUE  0.88678481  0.7145327 FALSE
    

    它在可读性方面并没有赢得任何分数,但在性能方面似乎还可以。

    以下是一些要测试的示例数据:

    library(data.table)
    set.seed(100)
    Nrow = 3000000
    dt <- data.table(a = sample(10, Nrow, TRUE), 
                     b = sample(10, Nrow, TRUE), 
                     c = sample(10, Nrow, TRUE), 
                     x = rnorm(Nrow), 
                     y = rnorm(Nrow),
                     d = sample(10, Nrow, TRUE)) 
    

    ...一些要测试的功能...

    fun1 <- function(indt) {
      indt[, c("a", "b", "c") := lapply(.SD, `==`, 3), 
         .SDcols = c("a", "b", "c")][, d := (d == 6)][]
    }
    
    fun2 <- function(indt) {
      for (i in c("a","b","c")) indt[, (i):=get(i)==3]
      for (i in c("d"))         indt[, (i):=get(i)==6]
      indt
    }
    
    fun3 <- function(indt) {
      f <- function(col,x) indt[,(col):=(.SD==x),.SDcols=col]
      lapply(list("a","b","c"), f, 3)
      lapply(list("d"), f, 6)
      indt
    }
    

    ...还有一些时间安排...

    microbenchmark(fun1(copy(dt)), fun2(copy(dt)), fun3(copy(dt)), times = 10)
    # Unit: milliseconds
    #            expr      min        lq    median        uq       max neval
    #  fun1(copy(dt)) 518.6034  535.0848  550.3178  643.2968  695.5819    10
    #  fun2(copy(dt)) 830.5808 1037.8790 1172.6684 1272.6236 1608.9753    10
    #  fun3(copy(dt)) 922.6474 1029.8510 1097.7520 1145.1848 1340.2009    10
    
    identical(fun1(copy(dt)), fun2(copy(dt)))
    # [1] TRUE
    identical(fun2(copy(dt)), fun3(copy(dt)))
    # [1] TRUE
    

    在这个规模上,我会选择对你来说最易读的东西(除非那些毫秒真的很重要),但如果你的数据更大,你可能想尝试更多不同的选项。


    来自马特的补充

    同意。要跟进评论,这里是fun4,但在这种大小(3e6 行,90MB)上,它只是最快的速度

    fun4 <- function(indt) {
      for (i in c("a","b","c")) set(indt,NULL,i,indt[[i]]==3)
      for (i in c("d"))         set(indt,NULL,i,indt[[i]]==6)
      indt
    }
    
    microbenchmark(copy(dt), fun1(copy(dt)), fun2(copy(dt)), fun3(copy(dt)), 
                   fun4(copy(dt)), times = 10)
    # Unit: milliseconds
    #            expr        min         lq     median         uq       max neval
    #        copy(dt)   64.13398   65.94222   68.32217   82.39942  110.3293    10
    #  fun1(copy(dt))  601.84611  618.69288  690.47179  713.56760  766.1534    10
    #  fun2(copy(dt))  887.99727  950.33821  978.98988 1071.31253 1180.1281    10
    #  fun3(copy(dt)) 1566.90858 1574.30635 1603.55467 1673.38625 1771.4054    10
    #  fun4(copy(dt))  566.43528  568.91103  575.06881  672.44021  692.9839    10
    
    > identical(fun1(copy(dt)), fun4(copy(dt)))
    [1] TRUE
    

    接下来我将数据大小增加了 10 倍,达到 3000 万行,即 915MB。

    请注意,这些时间现在以秒为单位,并且在我的慢速上网本上。

    set.seed(100)
    Nrow = 30000000
    dt <- data.table(a = sample(10, Nrow, TRUE), 
                  b = sample(10, Nrow, TRUE), 
                  c = sample(10, Nrow, TRUE), 
                  x = rnorm(Nrow), 
                  y = rnorm(Nrow),
                  d = sample(10, Nrow, TRUE)) 
    object.size(dt)/1024^2
    # 915 MB
    microbenchmark(copy(dt),fun1(copy(dt)), fun2(copy(dt)), fun3(copy(dt)), 
                     fun4(copy(dt)), times = 3)
    # Unit: seconds
    #            expr       min        lq    median       uq      max neval
    #        copy(dt)   8.04262  53.68556  99.32849 269.4414 439.5544     3
    #  fun1(copy(dt)) 207.70646 260.16710 312.62775 317.8966 323.1654     3
    #  fun2(copy(dt)) 421.78934 502.03503 582.28073 658.0680 733.8553     3
    #  fun3(copy(dt)) 104.30914 187.49875 270.68836 384.7804 498.8724     3
    #  fun4(copy(dt)) 158.17239 165.35898 172.54557 183.4851 194.4246     3
    

    这里,fun4 的平均速度快了很多,我猜这是因为 for 一次循环一列的内存效率。在fun1fun3 中,:= 的 RHS 是三列宽,然后分配给三个目标列。话虽如此,为什么我之前的fun2 最慢呢?毕竟它是逐列的。也许get() 在进入== 之前复制该列。

    fun3 有一次运行速度最快(104 对 158)。我不确定我是否信任microbenchmark。我似乎记得 Radford Neal 对microbenchmark 的一些批评,但不记得结果。

    这些时间在我的上网本上非常慢:

    $ lscpu
    Architecture:          x86_64
    CPU op-mode(s):        32-bit, 64-bit
    Byte Order:            Little Endian
    CPU(s):                2
    On-line CPU(s) list:   0,1
    Thread(s) per core:    1
    Core(s) per socket:    2
    Socket(s):             1
    NUMA node(s):          1
    Vendor ID:             AuthenticAMD
    CPU family:            20
    Model:                 2
    Stepping:              0
    CPU MHz:               800.000
    BogoMIPS:              1995.06
    Virtualisation:        AMD-V
    L1d cache:             32K
    L1i cache:             32K
    L2 cache:              512K
    NUMA node0 CPU(s):     0,1
    
    > sessionInfo()
    R version 3.1.0 (2014-04-10)
    Platform: x86_64-pc-linux-gnu (64-bit)   
    
    attached base packages:
    [1] stats     graphics  grDevices utils     datasets  methods   base     
    
    other attached packages:
    [1] microbenchmark_1.3-0 data.table_1.9.2     bit64_0.9-3          bit_1.1-11
    

    【讨论】:

    • +10 我猜你的速度更快,因为两次调用[.data.table 而不是我的四个。这给了我一个想法,我将在 ...
    【解决方案2】:

    我首先尝试的是:

    > dt
       a b c           x          y d
    1: 1 1 1 -0.50219235  0.1169713 4
    2: 2 0 2  0.13153117  0.3186301 6
    3: 3 1 1 -0.07891709 -0.5817907 6
    4: 1 3 3  0.88678481  0.7145327 7
    > for (i in c("a","b","c")) dt[get(i)==3, (i):=TRUE]
    > dt[d==6, d:=TRUE]
    

    但那得到了错误的答案:

    > dt
       a b c           x          y d
    1: 1 1 1 -0.50219235  0.1169713 4
    2: 2 0 2  0.13153117  0.3186301 1
    3: 1 1 1 -0.07891709 -0.5817907 1
    4: 1 1 1  0.88678481  0.7145327 7
    > 
    

    这是因为 := 的 RHS 被强制匹配列的类型,即 TRUE 在这种情况下被强制为 1。但是您想更改列的类型,这在data.table 中故意比较难。想象一下 RAM 中有一个 20GB 的 data.table - 您几乎不想更改列类型,因为这将涉及复制整个列。 99% 的时间您都希望将 RHS 强制转换为列的类型,例如,如果列类型为 integer,则将 :=1 强制转换为 :=1L

    要更改列类型,您需要plonk 将新列直接插入该列指针槽。您可以通过使 RHS 与行数一样长来做到这一点。

    > for (i in c("a","b","c")) dt[, (i):=get(i)==3]
    > for (i in c("d"))         dt[, (i):=get(i)==6]
    > dt
           a     b     c           x          y     d
    1: FALSE FALSE FALSE -0.50219235  0.1169713 FALSE
    2: FALSE FALSE FALSE  0.13153117  0.3186301  TRUE
    3:  TRUE FALSE FALSE -0.07891709 -0.5817907  TRUE
    4: FALSE  TRUE  TRUE  0.88678481  0.7145327 FALSE
    >
    

    【讨论】:

      【解决方案3】:

      这似乎符合您的要求:

      f <- function(col,x) dt[,(col):=(.SD==x),.SDcols=col]
      lapply(list("a","b","c"), f, 3)
      lapply(list("d"), f, 6)
      dt
      #        a     b     c           x          y     d
      # 1: FALSE FALSE FALSE -0.50219235  0.1169713 FALSE
      # 2: FALSE FALSE FALSE  0.13153117  0.3186301  TRUE
      # 3:  TRUE FALSE FALSE -0.07891709 -0.5817907  TRUE
      # 4: FALSE  TRUE  TRUE  0.88678481  0.7145327 FALSE
      

      注意事项:

      • 第二个lapply(...) 可以替换为:dt[,d:=(d==6)] 但我想知道您的真实案例是否比这更复杂。
      • 此方法不适用于数据帧。原因很微妙 并且与通过引用更新数据表有关。所以例如 如果您将数据框传递给函数,则将其传递 按值 - 一个副本 制作。如果你在函数中修改它,你正在修改 复制;原件不变。另一方面,如果您传递数据 表到一个函数,它被传递引用,并修改 函数内部所做的都反映在原始数据表中。

      【讨论】:

      • +1 从来没有像这样使用lapply。虽然,DT[,:=] 不可见地返回 DT,所以如果 DT 很大,lapply 可能会返回一个巨大的结果(例如,dt 打印到控制台 3 次,用于第一个 lapply)。跨度>
      • @MattDowle 感谢您的编辑 - 这更清晰。每天我都会学到一些关于 data.table 的新知识。
      【解决方案4】:

      这是另一种方法,它在速度方面与前两种方法相差不远,并且可能更具可读性:

      # the variables and values you want
      vars = c('a','b','c','d')
      values = c(3,3,3,6)
      
      dt[, (vars) := Map('==', .SD, values), .SDcols = vars]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-22
        • 1970-01-01
        • 2020-03-12
        • 1970-01-01
        相关资源
        最近更新 更多