首页 文章

使用data.table进行矩阵运算和分量加法

提问于
浏览
7

如果事先不知道要求和的矩阵数,那么在组件方面添加组合的最佳方法是什么?更一般地说,是否有一种很好的方法在data.table的上下文中执行矩阵(或多维数组)操作?我使用 data.table 来通过几个固定变量或类别对数据进行排序和分组的效率,每个变量或类别包含不同数量的观察值 .

例如:

  • 找到数据的每个观察(行)中给出的向量分量的外积,返回每行的矩阵 .

  • 在每组数据类别的所有行上按组件顺序对结果矩阵求和 .

这里用2x2矩阵说明,只有一个类别:

library(data.table)

# example data, number of rows differs by category t
N <- 5
dt <- data.table(t = rep(c("a", "b"), each = 3, len = N), 
                 x1 = rep(1:2, len = N), x2 = rep(3:5, len = N),
                 y1 = rep(1:3, len = N), y2 = rep(2:5, len = N))
setkey(dt, t)
> dt
   t x1 x2 y1 y2
1: a  1  3  1  2
2: a  2  4  2  3
3: a  1  5  3  4
4: b  2  3  1  5
5: b  1  4  2  2

我尝试了一个函数来计算外积的矩阵和, %o%

mat_sum <- function(x1, x2, y1, y2){
  x <- c(x1, x2) # x vector
  y <- c(y1, y2) # y vector
  xy <- x %o% y # outer product (i.e. 2x2 matrix)
  sum(xy)  # <<< THIS RETURNS A SINGLE VALUE, NOT WHAT I WANT.
  }

当然,这不起作用,因为 sum 将数组中的所有元素相加 .

我看到this answer正在使用 Reduce('+', .list) ,但这似乎要求已经添加了所有矩阵的 list . 我还没想出如何在 data.table 内做到这一点,所以相反,我有一个繁琐的解决办法:

# extract each outer product component first...
mat_comps <- function(x1, x2, y1, y2){
  x <- c(x1, x2) # x vector
  y <- c(y1, y2) # y vector
  xy <- x %o% y # outer product (i.e. 2x2 matrix)
  xy11 <- xy[1,1]
  xy21 <- xy[2,1]
  xy12 <- xy[1,2]
  xy22 <- xy[2,2]
  return(c(xy11, xy21, xy12, xy22))
}

# ...then running this function on dt, 
# taking extra step (making column 'n') to apply it row-by-row...
dt[, n := 1:nrow(dt)]
dt[, c("xy11", "xy21", "xy12", "xy22") := as.list(mat_comps(x1, x2, y1, y2)), 
   by = n]

# ...then sum them individually, now grouping by t
s <- dt[, list(s11 = sum(xy11),
               s21 = sum(xy21),
               s12 = sum(xy12),
               s22 = sum(xy22)),
        by = key(dt)]
> s
   t s11 s21 s12 s22
1: a   8  26  12  38
2: b   4  11  12  23

并给出了总和的组件,最终可以转换回矩阵 .

2 回答

  • 2

    通常, data.table 旨在用于列 . 你将问题转化为col-wise操作越多,你就越能摆脱 data.table .

    这是尝试完成此操作 . 可能有更好的方法 . 这更像是一个模板,提供了解决问题的想法(尽管我知道在所有情况下可能都不可能) .

    xcols <- grep("^x", names(dt))
    ycols <- grep("^y", names(dt))
    combs <- CJ(ycols, xcols)
    len <- seq_len(nrow(combs))
    cols = paste("V", len, sep="")
    for (i in len) {
        c1 = combs$V2[i]
        c2 = combs$V1[i]
        set(dt, i=NULL, j=cols[i], value = dt[[c1]] * dt[[c2]])
    }
    
    #    t x1 x2 y1 y2 V1 V2 V3 V4
    # 1: a  1  3  1  2  1  3  2  6
    # 2: a  2  4  2  3  4  8  6 12
    # 3: a  1  5  3  4  3 15  4 20
    # 4: b  2  3  1  5  2  3 10 15
    # 5: b  1  4  2  2  2  8  2  8
    

    这基本上适用于外部产品 . 现在只需聚合它就可以了 .

    dt[, lapply(.SD, sum), by=t, .SDcols=cols]
    
    #    t V1 V2 V3 V4
    # 1: a  8 26 12 38
    # 2: b  4 11 12 23
    

    HTH


    编辑:修改 cols, c1, c2 一点,以获得 V2V3 的正确顺序的输出 .

  • 7

    EDIT: 对于"x"和"y"中的2个元素,修改后的函数可以是:

    ff2 = function(x_ls, y_ls)
    {
       combs_ls = lapply(seq_along(x_ls[[1]]), 
                         function(i) list(sapply(x_ls, "[[", i), 
                                          sapply(y_ls, "[[", i)))
       rowSums(sapply(combs_ls, function(x) as.vector(do.call(outer, x))))
    }
    

    其中,“x_ls”和“y_ls”是各个矢量的列表 .

    使用它:

    dt[, as.list(ff2(list(x1, x2), list(y1, y2))), by = t]
    #   t V1 V2 V3 V4
    #1: a  8 26 12 38
    #2: b  4 11 12 23
    

    在其他“data.frames / tables”上:

    set.seed(101)
    DF = data.frame(group = rep(letters[1:3], c(4, 2, 3)), 
                    x1 = sample(1:20, 9, T), x2 = sample(1:20, 9, T), 
                    x3 = sample(1:20, 9, T), x4 = sample(1:20, 9, T),
                    y1 = sample(1:20, 9, T), y2 = sample(1:20, 9, T), 
                    y3 = sample(1:20, 9, T), y4 = sample(1:20, 9, T))               
    DT = as.data.table(DF)
    
    DT[, as.list(ff2(list(x1, x2, x3, x4), 
                     list(y1, y2, y3, y4))), by = group]
    #   group  V1  V2  V3  V4  V5  V6  V7  V8  V9 V10 V11 V12 V13 V14 V15 V16
    #1:     a 338 661 457 378 551 616 652 468 460 773 536 519 416 766 442 532
    #2:     b 108 261 171  99  29  77  43  29 154 386 238 146 161 313 287 121
    #3:     c 345 351 432 293 401 421 425 475 492 558 621 502 510 408 479 492
    

    但是,我不知道“data.table”中的一个如何没有明确说明在函数内部使用哪些列;即如何做到相当于:

    do.call(rbind, lapply(split(DF[-1], DF$group), 
                          function(x) 
                              do.call(ff2, c(list(x[grep("^x", names(x))]), 
                                             list(x[grep("^y", names(x))])))))
    #  [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16]
    #a  338  661  457  378  551  616  652  468  460   773   536   519   416   766   442   532
    #b  108  261  171   99   29   77   43   29  154   386   238   146   161   313   287   121
    #c  345  351  432  293  401  421  425  475  492   558   621   502   510   408   479   492
    

    OLD ANSWER:

    也许你可以定义你的功能:

    ff1 = function(x1, x2, y1, y2)
         rowSums(sapply(seq_along(x1), 
                        function(i) as.vector(c(x1[i], x2[i]) %o% c(y1[i], y2[i]))))
    
    dt[, as.list(ff1(x1, x2, y1, y2)), by = list(t)]
    #   t V1 V2 V3 V4
    #1: a  8 26 12 38
    #2: b  4 11 12 23
    

相关问题