为 data.tables 编写通过引用替换的高效函数

Writing efficient functions for data.tables that replace by reference

我有一个 data.table,其中包含多个二进制列,这些列具有相同的值,我想在一次操作中重新编码。我已经修改了一个最初为 data.frames 编写的函数,但我不确定我是否真的以我修改它的方式利用了 data.table 的速度:具体来说,我怀疑该函数可能仍然是复制值。

如何确保函数通过引用替换值?

这是一个玩具数据集:

# Example data:
id <- c(1,2,3,4,5)
fruit <- c("apple", "orange", "banana", "strawbery", "rasberry")
mydate <- c("2015-09-01", "2015-09-02", "2015-11-15", "2016-02-24", "2016-03-08")
eaten <- c("y", "y", "n", "y", "u")
present <- c("n", "n", "y", "y", "y")

dt <- data.table(id, fruit, mydate, eaten, present)
dt[, mydate := as.Date(mydate, format = "%Y-%m-%d")]
dt[, sex := c("m", "f", "f", "m", "f")]

# Columns to update:
bincols <- c("eaten", "present")

重新编码之前,数据如下所示:

> dt
   id     fruit     mydate eaten present sex
1:  1     apple 2015-09-01     y       n   m
2:  2    orange 2015-09-02     y       n   f
3:  3    banana 2015-11-15     n       y   f
4:  4 strawbery 2016-02-24     y       y   m
5:  5  rasberry 2016-03-08     u       y   f

函数如下:

recode.multi <- function(datacols, oldval, newval) {
  for (i in 1:length(datacols)) {
    datacols[datacols == oldval[i]] = newval[i]
  }
  datacols
}

...应用于数据:

dt[, (bincols) := lapply(.SD, recode.multi, oldval = c("u", "n", "y"), newval = c(NA_real_, 0, 1)), .SDcols = bincols]

...和输出,它根据需要更新值但不确定在此过程中是否正在复制列?

> dt
   id     fruit     mydate eaten present sex
1:  1     apple 2015-09-01     1       0   m
2:  2    orange 2015-09-02     1       0   f
3:  3    banana 2015-11-15     0       1   f
4:  4 strawbery 2016-02-24     1       1   m
5:  5  rasberry 2016-03-08    NA       1   f

我尝试将函数中的最后一个“=”更改为“:=”,但在检查 'datacols' 是否为 data.table 时出现错误。在函数中添加一个子句来检查是否是。data.table == TRUE 没有解决问题(返回相同的错误)。

任何关于实现此功能的最data.table 适当方法的想法将不胜感激。

我会做...

recodeDT = data.table(old = c("u", "n", "y"), new = c(NA_integer_, 0L, 1L), key = "old")

dt[, (bincols) := lapply(.SD, function(x) recodeDT[.(x), new]), .SDcols = bincols]

为了清楚起见,我认为最好将任何有限重映射存储在 table 中,但我不知道这样是否更有效率。如果您将变量存储为因子,您可以简单地调整水平,这应该非常快。你可以使用 setattr(x, "levels", z),也许。

旁注:您可能希望将它们编码为整数而不是浮点数。

这与 Frank 的类似,但将参数传递给构建翻译向量和 returns 翻译的函数。您不需要在函数内部进行循环,因为 lapply:= 和 .SDcols 函数正在 [.data.table.

内部进行循环
recode_dt <- function(datacol, oldval, newval) 
    { trans <- setNames(newval, oldval)
     trans[ datacol ]   }

dt[, (bincols) := lapply(.SD, recode_dt, oldval = c("u", "n", "y"), 
                                         newval = c(NA_real_, 0, 1)), 
     .SDcols = bincols]
dt
#===============
   id     fruit     mydate eaten present sex
1:  1     apple 2015-09-01     1       0   m
2:  2    orange 2015-09-02     1       0   f
3:  3    banana 2015-11-15     0       1   f
4:  4 strawbery 2016-02-24     1       1   m
5:  5  rasberry 2016-03-08    NA       1   f

请注意,您的列实际上并不是您从其中一条评论中想到的那样的因素。如果您构建了一个 data.frame 作为中间步骤,它们可能已经存在。

为了确定我的原始解决方案是否在复制值,我将我的、Frank 和 42 的解决方案应用于我的真实数据集,该数据集有 8933 个观察值和 150 列要使用函数更新。以下 system.time 的结果:

@Amy M: 332.82 秒

@Frank: 0.15 秒

@42(原版):4.13秒

@42(经过@Frank 的修改):0.05 秒

Frank 和 42 的解决方案都比我的快得多(所以我的必须是复制)。

我转载了下面最快的解法(Frank修改后的42):

recode.multi <- function(datacol, oldval, newval) {
  trans <- setNames(newval, oldval)
  trans[ match(datacol, names(trans)) ]
}