【问题标题】:get(columnname) vs [[columnname]] in data.tabledata.table 中的 get(columnname) 与 [[columnname]]
【发布时间】:2018-03-18 09:22:21
【问题描述】:

在许多情况下,当我需要使用变量中传递的名称来寻址列时,我会看到以下两个选项:myDT[[myCol]]myDT[,get(myCol)],例如:

# get() ####
cast_num_get <- function(inpDT, cols2cast){
  for (thisCol in cols2cast){
    inpDT[, (thisCol):=as.numeric(get(thisCol))]
  }
  return(inpDT);
}

# [[ #### 
cast_num_b <- function(inpDT, cols2cast){
  for (thisCol in cols2cast){
    inpDT[[thisCol]] <- inpDT[[thisCol]]
  }
  return(inpDT);
}


# two more options added from the comments: 

# lapply(.SD) ####
cast_num_apply <- function(inpDT, cols2cast){
  inpDT[, (cols2cast) := lapply(.SD, as.numeric), .SDcols = cols2cast]
  return(inpDT);
}

# set() ####
cast_num_for_set <- function(inpDT, cols2cast){
  for (thisCol in cols2cast){
    set(inpDT, j = thisCol, value = as.numeric(inpDT[[thisCol]]))
  }
  return(inpDT);
}

【问题讨论】:

  • 在这个例子中,inpDT[, (cols2cast) := lapply(.SD, as.numeric), .Sdcols = cols2cast] imo 会比你展示的两个选项中的任何一个都好。
  • 另外,如果您已经使用 for 循环,请使用 set 而不是 `[.data.table`。除了:= 是通过引用分配的,而[[ 是原始的并且非常轻量级(尽管可能会复制 - 至少是一个浅的)。 get 通常效率很低,在 data.table 环境中工作时通常有更好的选择。
  • 您没有显示任何基准,但我怀疑您的基准测试不正确。 [[ 应该比进入 [ 更快,即使只是因为它是一个函数调用而不是两个调用。
  • 你的cast_num_b-option 有一个不公平的优势:它什么也没做。而不是inpDT[[thisCol]] &lt;- inpDT[[thisCol]],您应该在该函数中使用inpDT[[thisCol]] &lt;- as.numeric(inpDT[[thisCol]])。还添加了一个答案,比较了不同的方法和一些解释。

标签: r data.table


【解决方案1】:

对于这个例子,我会使用:

DT[, (cols) := lapply(.SD, as.numeric), .SDcols = cols]

两个替代方案(基于my answer here)和for

# alternative 1 with 'set'
for (col in cols) set(DT, j = col, value = as.numeric(DT[[col]]))

# alternative 2 with ':='
for (col in cols) DT[, (col) := as.numeric(DT[[col]])]

这三种方法都不一定更好。它们都具有相同的优势:它们将通过引用更新DT

将不同方法与基准进行比较:

microbenchmark(vasily_get = {inpDT <- copy(DT); cast_num_get(inpDT, cols)},
               vasily_b = {inpDT <- copy(DT); inpDT <- cast_num_b(inpDT, cols)},
               jaap_lapply = {inpDT <- copy(DT); inpDT[, (cols) := lapply(.SD, as.numeric), .SDcols = cols]},
               jaap_for_set1 = {inpDT <- copy(DT); for (col in cols) set(inpDT, j = col, value = as.numeric(inpDT[[col]]))},
               jaap_for_set2 = {inpDT <- copy(DT); for (col in cols) inpDT[, (col) := as.numeric(inpDT[[col]])]},
               times = 100)

给予:

Unit: milliseconds
          expr      min       lq     mean   median       uq      max
    vasily_get 399.0723 414.2708 530.3024 429.5070 663.3513 1194.827
      vasily_b 388.7294 408.0004 528.4039 418.9236 664.5881 1441.941
   jaap_lapply 401.8001 424.1902 562.9259 453.5073 668.3900 1376.654
 jaap_for_set1 399.2213 433.9918 568.7211 628.4220 668.1248 1198.950
 jaap_for_set2 395.1966 405.5584 510.2038 421.3801 652.1263 1097.931

这两种方法在速度方面都不是很突出。但是,cast_num_b 方法有一个很大的缺点:要使更改永久化,您必须将该函数的结果分配回输入 data.table

当你运行以下代码时:

inpDT <- copy(DT)
address(inpDT)
inpDT <- cast_num_b(inpDT, cols)
address(inpDT)

你得到:

> inpDT <- copy(DT)
> address(inpDT)
[1] "0x145eb6a00"
> inpDT <- cast_num_b(inpDT, cols)
> address(inpDT)
[1] "0x12a632ce8"

如您所见,计算机内存中的位置已更改。因此,它可以被认为是效率较低的方法。


使用过的数据:

DT <- data.table(lets = sample(LETTERS, 1e6, TRUE),
                 V1 = as.character(rnorm(1e6)),
                 V2 = as.character(rnorm(1e6)),
                 V3 = as.character(rnorm(1e6)),
                 V4 = as.character(rnorm(1e6)))

cols <- names(DT)[2:5]

【讨论】:

  • 我很想知道这与 nrows 和 ncols 有何不同。我尝试了n = 1e1; nc = 1e6; DT = as.data.table(matrix(sample(c("bah", "1.0"), n*nc, TRUE), n, nc); system.time(DT[, lapply(.SD, as.numeric)]),但没有耐心等待它完成。顺便说一句,Jan 开始制作一个基准小插曲 github.com/Rdatatable/data.table/blob/…,它提到了可能相关的 copy 和 times=100 的使用。
  • @Frank 谢谢。我同意使用times = 100 不是必需的,times = 10 一样好(并且会得出相同的结论)。虽然我同意使用copy 会增加开销,但我使用它来获得几种方法之间的公平基准。无论如何,会研究那个小插曲,但它非常密集 imo atm。
猜你喜欢
  • 1970-01-01
  • 2013-10-01
  • 1970-01-01
  • 2020-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-02
  • 1970-01-01
相关资源
最近更新 更多