【问题标题】:Faster way to subset data table instead of a for loop R更快的方法来子集数据表而不是 for 循环 R
【发布时间】:2016-06-07 17:05:15
【问题描述】:

我在 R 中有一个数据表(您需要安装数据表包),它使用 X 和 Y 坐标以及来自正态分布和均匀分布的随机数据值生成。坐标代表 2000x1600 阵列上的点,必须分成 16 个较小的“扇区”,每个 500x400。这些部门需要取它们的正态分布平均值,除以均匀分布值的 min^2。我还使用提供的函数 startstop 创建了两个变量 x 和 y,它们具有 16 个扇区的坐标和一个计算每个扇区的数字的函数。

library(data.table)
DT <- data.table(X = rep(1:2000, times = 1600), Y = rep(1:1600, each = 2000), Norm =rnorm(1600*2000), Unif = runif(1600*2000))

sectorCalc <- function(x,y,DT) {
    sector <- numeric(length = 16)
    for (i in 1:length(sector)) {
        sect <- DT[X %between% c(x[[1]][i],x[[2]][i]) & Y %between% c(y[[1]][i],y[[2]][i])]
        sector[i] <- sCalc(sect)
    }
    return(sector)
 }

startstop <- function(width, y = FALSE) {
    startend <- width - (width/4 - 1)
    start <- round(seq(0, startend, length.out = 4))
    stop <- round(seq(width/4, width, length.out = 4))
    if  (length(c(start,stop)[anyDuplicated(c(start,stop))]) != 0) {
        dup <- anyDuplicated(c(start,stop))
        stop[which(stop == c(start,stop)[dup])] <- stop[which(stop == c(start,stop)[dup])] - 1
}
    if (y == TRUE) {
        coord <- list(rep(start, each = 4), rep(stop, each = 4))
  } else if (y == FALSE) {
        coord <- list(rep(start, times = 4), rep(stop, times = 4))
  }
  return(coord)
}

x <- startstop(2000)
y <- startstop(1600, T)

sectorNos <- sectorCalc(x,y,DT)

startstop 函数并不是真正的问题,但我需要一种更快的方法来对数据表进行子集化。必须对“sectorCalc”函数进行一些修改。 for 循环是我能想到的最好方法,但我对数据表没有太多经验。关于更快地分解数据表的方法有什么想法吗?

【问题讨论】:

  • 这个问题描述得非常好(包括示例数据和代码)。只是(真的)一点希望改进这个问题:在使用随机数创建样本数据时,您应该包括 set.seed,以便可以通过不同的答案重现结果。
  • 这个功能我没用过。谢谢你的提示。 @RYoda
  • 请避免以后大幅更改问题,因为它会使以前的答案无效。你能解释一下,你的功能是做什么的(意图?)。它看起来为 Norm 最低值的 2%(为什么是 2%?)计算类似于方差或 stddev 的东西。谢谢:-)
  • 该函数只是一个分配的示例计算,它已提供。目的是成功地对数据表进行子集化并将函数应用于每个子集。我可以成功地做到这一点,我只是在寻找一种更快的方法来做到这一点。 @RYoda
  • 根据@RYoda 的评论,我建议将此问题恢复为您的原始问题,然后接受其中一个答案(如果它们对您有用)。然后,接受你的新要求并提出一个新问题(但链接回这个问题)。

标签: r for-loop indexing data.table subset


【解决方案1】:

不仅使用包data.table 还使用cut 函数来构建区间“组”的解决方案:

# Create your test data
library(data.table)

set.seed(123)      # make random numbers reproducible to allow comparison of different answers
DT <- data.table(X = rep(1:2000, times = 1600), Y = rep(1:1600, each = 2000), Norm =rnorm(1600*2000), Unif = runif(1600*2000))

# calculate the sector by cutting the x and y values into groups defined by the interval breaks
DT[, x.sect := cut(DT[, X], c(0, 499, 1000, 1500, 2000), dig.lab=10)] # Intervals should be: seq(0, 2000, by=500) lower bound is less one since it is not included in the interval (see help for cut function)
DT[, y.sect := cut(DT[, Y], c(0, 399, 800, 1200, 1600), dig.lab=10)] # Intervals should be: seq(0, 1600, by=400)

# Now calculate per group (calculation logic "stolen" from the working answer of user "Symbolix"
DT[, .(sect = mean(Norm)/min(Unif)^2), by=.(x.sect, y.sect)]

请注意:我认为第一个和第二个间隔的大小在原始解决方案中是错误的(x 是 499 而不是 500,y 是 399 而不是 400,所以我无法使用seq 函数重现您想要的间隔,但必须手动枚举间隔中断)。

编辑 1:我已将添加 x.sect 和 y.sect 列的原始代码替换为通过引用添加列的改进解决方案 (:=)。

编辑 2:如果您想订购结果,您有(至少)两个选项:

# "Chaining" (output is input of next)
DT[, .(sect = mean(Norm)/min(Unif)^2), by=.(x.sect, y.sect)][order(x.sect, y.sect),]
# Or: Use the "keyby" param instead of "by"
DT[, .(sect = mean(Norm)/min(Unif)^2), keyby=.(x.sect, y.sect)]

编辑 3:在上面的代码中将 dig.lab=10 参数添加到 cut 函数以避免科学记数法的间隔中断。

【讨论】:

  • 您知道在计算之前对每个sect 进行排序或使用setkey 的方法吗?
  • 哦,是的,使用data.tablekeyby 参数而不是by(请参阅帮助?data.table。由于计算是“一次性全表扫描”,所以我不这样做)不认为setkey 会提高性能,但您可以尝试一下。从keyby 上的帮助文档中:为方便起见,随后在结果的 by 列上运行额外的 setkey()。当您希望对结果进行排序时,通常使用 'keyby='。大声地我们将 keyby= 读为 by= 然后 setkey.. .
  • 对不起,可能是误会了您:您是要对结果进行特殊排序还是提高计算速度?
  • 不,出于统计目的,我需要按另一列排序。所以我需要按x.secty.sect 分组,但如果我想按“标准”排序。
  • 可以DT[, .(sect = mean(Norm)/min(Unif)^2), by=.(x.sect, y.sect)][order(Norm),]
【解决方案2】:

要替换您的sectorCalc 功能,我认为我们可以使用data.tables 连接

当您循环遍历sector 的每一行时,您只需创建一个data.table 即可加入您的sector 数据, 指定要加入的列(这里我使用key_col),并为每一行指定一个“组”变量,以使我们能够执行 最后的计算:

x <- startstop(2000)
y <- startstop(1600, T)
## copy the original DT
dt <- copy(DT)


dt_xy <- data.table(x_1 = x[[1]],
                    x_2 = x[[2]],
                    y_1 = y[[1]],
                    y_2 = y[[2]])


dt[, key_col := 1]
dt_xy[, `:=`(key_col = 1, xy_grp = seq(1,.N))]

## Use a data.table join, allowing cartesian, then filter out results.
dt_res <- dt[ dt_xy, on="key_col", allow.cartesian=T][x_1 <= X & X <= x_2 & y_1 <= Y & Y <= y_2]

## calculate 'sect' as required.
dt_sect <- dt_res[, .(sect = mean(Norm)/min(Unif)^2)   , by=.(xy_grp)]

【讨论】:

  • 在使用这种方法进行计算之前,是否可以setkey 每个“教派”?例如,在使用现有列或DT 中的第五个列创建dt_sect 时,是否有办法做到这一点?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-07
  • 1970-01-01
  • 2020-12-02
  • 1970-01-01
  • 2020-11-21
  • 1970-01-01
  • 2020-11-04
相关资源
最近更新 更多