【问题标题】:Select columns of data.table based on regex根据正则表达式选择data.table的列
【发布时间】:2015-07-23 06:35:01
【问题描述】:

如何根据正则表达式选择 data.table 的列? 考虑一个简单的例子如下:

library(data.table)
mydt <- data.table(foo=c(1,2), bar=c(2,3), baz=c(3,4))

有没有办法使用基于正则表达式的数据表中的barbaz 列?我知道以下解决方案有效,但如果表更大并且我想选择更多变量,这很容易变得很麻烦。

mydt[, .(bar, baz)]

我想在dplyr::select() 中有类似matches() 的内容,但仅供参考。

【问题讨论】:

  • 你可以做mydt[, grep(c("bar|baz"), names(mydt)), with = FALSE],但我认为with会创建一个副本。
  • @DavidArenburg 不要认为它会创建副本。
  • @nicola 我记得 Arun 提到过几次,但从未真正检查过
  • @DavidArenburg,这将是非常奇怪的行为,不是吗?

标签: regex r data.table


【解决方案1】:

大卫的回答会奏效。但是,如果您的正则表达式很长并且您希望先完成它,请尝试:

cols <- grep("<regex pattern>", names(mydt), value=T)
mydt[, cols, with=FALSE]

这取决于您的偏好和需求。如果您需要完整的原始表,您还可以将子集表分配给选定的变量。

【讨论】:

    【解决方案2】:

    “data.table”还有一个subset 方法,因此您始终可以使用如下内容:

    subset(mydt, select = grep("bar|baz", names(mydt)))
    #    bar baz
    # 1:   2   3
    # 2:   3   4
    

    turns out 为“data.table”创建 startswith 类型的函数并不是很简单。

    【讨论】:

    • 嗯,我想知道为什么 OP 更喜欢这个而不是 mydt[, grep(c("bar|baz"), names(mydt)), with = FALSE]
    • @DavidArenburg,它稍微快一点?
    • 你测试了吗?会很有趣的发现。我一生中从未使用过subset,但如果这更快,我可能会给它一个机会。
    • @DavidArenburg, tracemem 不显示任何带有with 的副本。
    • 那为什么会比subset慢呢?
    【解决方案3】:

    你也可以尝试使用data.table包中的%like%,这是一个“调用regexpr的便利函数”。但是使代码更具可读性;)

    在这种情况下,回答您的问题:

    mydt[, .SD, .SDcols = names(mydt) %like% "bar|baz"]
    

    由于%like% 返回一个逻辑向量,因此可以使用以下内容获取除包含“foo”的列之外的每一列:

    mydt[, .SD, .SDcols = ! names(mydt) %like% "foo"]
    

    其中!否定逻辑向量。

    【讨论】:

      【解决方案4】:

      更新:我更新了与@sindri_baldur 答案的比较 - 使用版本1.12.6。根据结果​​,patterns() 是一种方便的快捷方式,但如果性能很重要,则应坚持使用..with = FALSE 解决方案(见下文)。


      显然,从 1.10.2 版本开始,有一种新方法可以实现这一点。

      library(data.table)
      cols <- grep("bar|baz", names(mydt), value = TRUE)
      mydt[, ..cols]
      

      它似乎是发布的解决方案中运行速度最快的。

      # Creating a large data.table with 100k rows, 32 columns
      n <- 100000
      foo_cols <- paste0("foo", 1:30)
      big_dt <- data.table(bar = rnorm(n), baz = rnorm(n))
      big_dt[, (foo_cols) := rnorm(n)]
      
      # Methods
      subsetting <- function(dt) {
          subset(dt, select = grep("bar|baz", names(dt)))
      }
      
      usingSD <- function(dt) {
          dt[, .SD, .SDcols = names(dt) %like% "bar|baz"]
      }
      
      usingWith <- function(dt) {
          cols <- grep("bar|baz", names(dt), value = TRUE)
          dt[, cols, with = FALSE]
      }
      
      usingDotDot <- function(dt) {
          cols <- grep("bar|baz", names(dt), value = TRUE)
          dt[, ..cols]
      }
      
      usingPatterns <- function(dt) {
        dt[, .SD, .SDcols = patterns("bar|baz")]
      }
      
      # Benchmark
      microbenchmark(
          subsetting(big_dt), usingSD(big_dt), usingWith(big_dt), usingDotDot(big_dt), usingPatterns(big_dt),
          times = 5000
      )
      
      #Unit: microseconds
      #                  expr  min   lq  mean median    uq    max neval
      #    subsetting(big_dt)  430  759  1672   1309  1563  82934  5000
      #       usingSD(big_dt)  547  951  1872   1461  1797  60357  5000
      #     usingWith(big_dt)  278  496  1331   1112  1304  62656  5000
      #   usingDotDot(big_dt)  289  483  1392   1117  1344  55878  5000
      # usingPatterns(big_dt)  596 1019  1984   1518  1913 120331  5000
      

      【讨论】:

      • 来自 data.table news:“当 j 是一个以 .. 为前缀的符号时,它将在调用范围内查找,其值被视为列名或数字。(.. .) 将所有操作系统中的目录.. 视为一级目录,这意味着父目录(...)它是实验性的。”
      • Fwiw,我认为如果速度对此很重要,那么代码一定有一些组织问题。在我的系统上一个糟糕但更快的方法:microbenchmark(bad = `[.noquote`(big_dt, names(big_dt) %like% "bar|baz"), good = big_dt[, .SD, .SDcols = names(big_dt) %like% "bar|baz"]) 类似 setDT(as.list(big_dt)[names(big_dt) %like% "bar|baz"])
      • 你在微基准测试中忘记了最后一个,usingPatterns :)
      • 感谢@emilBeBri,它只包含在结果中。我更新了代码。
      【解决方案5】:

      data.table v1.12.0(2019 年 1 月)以来,您可以:

      mydt[, .SD, .SDcols = patterns("bar|baz")]
      

      来自官方文档?data.table,关于.SDcols 参数:

      [...] 您可以根据它们的 names 过滤列以包含在.SD 中 通过.SDcols=patterns(regex1, regex2, ...) 表达。包含的 列将是每个标识的列的交集 图案;可以在正则表达式中使用 | 轻松指定模式联合。 [...] 你 也可以像往常一样使用.SDcols = !patterns(...) 反转模式。

      【讨论】:

      • 谢谢,这确实是data.table期待已久的功能。我用它更新了我的基准。它似乎工作得更慢,但在许多情况下,这根本不重要。
      【解决方案6】:

      为了可读性和性能,我建议使用这种单行代码。

      mydt[,names(mydt) %like% "bar|baz", with=F] 
      

      按照@Janosdivenji 的回答: 最后一行见usingLike

      Unit: microseconds
                        expr     min        lq     mean    median        uq       max neval
          subsetting(big_dt) 370.582  977.2760 1194.875 1016.4340 1096.9285  25750.94  5000
             usingSD(big_dt) 554.330 1084.8530 1352.039 1133.4575 1226.9060 189905.39  5000
           usingWith(big_dt) 238.481  832.7505 1017.051  866.6515  927.8460  22717.83  5000
         usingDotDot(big_dt) 256.005  844.8770 1101.543  878.9935  936.6040 181855.43  5000
       usingPatterns(big_dt) 569.787 1128.0970 1411.510 1178.2895 1282.2265 177415.23  5000
           usingLike(big_dt) 262.868  852.5805 1059.466  887.3455  948.6665  23971.70  5000
      

      【讨论】:

      • 这与usingWith 解决方案相同 - 关于这个问题,如果使用%like%grep() 选择cols 则无关紧要
      猜你喜欢
      • 2019-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-14
      • 2011-06-18
      • 2010-11-27
      • 1970-01-01
      • 2018-09-25
      相关资源
      最近更新 更多