2014-06-18 20 views
7

Qual è il modo migliore di fare l'aggiunta di componente se il numero di matrici da sommare non è noto in anticipo? Più in generale, esiste un buon metodo per eseguire operazioni di matrice (o array multidimensionale) nel contesto di ? Io uso data.table per la sua efficienza nell'ordinamento e nel raggruppamento dei dati per mezzo di diverse variabili fisse, o categorie, ciascuna comprendente un diverso numero di osservazioni.operazioni con le matrici e aggiunta di componenti mediante data.table

Ad esempio:

  1. Trova il prodotto esterno di componenti vettoriali fornite in ciascuna osservazione (riga) dei dati, restituendo una matrice per ogni riga.
  2. Somma le matrici risultanti in base al componente su tutte le righe di ciascun raggruppamento di categorie di dati.

qui illustrate con matrici 2x2 e solo una categoria:

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 

ho tentato una funzione per calcolare somma matrice sul prodotto esterno, %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. 
    } 

che, naturalmente, non fa funziona perché sum somma tutti gli elementi attraverso gli array.

vidi this answer utilizzando Reduce('+', .list) ma che sembra richiedere già avere un list di tutte le matrici da aggiungere. Non ho capito come farlo all'interno data.table, così invece ho un ingombrante work-around:

# 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 

e che dà i componenti sommate, che possono finalmente essere riconvertiti in matrici.

+0

+1 Che grande prima domanda. Benvenuto in Stack Overflow. –

risposta

7

In generale, data.table è progettato per funzionare con le colonne. Più trasformi il tuo problema in operazioni col-saggio, più puoi uscire da data.table.

Ecco un tentativo di eseguire questa operazione col-saggio. Probabilmente ci sono modi migliori. Questo è inteso più come un modello, per fornire un'idea su come affrontare il problema (anche se capisco che potrebbe non essere possibile in tutti i casi).

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 

vale Questo fondamentalmente il prodotto esterno col-saggio. Ora si tratta solo di aggregarlo.

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


Edit: Modificato cols, c1, c2 un po 'per ottenere il risultato con l'ordine corretto per V2 e V3.

+0

Qui ci sono molti aspetti utili, specialmente l'uso di 'CJ' e' .SD', ma anche 'seq',' grep' e altri comandi di stringa con cui non ero abbastanza familiare. Questo modello si estende direttamente alle matrici m-by-n, sottraendo convenientemente le dimensioni da xcols e ycols. Una domanda è perché V2 e V3 sono invertiti – Scott

+0

Grazie per la modifica, anche se non vorrei chiamare l'originale un ordine "errato". È solo a causa del riempimento predefinito di R degli elementi matriciali che ho elencato 11, 21, 12, 22. Interessante che 'CJ' al contrario vada in termini di righe, che in realtà trovo più naturale. Forse uso 'cols <- paste (" V ", comb $ V1, combs $ V2, sep =" ")' per aiutarmi a tenere traccia degli indici. – Scott

2

EDIT: non solo 2 elementi in s "x" e s "y", una funzione modificata potrebbe essere:

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)))) 
} 

dove, "x_ls" e "y_ls" sono elenchi di i rispettivi vettori.

Usandolo:

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 

e su altri "data.frames/tavoli":

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 

Non lo so, però, come si potrebbe in "data.table" non indicare esplicitamente quali colonne utilizzare all'interno della funzione; vale a dire come si potrebbe fare l'equivalente di:

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 

RISPOSTA VECCHIO:

Forse si potrebbe definire la funzione come:

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 
+0

Questo è pulito e compatto. Potrebbe essere necessario regolare semplicemente la funzione (e gli argomenti) per contenere le dimensioni arbitrarie dei vettori x e y. – Scott

+0

@Scott: ho appena modificato la risposta con una soluzione alternativa, anche se non sono sicuro di quanto possa essere utile –