使用RStorm计算多个data.table列的Welford方差

时间:2015-12-31 20:10:54

标签: r statistics data.table grouping lapply

鉴于以下data.table dt

    i a  b
 1: 1 1 NA
 2: 2 1 NA
 3: 2 2  2
 4: 3 1  1
 5: 3 2  2
 6: 3 3 NA
 7: 4 1 NA
 8: 4 2  2
 9: 4 3  3
10: 4 4 NA

我想使用Welford's Methoda包工具计算按b列分组的列iRStorm的运行差异。我按照page 4 of RStorm's vignette上的示例进行了操作并阅读了introductory paper on RStorm,但我无法弄清楚如何使其发挥作用。这是我的代码:

library(RStorm)
dt = data.table(i=c(1,2,2,3,3,3,4,4,4,4), a=c(1,1:2,1:3,1:4), b=c(NA,NA,2,1,2,NA,NA,2,3,NA)
in_cols = c('a','b')
out_cols <- paste0(in_cols, '.var.Welford')
## Calculaing variance using Welford's method
## See: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
## See: "RStorm: Developing and Testing Streaming Algorithms in R", R Journal Vol 6/1
var.Welford <- function(x, ...) {
    x <- as.numeric(x[1])
    params <- GetHash("params2")
    if (!is.data.frame(params)) {
        params <- list()
        params$M <- params$S <- params$n <- 0
    }
    x <- ifelse(is.na(x), params$M, x)
    n <- params$n + 1
    delta <- (x - params$M)
    M <- params$M + ( delta / (n + 1) )
    S <- params$S + delta*(x - M)
    SetHash("params2", data.frame(n=n,M=M,S=S))
    var <- ifelse(n > 1, S / (n-1), 0)
    TrackRow("var.Welford", data.frame(var = var))
}
computeVarWelford <- function(x) {
    topology <- Topology(as.data.frame(x=as.data.frame(x)))
    topology <- AddBolt(topology, Bolt(var.Welford, listen = 0))
    result <- RStorm(topology)
    # GetTrack('var.Welford', result)
    result$track$var.Welford
}

## Execute:
dt[, eval(out_cols) := lapply(.SD, function(x) {return(as.list(computeVarWelford(x))[1])})
, by=i, .SDcols = in_cols]

执行上述行会将dt转换为:

    i a  b                       a.var.Welford                       b.var.Welford
 1: 1 1 NA                                   0                                   0
 2: 2 1 NA                                 0,2                   0.000000,2.666667
 3: 2 2  2                                 0,2                   0.000000,2.666667
 4: 3 1  1                         0.0,2.0,2.5                               0,2,1
 5: 3 2  2                         0.0,2.0,2.5                               0,2,1
 6: 3 3 NA                         0.0,2.0,2.5                               0,2,1
 7: 4 1 NA 0.000000,2.000000,2.500000,3.333333 0.000000,2.666667,3.375000,2.250000
 8: 4 2  2 0.000000,2.000000,2.500000,3.333333 0.000000,2.666667,3.375000,2.250000
 9: 4 3  3 0.000000,2.000000,2.500000,3.333333 0.000000,2.666667,3.375000,2.250000
10: 4 4 NA 0.000000,2.000000,2.500000,3.333333 0.000000,2.666667,3.375000,2.250000

从结果可以清楚地看出,每个(列,组)对的整个差异列表都被复制到该(列,组)对的每个元素中,而不是被映射到该(列,组)对的所有元素。这就是我真正想要的:

    i a  b     a.var.Welford        b.var.Welford
 1: 1 1 NA     0                    0
 2: 2 1 NA     0                    0
 3: 2 2  2     2                    2.666667
 4: 3 1  1     0.0                  0
 5: 3 2  2     2.0                  2
 6: 3 3 NA     2.5                  1
 7: 4 1 NA     0.000000             0.000000
 8: 4 2  2     2.000000             2.666667
 9: 4 3  3     2.500000             3.375000
10: 4 4 NA     3.333333             2.250000

我真的希望有一个简单的解决办法,但我还没有能够解决我的生活。每当我尝试我认为应该工作的东西时,我最终都会从data.table

收到错误
  

j = list(...)中的所有项目都应该是原子矢量或列表。如果你是   尝试类似j = list(.SD,newcol = mean(colA))然后使用:= by   而不是(快得多),或之后cbind或合并。

我理解这意味着我在FUN代码中尝试的任何lapply(.SD, FUN)的返回值的维度与data.table期望的维度不对应该组的专栏。

非常感谢任何和所有帮助。

编辑:好的解决方案非常简单。我觉得我好笨。但是,以后是谁可能需要它的答案

## Make sure to use [[]] at the end. My problem came entirely down to using [].
dt[, eval(out_cols) := lapply(.SD, function(x) {return(as.list(computeVarWelford(x))[[1]])})
   , by=i, .SDcols = in_cols]

这就像一个魅力。我得到了我需要的东西:

    i a  b a.var.Welford b.var.Welford
 1: 1 1 NA      0.000000      0.000000
 2: 2 1 NA      0.000000      0.000000
 3: 2 2  2      2.000000      2.666667
 4: 3 1  1      0.000000      0.000000
 5: 3 2  2      2.000000      2.000000
 6: 3 3 NA      2.500000      1.000000
 7: 4 1 NA      0.000000      0.000000
 8: 4 2  2      2.000000      2.666667
 9: 4 3  3      2.500000      3.375000
10: 4 4 NA      3.333333      2.250000

1 个答案:

答案 0 :(得分:0)

编辑:我不再需要下面的黑客解决方案了。这是解决此问题的代码(注意[[]]而不是[]修复):

dt[, eval(out_cols) := lapply(.SD, function(x) {return(as.list(computeVarWelford(x))[[1]])})
   , by=i, .SDcols = in_cols]
老人:好的,所以我找到了让它最终运作的方法。但我觉得这很难看。我现在要接受这个作为我的答案,但是如果有人有更好的解决方案,我很乐意听到它,并且如果它比我的好,那就接受它作为这个问题的答案。

解决方案:

out_cols_fixed <- paste0(out_cols, '.fixed')
dt[,eval(out_cols_fixed) := lapply(.SD, function(x) { return(x[1][[1]]) }), by=i, .SDcols = out_cols]
dt[,eval(out_cols) := NULL]
setnames(dt, old = out_cols_fixed, new = out_cols)

根据需要dt的结果:

    i a  b a.var.Welford b.var.Welford
 1: 1 1 NA      0.000000      0.000000
 2: 2 1 NA      0.000000      0.000000
 3: 2 2  2      2.000000      2.666667
 4: 3 1  1      0.000000      0.000000
 5: 3 2  2      2.000000      2.000000
 6: 3 3 NA      2.500000      1.000000
 7: 4 1 NA      0.000000      0.000000
 8: 4 2  2      2.000000      2.666667
 9: 4 3  3      2.500000      3.375000
10: 4 4 NA      3.333333      2.250000

我首先尝试了以下内容,但它没有用。任何人都可以解释原因吗?

dt[,eval(out_cols) := lapply(.SD, function(x) { return(x[1][[1]]) }), by=i, .SDcols = out_cols]

我在运行上面一行时遇到以下错误:

  

[.data.table(dt ,, :=(eval(out_cols),lapply(.SD,   function(x){:RHS的类型('double')必须与LHS('list')匹配。至   检查和胁迫会以最快的速度影响性能   案例。更改目标列的类型,或强制RHS   of:=你自己(例如使用1L代替1)