【问题标题】:How to use data.table within functions and loops?如何在函数和循环中使用 data.table?
【发布时间】:2017-07-11 14:09:31
【问题描述】:

在评估data.table(与dplyr)的实用性时,一个关键因素是在函数和循环中使用它的能力。
为此,我修改了本文中使用的代码 sn-p:data.table vs dplyr: can one do something well the other can't or does poorly?,这样,它就变成了数据集,而不是硬编码的数据集变量名称(“钻石”数据集的“切割”和“价格”变量) -agnostic - cut-n-paste 准备好在任何函数或循环中使用(当我们事先不知道列名时)。

这是原始代码:

library(data.table)
dt <- data.table(ggplot2::diamonds)
dt[cut != "Fair", .(mean(price),.N), by = cut]  

这是它与数据集无关的等价物:

dt <- data.table(diamonds)
nVarGroup <- 2 #"cut"
nVarMeans <- 7 #"price"

strGroupConditions <- levels(dt[[nVarGroup]])[-1] # "Good" "Very Good" "Premium" "Ideal" 
strVarGroup <- names(dt)[nVarGroup]
strVarMeans <- names(dt)[nVarMeans]
qAction <- quote(mean(get(strVarMeans))) #! w/o get() it does not work! 
qGroup <- quote(get(strVarGroup) %in% strGroupConditions) #! w/o get() it does not work! 
dt[eval(qGroup), .(eval(qAction), .N), by = strVarGroup]

注意(感谢下方回复):如果需要通过引用改变变量值,需要使用(),而不是get(),如下图:

strVarToBeReplaced <- names(dt)[1]
dt[eval(qGroup), (strVarToBeReplaced) := eval(qAction), by = strGroup][] 

现在:您可以剪切并粘贴以下代码以满足您的所有循环需求:

for(nVarGroup in 2:4)       # Grouped by several categorical values...
  for(nVarMeans in 5:10) {  # ... get means of all numerical parameters
    strGroupConditions <- levels(dt[[nVarGroup]])[-1] 
    strVarGroup <- names(dt)[nVarGroup]
    strVarMeans <- names(dt)[nVarMeans]
    qAction  <- quote(mean(get(strVarMeans))) 
    qGroup <- quote(get(strVarGroup) %in% strGroupConditions) 
    p <- dt[eval(qGroup), .(AVE=eval(qAction), COUNT=.N), by = strVarGroup]

    print(sprintf("nVaGroup=%s, nVarMeans=%s: ", strVarGroup, strVarMeans))
    print(p)
  }

我的第一个问题:
上面的代码虽然实现了所需的功能/循环需求,但看起来非常复杂。 - 它使用不同的多个(可能不一致的)非直观技巧,例如()get()quote()/eval()[[]] 的组合。对于如此直接的需求来说似乎太多了......

还有其他更好的方法来访问和修改循环中的 data.tables 值吗? 或许使用on=lapply/.SD/.SDcols

请在下面分享您的想法。本次讨论旨在补充和巩固其他帖子中的相关内容(例如此处列出的:How can one work fully generically in data.table in R with column names in variables)。最终,最好创建一个专用的小插图,以便在 functionsloops 中使用 data.table

第二个问题:
dplyr 是否更容易实现此目的? - 但是,对于这个问题,我设置了一个单独的帖子:Is dplyr easier than data.table to be used within functions and loops?

【问题讨论】:

  • 如果您愿意放弃欺骗或错误的列名和/或先清理它们,我想这更容易。
  • 建议:发布一个新的 qn 比更改以前的 qn 更好,因为新的 qn 不会获得太多可见性。试试dt[eval(qGroup), (strVarToBeReplaced) := 999, by = strGroup][]
  • 我不明白你的问题。您的“与数据集无关”的代码比直接使用 data.table 更通用吗?
  • 您提到[[]]data.table 小插曲中没有得到很好的描述。但是,DT[["v"]] 包含在 ?data.table 的示例部分,“以 data.frame 方式选择列”,注释“与 DT[, v] 相同,但速度更快”。所以,这似乎是首选的方式
  • 问题不在于缺少文档(例如 [[]] 上的 .我们所有人都可以轻松地重用 stackoverflow.com 上提供的内容 - 只需简单的剪切粘贴即可,无需弄清楚如何用我们的变量替换“硬编码”变量名。想象一下 - 如果我们在示例中不使用“硬编码”名称变量,而是始终使用与数据集无关的代码,那么使用彼此的代码共同开发开源库会变得多么容易......干杯!

标签: r function loops data.table dplyr


【解决方案1】:

OP 已要求 与数据集无关的等效项 用于分组和聚合。

有了development version 1.10.5data.table 获得了新的分组集 函数:rollup()cube()groupingsets(),它们允许在不同的分组级别同时聚合生成小计和总计。

增加的抽象级别可用于数据集无关方法。在 OP 的示例中使用双嵌套 for 循环计算的小计也可以由

生成
library(data.table) # version 1.10.5 required
dt = data.table(ggplot2::diamonds)
groupingsets(dt, c(lapply(.SD, mean), list(COUNT = .N)), 
     by = names(dt)[2:4], .SDcols = 5:10, id = FALSE,
     sets = as.list(names(dt)[2:4]))
          cut color clarity    depth    table    price        x        y        z COUNT
 1:     Ideal    NA      NA 61.70940 55.95167 3457.542 5.507451 5.520080 3.401448 21551
 2:   Premium    NA      NA 61.26467 58.74610 4584.258 5.973887 5.944879 3.647124 13791
 3:      Good    NA      NA 62.36588 58.69464 3928.864 5.838785 5.850744 3.639507  4906
 4: Very Good    NA      NA 61.81828 57.95615 3981.760 5.740696 5.770026 3.559801 12082
 5:      Fair    NA      NA 64.04168 59.05379 4358.758 6.246894 6.182652 3.982770  1610
 6:        NA     E      NA 61.66209 57.49120 3076.752 5.411580 5.419029 3.340689  9797
 7:        NA     I      NA 61.84639 57.57728 5091.875 6.222826 6.222730 3.845411  5422
 8:        NA     J      NA 61.88722 57.81239 5323.818 6.519338 6.518105 4.033251  2808
 9:        NA     H      NA 61.83685 57.51781 4486.669 5.983335 5.984815 3.695965  8304
10:        NA     F      NA 61.69458 57.43354 3724.886 5.614961 5.619456 3.464446  9542
11:        NA     G      NA 61.75711 57.28863 3999.136 5.677543 5.680192 3.505021 11292
12:        NA     D      NA 61.69813 57.40459 3169.954 5.417051 5.421128 3.342827  6775
13:        NA    NA     SI2 61.77217 57.92718 5063.029 6.401370 6.397826 3.948478  9194
14:        NA    NA     SI1 61.85304 57.66254 3996.001 5.888383 5.888256 3.639845 13065
15:        NA    NA     VS1 61.66746 57.31515 3839.455 5.572178 5.581828 3.441007  8171
16:        NA    NA     VS2 61.72442 57.41740 3924.989 5.657709 5.658859 3.491478 12258
17:        NA    NA    VVS2 61.66378 57.02499 3283.737 5.218454 5.232118 3.221465  5066
18:        NA    NA    VVS1 61.62465 56.88446 2523.115 4.960364 4.975075 3.061294  3655
19:        NA    NA      I1 62.73428 58.30378 3924.169 6.761093 6.709379 4.207908   741
20:        NA    NA      IF 61.51061 56.50721 2864.839 4.968402 4.989827 3.061659  1790

因此,我们不必知道列的名称。但是,我们必须指定要分组的列以及要聚合的列。

【讨论】:

    【解决方案2】:

    这可能不是最类似于data.table 或最快的解决方案,但我会简化此特定循环中的代码,如下所示:

    for(nVarGroup in 2:4) {      # Grouped by several categorical values...
      for(nVarMeans in 5:10) {  # ... get means of all numerical parameters
        strGroupConditions <- levels(dt[[nVarGroup]])[-1] 
        strVarGroup <- names(dt)[nVarGroup]
        strVarMeans <- names(dt)[nVarMeans]
        # qAction <- quote(mean(get(strVarMeans)))
        # qGroup <- quote(get(strVarGroup) %in% strGroupConditions)
        # p <- dt[eval(qGroup), .(AVE = eval(qAction), COUNT = .N), by = strVarGroup]
        setkeyv(dt, strVarGroup)
        p <- dt[strGroupConditions, .(AVE = lapply(.SD, mean), COUNT = .N), by = strVarGroup, 
                .SDcols = strVarMeans]
    
        print(sprintf("nVaGroup = %s, nVarMeans = %s", strVarGroup, strVarMeans))
        print(p)
      }
    }
    

    我已将旧代码作为注释留作参考。

    qActionlapply(.SD, mean).SDcols 参数一起使用。

    用于子集行的qGroup 被设置键和提供所需值的向量作为i 参数的组合替换。


    如果是更复杂的子集表达式,我会尝试使用on= 语法使用非等(或条件)连接。

    或者,按照Matt Dowle's advice 创建要评估的一个 表达式,“类似于构造动态SQL 语句以发送到服务器”。

    Matt 建议创建一个辅助函数

    EVAL <- function(...) eval(parse(text = paste0(...)), envir = parent.frame(2))
    

    可以与gsubfn包中fn$的准perl类型字符串插值结合,以提高EVAL解决方案的可读性,如suggested by G. Grothendieck

    这样,循环的代码最终变成:

    EVAL <- function(...) eval(parse(text = paste0(...)), envir = parent.frame(2))
    library(gsubfn)
    
    for(nVarGroup in 2:4) {      # Grouped by several categorical values...
      for(nVarMeans in 5:10) {  # ... get means of all numerical parameters
        strGroupConditions = levels(dt[[nVarGroup]])[-1] 
        strVarGroup = names(dt)[nVarGroup]
        strVarMeans = names(dt)[nVarMeans]
        p <- fn$EVAL("dt[$strVarGroup %in% strGroupConditions, .(AVE=mean($strVarMeans), COUNT=.N), by = strVarGroup]" )
    
        print(sprintf("nVaGroup = %s, nVarMeans = %s", strVarGroup, strVarMeans))
        print(p)
      }
    }
    

    现在,data.table 语句看起来很像“本机”语句,只是在引用变量内容的地方使用了 $strVarGroup$strVarMeans


    在 1.1.0 版(CRAN 于 2016 年 8 月 19 日发布)中,stringr 包获得了字符串插值函数 str_interp(),这是此处 gsubfn 包的替代方案。

    使用str_interp(),for 循环中的中心语句将变为

    p <- EVAL(stringr::str_interp(
      "dt[${strVarGroup} %in% strGroupConditions, .(AVE=mean(${strVarMeans}), COUNT=.N), by = strVarGroup]"
      ))
    

    并且可以删除对library(gsubfn) 的调用。

    【讨论】:

      猜你喜欢
      • 2020-12-03
      • 2021-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 2020-11-19
      • 2013-12-28
      • 2020-10-11
      相关资源
      最近更新 更多