【问题标题】:R : data.table subsetting based on a integer columnR:基于整数列的 data.table 子集
【发布时间】:2014-01-04 10:21:10
【问题描述】:

只是想知道是否有一种更巧妙的方法来对 data.table 进行子集化。基本上我有一张有百万行和数百列的大桌子。我想根据一个整数 col/s 对它进行子集化,该整数 col/s 的值介于我定义的范围之间。

我想知道是否将相关列设置为键,这将是二进制搜索,但不确定我是否可以找到一系列值之间的行。

下面的人为示例。

> n = 1e7
> dt <- data.table(a=rnorm(n),b=sample(letters,replace=T,n))
> system.time(subset(dt, a > 1 & a < 2))
   user  system elapsed 
  1.596   0.000   1.596
> system.time(dt[a %between% c(1,2)])
   user  system elapsed 
  1.168   0.000   1.168 

这样的事情可以做吗?

setkey(dt,a)
dt[  ] : get me the rows between 1 and 2 values of the key

谢谢! -阿比

【问题讨论】:

  • between 不会节省任何时间,因为它包含代码 x &gt;= lower &amp; x &lt;= upperdt[a &gt; 1 &amp; a &lt; 2] 会一样快
  • 如何使用设置密钥?我刚刚更新了我的问题,不确定我是否可以对某个键进行范围搜索。

标签: r dataframe data.table


【解决方案1】:

如果您a 上设置密钥(这将需要一些时间(在我的机器上n=1e7 需要14.7 秒), 那么您可以使用滚动连接来识别您感兴趣的区域的开始和结束。

# thus the following will work. 
dt[seq.int(dt[.(1),.I,roll=-1]$.I, dt[.(2), .I, roll=1]$.I)]


n = 1e7
dt <- data.table(a=rnorm(n),b=sample(letters,replace=T,n))
system.time(setkey(dt,a))
#  This  does take some time
# user  system elapsed 
# 14.72    0.00   14.73
library(microbenchmark)
f1 <- function() t1 <- dt[floor(a) == 1]
f2 <-  function() t2 <- dt[a >= 1 & a <= 2]
f3 <- function() {t3 <- dt[seq.int(dt[.(1),.I,roll=-1]$.I, dt[.(2), .I, roll=1]$.I)]   }
microbenchmark(f1(),f2(),f3(), times=10)
# Unit: milliseconds
#  expr       min        lq    median        uq       max neval
#  f1() 371.62161 387.81815 394.92153 403.52299 489.61508    10
#  f2() 529.62952 536.23727 544.74470 631.55594 634.92275    10
#  f3()  65.58094  66.34703  67.04747  75.89296  89.10182    10

现在“快”了,但因为我们之前花了一些时间设置密钥。

添加@eddi 的基准测试方法

 f4 <- function(tolerance = 1e-7){  # adjust according to your needs
  start = dt[J(1 + tolerance), .I[1], roll = -Inf]$V1
  end   = dt[J(2 - tolerance), .I[.N], roll = Inf]$V1
 if (start <= end) dt[start:end]}
 microbenchmark(f1(),f2(),f3(),f4(), times=10)
# Unit: milliseconds
#  expr      min        lq    median        uq       max neval
#  f1() 373.3313 391.07479 440.07025 488.54020 491.48141    10
#  f2() 523.2319 530.11218 533.57844 536.67767 629.53779    10
#  f3()  65.6238  65.71617  66.09967  66.56768  83.27646    10
#  f4()  65.8511  66.26432  66.62096  83.86476  87.01092    10

Eddi 的方法稍微安全一些,因为它考虑了浮点容差。

【讨论】:

    【解决方案2】:

    如果你有一个键集,那么你的数据就会被排序,所以只需找到端点并取它们之间的点:

    setkey(dt, a)
    tolerance = 1e-7  # adjust according to your needs
    start = dt[J(1 + tolerance), .I[1], roll = -Inf]$V1
    end   = dt[J(2 - tolerance), .I[.N], roll = Inf]$V1
    if (start <= end) dt[start:end]
    

    这将比 Arun 的 floor 方法慢一点,因为它执行 2 个连接,但从好的方面来说,您可以插入任何您喜欢的数字。

    【讨论】:

    • 鉴于滚动连接只会产生一个返回值,您可以避免使用 [1] [.N] 子集。
    • 我确信roll 迟早会推出 :)
    • @mnel 其实不是这样,试试data.table(a = c(1,1,1), key = 'a')[J(1), roll = Inf]
    • 啊,是的。好点子。 (我更喜欢自己的一致性,以便所有由 roll 定义的“匹配”的记录(即 roll = -1 等),这将避免这两个调用。
    【解决方案3】:

    在这里执行setkey 会很昂贵(即使您要在1.8.11 中使用快速排序),因为它还必须移动数据(通过引用)。

    但是,您可以使用floor 函数来解决此问题。基本上,如果您想要 [1,2] 中的所有数字(注意:此处包括 1 和 2),那么 floor 将为所有这些值提供值“1”。也就是说,你可以这样做:

    system.time(t1 <- dt[floor(a) == 1])
    #   user  system elapsed 
    #  0.234   0.001   0.238 
    

    这相当于做dt[a &gt;= 1 &amp; a &lt;=2],速度是原来的两倍。

    system.time(t2 <- dt[a >= 1 & a <= 2])
    #   user  system elapsed 
    #  0.518   0.081   0.601 
    
    identical(t1,t2) # [1] TRUE
    

    但是,由于您不希望相等,您可以使用 hack 从列 a 中减去容差 = .Machine$double.eps^0.5。如果值在[1, 1+tolerance) 范围内,那么它仍然被认为是 1。如果它只是更多,那么它不再是 1(内部)。也就是说,它是机器可以识别为非 1 的最小数字 > 1。因此,如果您通过容差减去“a”,则所有内部表示为“1”的数字将变为 floor(.) 将导致 0 . 所以,你会得到 > 1 和

    dt[floor(a-.Machine$double.eps^0.5)==1]
    

    将给出与dt[a&gt;1 &amp; a&lt;2] 相同的结果。


    如果您必须重复执行此操作,那么可能使用此 floor 函数创建一个新列并在该 integer 列上设置键可能会有所帮助:

    dt[, fa := as.integer(floor(a-.Machine$double.eps^0.5))]
    system.time(setkey(dt, fa)) # v1.8.11
    #   user  system elapsed 
    #  0.852   0.158   1.043 
    

    现在,您可以使用二进制搜索查询您想要的任何范围:

    > system.time(dt[J(1L)])    # equivalent to > 1 & < 2
    #   user  system elapsed 
    #  0.071   0.002   0.076 
    > system.time(dt[J(1:4)])   # equivalent to > 1 & < 5
    #   user  system elapsed 
    #  0.082   0.002   0.085 
    

    【讨论】:

      【解决方案4】:

      我不是 data.table 专家,但据我了解,key 搜索 setkey(dt, b) ; dt['a'] 如此之快的原因是因为它使用二进制搜索而不是矢量扫描。对于子集需要二元运算符的数字列,这是不可能的。

      唯一的选择是做类似的事情:

      dt[,Between:=ifelse(a > 1 & a < 2, 'yes', 'no')]
      setkey(dt, Between)
      > system.time(dt['yes'])
         user  system elapsed 
         0.04    0.00    0.03 
      

      有趣的是,它甚至比:

      Index = dt[,a > 1 & a < 2]
      > system.time(dt[Index])
         user  system elapsed 
         0.23    0.00    0.23 
      

      但由于无论如何您都可以将子集保存为单独的 data.table,因此我认为这没有太多应用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-16
        • 2013-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-21
        相关资源
        最近更新 更多