【问题标题】:Using := in data.table with paste()在 data.table 中使用 := 和 paste()
【发布时间】:2015-01-05 18:13:25
【问题描述】:

我已经开始将data.table 用于大型人口模型。到目前为止,我印象深刻,因为使用 data.table 结构将我的模拟运行时间减少了大约 30%。我正在尝试进一步优化我的代码并包含一个简化的示例。我的两个问题是:

  1. 是否可以在此代码中使用 := 运算符?
  2. 使用:= 运算符会更快吗(不过,如果我能够回答我的第一个问题,我应该能够回答我的问题 2!)?

我在运行 Windows 7 的机器上使用 R 版本 3.1.2,data.table 版本 1.9.4。

这是我的可重现示例:

library(data.table)

## Create  example table and set initial conditions
nYears = 10
exampleTable = data.table(Site = paste("Site", 1:3))
exampleTable[ , growthRate := c(1.1, 1.2, 1.3), ]
exampleTable[ , c(paste("popYears", 0:nYears, sep = "")) := 0, ]

exampleTable[ , "popYears0" := c(10, 12, 13)] # set the initial population size

for(yearIndex in 0:(nYears - 1)){
    exampleTable[[paste("popYears", yearIndex + 1, sep = "")]] <- 
    exampleTable[[paste("popYears", yearIndex, sep = "")]] * 
    exampleTable[, growthRate]
}

我正在尝试做类似的事情:

for(yearIndex in 0:(nYears - 1)){
    exampleTable[ , paste("popYears", yearIndex + 1, sep = "") := 
    paste("popYears", yearIndex, sep = "") * growthRate, ] 
}

但是,这不起作用,因为粘贴不适用于data.table,例如:

exampleTable[ , paste("popYears", yearIndex + 1, sep = "")]
# [1] "popYears10"

我查看了data.table documentation。 FAQ 的第 2.9 节使用cat,但这会产生空输出。

exampleTable[ , cat(paste("popYears", yearIndex + 1, sep = ""))]
# [1] popYears10NULL

另外,我尝试搜索 Google 和 rseek.org,但没有找到任何东西。如果缺少明显的搜索词,我将不胜感激搜索提示。我一直发现搜索 R 运算符很困难,因为搜索引擎不喜欢符号(例如,“:=”)并且“R”可能含糊不清。

【问题讨论】:

  • 对于刚开始的人来说还不错。问题也很好。
  • 您可能喜欢paste0() 函数,它是paste(..., sep = "") 的快捷方式。节省一点打字时间。
  • 看看set()。示例:for (i in 1:5) set(DT, i, 3L, i+1)for (i in 1:5) DT[i, z:= i+1] 快得多,其中3Lz 的位置。

标签: r data.table


【解决方案1】:
## Start with 1st three columns of example data
dt <- exampleTable[,1:3]

## Run for 1st five years
nYears <- 5
for(ii in seq_len(nYears)-1) {
    y0 <- as.symbol(paste0("popYears", ii))
    y1 <- paste0("popYears", ii+1)
    dt[, (y1) := eval(y0)*growthRate]
}

## Check that it worked
dt
#     Site growthRate popYears0 popYears1 popYears2 popYears3 popYears4 popYears5
#1: Site 1        1.1        10      11.0     12.10    13.310   14.6410  16.10510
#2: Site 2        1.2        12      14.4     17.28    20.736   24.8832  29.85984
#3: Site 3        1.3        13      16.9     21.97    28.561   37.1293  48.26809

编辑:

因为在 cmets 中不断出现使用 set() 加速此操作的可能性,所以我将把这个附加选项放在那里。

nYears <- 5

## Things that only need to be calculated once can be taken out of the loop
r <- dt[["growthRate"]]
yy <- paste0("popYears", seq_len(nYears+1)-1)

## A loop using set() and data.table's nice compact syntax
for(ii in seq_len(nYears)) {
    set(dt, , yy[ii+1], r*dt[[yy[ii]]])
}

## Check results
dt
#     Site growthRate popYears0 popYears1 popYears2 popYears3 popYears4 popYears5
#1: Site 1        1.1        10      11.0     12.10    13.310   14.6410  16.10510
#2: Site 2        1.2        12      14.4     17.28    20.736   24.8832  29.85984
#3: Site 3        1.3        13      16.9     21.97    28.561   37.1293  48.26809

【讨论】:

  • @RichardErickson,为什么要预先分配? := 中有一些不必要的副本,在开发版本 (1.9.5) 中是 fixed recently。也许值得一试。或者使用 CRAN 版本中的set()
  • @RichardErickson -- 如果您真的只是希望每一列都是前一列的固定倍数,我想这样的事情可能会更快:@987654327 @.
  • @JoshO'Brien。别客气。再次感谢你的帮助。我查看了set(),但决定不使用它,因为我不知道如何在其中使用paste。使用列号的解决方法是不可取的,因为列号很脆弱(我很难学会这一点......)。另外,我正在使用data.table,因为我正在使用大量但不是大量的数据进行大量计算。作为data.table 的额外好处,我也喜欢它的语法,一旦我掌握了它。
  • @RichardErickson,你正在执行一个完整的专栏。我真的怀疑列分配是否有帮助(因为 data.table 过度分配列指针,并通过引用添加列)。我能想到提高速度的两个地方是 1)使用 set() 可以避免 [.data.table 泛型方法开销(因为你似乎运行了 10000 次左右)和 2)在函数上使用 cmpFun()需要一个 for 循环和 set() 字节码编译它。如果你能提供一个真实场景/数据维度的例子,我很想看看它。
  • @Arun set()明显 缺点(或至少相对不便)是其j 参数中的表达式没有“直接”访问列在dt。是通过dt[["var"]] 访问这些列,然后是获取它们的最有效方法,就像我刚刚添加的示例代码一样?
【解决方案2】:

在列名上苦苦挣扎是一个强有力的指标,表明宽格式可能不是给定问题的最佳选择。因此,我建议以长格式进行计算,并最终将结果从长格式重塑为宽格式。

nYears = 10
params = data.table(Site = paste("Site", 1:3),
                    growthRate = c(1.1, 1.2, 1.3), 
                    pop = c(10, 12, 13))
long <- params[CJ(Site = Site, Year = 0:nYears), on = "Site"][
  , growth := cumprod(shift(growthRate, fill = 1)), by = Site][
    , pop := pop * growth][]
dcast(long, Site + growthRate ~ sprintf("popYears%02i", Year), value.var = "pop")
     Site growthRate popYears 0 popYears 1 popYears 2 popYears 3 popYears 4 popYears 5 popYears 6 popYears 7 popYears 8 popYears 9 popYears10
1: Site 1        1.1         10       11.0      12.10     13.310    14.6410   16.10510   17.71561   19.48717   21.43589   23.57948   25.93742
2: Site 2        1.2         12       14.4      17.28     20.736    24.8832   29.85984   35.83181   42.99817   51.59780   61.91736   74.30084
3: Site 3        1.3         13       16.9      21.97     28.561    37.1293   48.26809   62.74852   81.57307  106.04499  137.85849  179.21604

说明

首先,使用交叉连接函数CJ() 和随后在Site 上的右连接将参数扩展为涵盖11 年(包括第0 年):

params[CJ(Site = Site, Year = 0:nYears), on = "Site"]
       Site growthRate pop Year
 1: Site 1        1.1  10    0
 2: Site 1        1.1  10    1
 3: Site 1        1.1  10    2
 4: Site 1        1.1  10    3
 5: Site 1        1.1  10    4
 6: Site 1        1.1  10    5
 7: Site 1        1.1  10    6
 8: Site 1        1.1  10    7
 9: Site 1        1.1  10    8
10: Site 1        1.1  10    9
11: Site 1        1.1  10   10
12: Site 2        1.2  12    0
13: Site 2        1.2  12    1
14: Site 2        1.2  12    2
15: Site 2        1.2  12    3
16: Site 2        1.2  12    4
17: Site 2        1.2  12    5
18: Site 2        1.2  12    6
19: Site 2        1.2  12    7
20: Site 2        1.2  12    8
21: Site 2        1.2  12    9
22: Site 2        1.2  12   10
23: Site 3        1.3  13    0
24: Site 3        1.3  13    1
25: Site 3        1.3  13    2
26: Site 3        1.3  13    3
27: Site 3        1.3  13    4
28: Site 3        1.3  13    5
29: Site 3        1.3  13    6
30: Site 3        1.3  13    7
31: Site 3        1.3  13    8
32: Site 3        1.3  13    9
33: Site 3        1.3  13   10
      Site growthRate pop Year

然后使用累积乘积函数cumprod() 分别为每个Site 从移位的增长率计算增长。对于每个Site,班次需要跳过第一年。然后通过乘以初始人口来计算人口。

最后,使用dcast() 将data.table 从长格式改写为宽格式。列标题是使用sprintf()即时创建的,以确保列的正确顺序。

【讨论】:

  • 感谢您的回答,但我认为您的回答对我的问题没有帮助。在我的简约示例中,我将growthRate 设为线性运算符。但是,在完整的问题中,growthRate 是一个非线性算子,每个时间步都需要更新。因此,for 循环是必需的。此外,在接受的答案下的 cmets 中也注意到了这一点。
猜你喜欢
  • 2010-10-19
  • 2017-11-18
  • 2016-08-04
  • 1970-01-01
  • 1970-01-01
  • 2020-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多