【问题标题】:Writing efficient functions for data.tables that replace by reference为通过引用替换的 data.tables 编写高效的函数
【发布时间】:2016-08-04 22:41:45
【问题描述】:

我有一个 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 时出错。向函数添加子句以检查 is.data.table == TRUE 是否没有解决问题(返回相同的错误)。

任何关于最适合使用 data.table 方法来处理此功能的想法将不胜感激。

【问题讨论】:

  • 不确定是否复制,但这是一段很好的代码。我不确定您在这里的意思:“我尝试将函数中的最后一个 = 更改为 :=。”您想更改哪个=
  • 我试过了:recode.multi &lt;- function(datacols, oldval, newval) { for (i in 1:length(datacols)) { datacols[datacols == oldval[i]] := newval[i] } datacols }

标签: r function replace data.table multiple-columns


【解决方案1】:

我愿意……

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]

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

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

【讨论】:

  • 谢谢 - 没想过使用键控查找表方法。我避免将字符向量转换为因子,因为如果添加额外级别会遇到困难,但可以看到这对于这种情况如何有效。
【解决方案2】:

这类似于弗兰克的,但它允许将参数传递给构建翻译向量并返回翻译的函数。由于 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

请注意,您的列实际上并不是您从其中一个 cmets 想到的因素。如果您构建了一个 data.frame 作为中间步骤,他们可能已经这样做了。

【讨论】:

  • 有趣的是,这些列在 LHS := RHS 的 RHS 内命名了元素,但在赋值之后没有。我的意思是dt[, str(lapply(.SD, recode_dt, oldval = c("u", "n", "y"), newval = c(NA_real_, 0, 1))), .SDcols = bincols]
  • 这与 data.frame 的行为并没有太大的不同。名称通常会被丢弃,除了第一列的名称然后变成行名。
  • +1 用于 setNames,我在想可能有一种方法可以使用“set”。我也喜欢它自动将更新的向量从字符转换为数字。
  • 它将返回一个与 newval 相同类的向量。弗兰克有一个很好的观点,即提供整数可能会更好。 setNames 也不是 data.table 函数,尽管它的前三个字母。
【解决方案3】:

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

@Amy M:332.82 秒

@Frank:0.15 秒

@42(原始):4.13 秒

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

Frank 和 42 的解决方案都比我的快得多(所以我的肯定是抄袭)。

我在下面转载了最快的解决方案(弗兰克修改后的42):

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

【讨论】:

  • 这是一个有趣的结果。也许如果你把-42函数的最后一行换成trans[ match(datacol, names(trans)) ]会更快。我猜不同值的数量和每个不同值在数据中重复的次数可能很重要。
  • 或者只是将整个函数重写为newvals[ match(datacol, oldvals) ]
  • 我无法让您的第二个建议起作用,但是修改 42 函数的最后一行确实会提高速度:经过的时间是 0.05 秒。
猜你喜欢
  • 1970-01-01
  • 2020-06-24
  • 2020-05-08
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 1970-01-01
  • 2018-08-12
  • 1970-01-01
相关资源
最近更新 更多