【问题标题】:Fastest way of finding matching rows查找匹配行的最快方法
【发布时间】:2015-09-06 11:28:57
【问题描述】:

我想知道在xts 对象中找到与某一特定行相同的所有行的最快方法是什么

library(xts)

nRows <- 3

coreData <- data.frame(a=rnorm(nRows), b=rnorm(nRows), c=rnorm(nRows))

testXts1 <- xts(coreData, order.by=as.Date(1:nRows))
testXts2 <- xts(coreData, order.by=as.Date((nRows + 1):(2*nRows)))
testXts3 <- xts(coreData, order.by=as.Date((2*nRows + 1):(3*nRows)))

testXts <- rbind(testXts1, testXts2, testXts3)

> testXts
                    a         b         c
1970-01-02 -0.3288756  1.441799  1.321608
1970-01-03 -0.7105016  1.639239 -2.056861
1970-01-04  0.1138675 -1.782825 -1.081799
1970-01-05 -0.3288756  1.441799  1.321608
1970-01-06 -0.7105016  1.639239 -2.056861
1970-01-07  0.1138675 -1.782825 -1.081799
1970-01-08 -0.3288756  1.441799  1.321608
1970-01-09 -0.7105016  1.639239 -2.056861
1970-01-10  0.1138675 -1.782825 -1.081799

rowToSearch <- first(testXts)

> rowToSearch
                    a        b        c
1970-01-02 -0.3288756 1.441799 1.321608

indicesOfMatchingRows <- unlist(apply(testXts, 1, function(row)  lapply(1:NCOL(row), function(i) row[i] == coredata(rowToSearch[, i]))))

testXts[indicesOfMatchingRows, ]

                    a         b         c
1970-01-02 -0.3288756  1.441799  1.321608
1970-01-05 -0.3288756  1.441799  1.321608
1970-01-08 -0.3288756  1.441799  1.321608

我相信这可以以更优雅、更快捷的方式完成。

一个更普遍的问题是你在 R 中怎么说“我有这个行矩阵 [5, ] 我如何在矩阵中找到与矩阵 [5, ] 相同的其他行的(索引)”。

如何在data.table中做到这一点?

【问题讨论】:

    标签: r data.table xts


    【解决方案1】:

    既然你说速度是你最关心的问题,你甚至可以通过使用 Rcpp 的 data.table 解决方案获得加速:

    library(Rcpp)
    cppFunction(
    "LogicalVector compareToRow(NumericMatrix x, NumericVector y) {
      const int nr = x.nrow();
      const int nc = x.ncol();
      LogicalVector ret(nr, true);
      for (int j=0; j < nr; ++j) {
        for (int k=0; k < nc; ++k) {
          if (x(j, k) != y[k]) {
            ret[j] = false;
            break;
          }
        }
      }
      return ret;
    }")
    testXts[compareToRow(testXts, rowToSearch),]
    #                   a         b         c
    # 1970-01-02 1.324457 0.8485654 -1.464764
    # 1970-01-05 1.324457 0.8485654 -1.464764
    # 1970-01-08 1.324457 0.8485654 -1.464764
    

    这是一个相当大的实例(有 100 万行)的比较:

    set.seed(144)
    bigXts <- testXts[sample(nrow(testXts), 1000000, replace=TRUE),]
    testDT <- as.data.frame(bigXts)
    
    josilber <- function(x, y) x[compareToRow(x, y),]
    roland.base <- function(x, y) x[colSums(t(x) != as.vector(y)) == 0L,]
    library(data.table)
    roland.dt <- function(testDT, y) {
      setDT(testDT, keep.rownames=TRUE)
      setkey(testDT, a, b, c)
      testDT[setDT(as.data.frame(y))]
    }
    library(microbenchmark)
    microbenchmark(josilber(bigXts, rowToSearch), roland.base(bigXts, rowToSearch), roland.dt(testDT, rowToSearch), times=10)
    # Unit: milliseconds
    #                              expr         min         lq       mean     median         uq       max
    #     josilber(bigXts, rowToSearch)    7.830986   10.24748   45.64805   14.41775   17.37049  258.4404
    #  roland.base(bigXts, rowToSearch) 3530.042324 3964.72314 4288.05758 4179.64233 4534.21407 5400.5619
    #    roland.dt(testDT, rowToSearch)   32.826285   34.95014  102.52362   57.30213  130.51053  267.2249
    

    该基准测试假定对象在调用roland.dt 之前已转换为数据帧(大约4 秒开销),并且compareToRows 在调用josilber 之前已经编译(大约3 秒开销)。 Rcpp 解决方案比基本 R 解决方案快约 300 倍,在中位运行时间中比 data.table 解决方案快约 4 倍。基于digest 的方法没有竞争力,每次执行时间超过 60 秒。

    【讨论】:

    • 使用摘要,您只需创建一次哈希表,因此如果您需要多次搜索,它可能会有所回报
    • 我认为这个基准测试对于roland.dt 来说太快了,因为testDT 正在通过引用 传递给roland.dt,在此期间排序第一次审判,这将使随后的审判更快。你可以在这里试试:a &lt;- data.table(x=10:1); a; foo &lt;- function(x){setkey(x); x[data.table(x=1:5)]}; b&lt;-foo(a); a 或阅读here
    • 差别不大,但在更“诚实”的一次性data.table 解决方案(下面的解决方案)上,我无法获得比基准的两倍更好的结果
    【解决方案2】:

    这是一个更快的基础 R 解决方案:

    ind <- colSums(t(testXts) != as.vector(rowToSearch)) == 0L
    testXts[ind,]
    

    这是一个使用 data.table 连接的解决方案:

    library(data.table)
    testDT <- as.data.frame(testXts)
    setDT(testDT, keep.rownames=TRUE)
    setkey(testDT, a, b, c)
    testDT[setDT(as.data.frame(rowToSearch))]
    

    但是,当comparing floating point numbers 时,我会保持警惕。

    【讨论】:

      【解决方案3】:

      这不使用data.table,但可能非常快。您可以通过散列行来做到这一点,

      library(digest)
      hash <- apply(testXts, 1, digest)
      testXts[which(hash[1] == hash)]
      
      #                    a          b          c
      # 1970-01-02 0.8466816 -0.7129076 -0.5742323
      # 1970-01-05 0.8466816 -0.7129076 -0.5742323
      # 1970-01-08 0.8466816 -0.7129076 -0.5742323
      

      【讨论】:

      • 您能否更新您的解决方案,使其使用提供的rowToSearch,而不是假设它正在搜索矩阵中的第一行?
      • @josilber 我无法弄清楚那部分,请随意编辑
      • 由于 OP 正在寻找效率,我会寻找一种更快的方法来生成密钥。 do.call(paste, as.data.frame(testXts)) 将所有数字粘贴在一起,看起来更快(在我的基准测试中使用预先计算的 testDT 时,我可以使其与基本 R 解决方案竞争)。
      【解决方案4】:

      最简单的data.table解决方案大概是:

      merge(as.data.table(testXts), as.data.table(rowToSearch, keep.rownames=FALSE))
      

      返回:

                a          b         c      index
      1: 1.685138 -0.3039018 -1.550871 1970-01-02
      2: 1.685138 -0.3039018 -1.550871 1970-01-05
      3: 1.685138 -0.3039018 -1.550871 1970-01-08
      

      为什么会这样:

      merge = 如果没有另外指定,则在公共列上进行内部连接。此内连接只返回与 rowToSearch 具有相同 (a, b, c) 值的列。

      右侧的keep.rownames=FALSE 确保删除rowToSearch 的日期索引(这是不想要的),并且不会进入用于加入的公共列。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-17
        • 2018-07-26
        相关资源
        最近更新 更多