在 data.table 中通过引用更好地替换?

Better replacement by reference in data.table?

有关,有没有更好的方法来完成下面显示的功能,具体使用data.table
注意:下面的所有代码都可以使用,但是......很慢。
(我使用简单的 "cleaning" 步骤只是为了演示问题)。

objective 是写一个函数 1) 高效 2) 替换 3) data.table 中的一些 值,以便它可以在循环中用于清理大量数据集。
在 C++ 中,这将使用指针和引用调用来完成,如下所示:

   void cleanDT(* dataTable dt); cleanDT(&dt222)

然而,在 R 中,我们每次调用函数时都会来回复制整个数据集 (data.tables)。

cleanDT <- function (dt) {
  strNames <- names(dt);   nCols <- 1:length(strNames)
  for (i in nCols) {
    strCol <- strNames[i]
    if ( class(dt[[strCol]]) == "numeric"  ) 
      dt[[strCol]] <- floor(dt[[strCol]])
    else 
      dt[[strCol]] <- gsub("I", "i", dt[[strCol]])
  }
  return(dt)
}
cleanDTByReference <- function (dt) {
  dtCleaned <- dt
  strNames <- names(dt);   nCols <- 1:length(strNames)
  for (i in nCols) {
    strCol = strNames[i]
    if ( class(dt[[strCol]]) == "numeric"  ) 
      dtCleaned[[strCol]] <- floor(dt[[strCol]])
    else 
      dtCleaned[[strCol]] <-  gsub("I", "i", dt[[strCol]]) 
  }
  eval.parent(substitute(dt <- dtCleaned))
}

dt222 <- data.table(ggplot2::diamonds); dt222[1:2]
dt222 <- cleanDT(dt222); dt222[1:2]

dt222 <- data.table(diamonds); dt222[1:2]
#   carat     cut color clarity depth table price    x    y    z
#1:  0.23   Ideal     E     SI2  61.5    55   326 3.95 3.98 2.43
#2:  0.21 Premium     E     SI1  59.8    61   326 3.89 3.84 2.31

cleanDTByReference(dt222); dt222[1:2]
#   carat     cut color clarity depth table price x y z
#1:     0   ideal     E     Si2    61    55   326 3 3 2
#2:     0 Premium     E     Si1    59    61   326 3 3 2

然后我们将使用此函数在循环中清理数据表列表,如下所示:

dt333 <- data.table(datasets::mtcars)
listDt <- list(dt222, dt333)

for(dt in listDt) {
  print(dt[1:2])
  cleanDTByReference(dt); print(dt[1:2])
}

理想情况下,因此,我希望使用一个函数以这种方式 "cleaned" 我所有的数据表。但目前在不使用引用的情况下,上面的代码实际上并没有改变 listDt,也没有改变 dt222dt333.
你能建议如何实现这一目标吗?

这是使用 data.table 的更好方法:

dt <- as.data.table(ggplot2::diamonds)
dt1 <- as.data.table(mtcars)

changeDT <- function(dt){
  cols <- names(dt)
  dt[, c(cols) := lapply(.SD, function(x) ifelse(sapply(x, is.numeric), 
                                                 floor(x), 
                                                 gsub("I", "i", x))),
     .SDcols = cols]
    }

    list1 <- list(dt, dt1)

    x <- lapply(list1, changeDT)

如果您遵循 data.table 语法,您可以通过引用 修改 data.table
(我强烈建议学习data.table vignettes and FAQ。)

函数定义:

change_DT_in_place <- function(DT){
  cat(address(DT), "\n")
  numcols <- DT[, which(sapply(.SD, is.numeric))]
  cat("num: ", numcols, "- ")
  if (length(numcols) > 0) {
    DT[, (numcols) := lapply(.SD, floor), .SDcols = numcols]
  }
  othcols <-  DT[, which(!sapply(.SD, is.numeric))]
  cat("other: ", othcols, "\n")
  if (length(othcols) > 0) {
    DT[, (othcols) := lapply(.SD, gsub, pattern = "I", replacement = "i"), 
       .SDcols = othcols]
  }
}

请注意,我添加了一些 cat() 语句来演示内部工作原理。 address() returns 变量在 RAM 中的地址。

要验证函数是否有效,必须证明 data.tables

  1. 已更改
  2. 但还没有被复制。

创建 data.tables:

library(data.table)
dt1 <- as.data.table(ggplot2::diamonds)
dt2 <- as.data.table(mtcars)

先修改data.table:

head(dt1)
#   carat       cut color clarity depth table price    x    y    z
#1:  0.23     Ideal     E     SI2  61.5    55   326 3.95 3.98 2.43
#2:  0.21   Premium     E     SI1  59.8    61   326 3.89 3.84 2.31
#3:  0.23      Good     E     VS1  56.9    65   327 4.05 4.07 2.31
#4:  0.29   Premium     I     VS2  62.4    58   334 4.20 4.23 2.63
#5:  0.31      Good     J     SI2  63.3    58   335 4.34 4.35 2.75
#6:  0.24 Very Good     J    VVS2  62.8    57   336 3.94 3.96 2.48
address(dt1)
#[1] "0000000015660EE0"
change_DT_in_place(dt1)
#0000000015660EE0 
#num:  1 5 6 7 8 9 10 - other:  2 3 4 
address(dt1)
#[1] "0000000015660EE0"
head(dt1)
#   carat       cut color clarity depth table price x y z
#1:     0     ideal     E     Si2    61    55   326 3 3 2
#2:     0   Premium     E     Si1    59    61   326 3 3 2
#3:     0      Good     E     VS1    56    65   327 4 4 2
#4:     0   Premium     i     VS2    62    58   334 4 4 2
#5:     0      Good     J     Si2    63    58   335 4 4 2
#6:     0 Very Good     J    VVS2    62    57   336 3 3 2

修改第二个data.table:

head(dt2)
#    mpg cyl disp  hp drat    wt  qsec vs am gear carb
#1: 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
#2: 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
#3: 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
#4: 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
#5: 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
#6: 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
address(dt2)
#[1] "0000000018C42E78"
change_DT_in_place(dt2)
#0000000018C42E78 
#num:  1 2 3 4 5 6 7 8 9 10 11 - other:   
address(dt2)
#[1] "0000000018C42E78"
head(dt2)
#   mpg cyl disp  hp drat wt qsec vs am gear carb
#1:  21   6  160 110    3  2   16  0  1    4    4
#2:  21   6  160 110    3  2   17  0  1    4    4
#3:  22   4  108  93    3  2   18  1  1    4    1
#4:  21   6  258 110    3  3   19  1  0    3    1
#5:  18   8  360 175    3  3   17  0  0    3    2
#6:  18   6  225 105    2  3   20  1  0    3    1

结论

在这两种情况下,函数都更改了 data.tables,从未更改的指针地址可以看出。