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

时间:2014-06-18 21:43:38

标签: r matrix data.table outer-join

如果事先不知道要求总和的矩阵数,那么添加分量的最佳方法是什么?更一般地说,是否有一种在的上下文中执行矩阵(或多维数组)运算的好方法?我使用data.table来分类和分组数据的效率由几个固定变量或类别组成,每个变量或类别包含不同数量的观察。

例如:

  1. 找到数据的每个观察(行)中给出的向量分量的外积,返回每行的矩阵。
  2. 对每组数据类别的所有行进行分量求和。
  3. 这里用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将数组中的所有元素相加。

    我使用Reduce('+', .list)看到了this answer,但这似乎要求已经添加了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 个答案:

答案 0 :(得分:7)

通常,data.table旨在使用列。将问题转化为整体操作的次数越多,就越能摆脱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的正确顺序的输出。

答案 1 :(得分:2)

修改 对于“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