2016-06-06 15 views
7

Ecco una spiegazione buona SO su row operations in data.tableoperazioni di riga nelle data.table usando `da = .I`

Un'alternativa che mi è venuta in mente è quello di utilizzare un unico id per ogni riga e quindi applicare una funzione utilizzando l'argomento by. Come questo:

library(data.table) 

dt <- data.table(V0 =LETTERS[c(1,1,2,2,3)], 
       V1=1:5, 
       V2=3:7, 
       V3=5:1) 

# create a column with row positions 
dt[, rowpos := .I] 

# calculate standard deviation by row 
dt[ , sdd := sd(.SD[, -1, with=FALSE]), by = rowpos ] 

Domande:

  1. C'è una buona ragione per non utilizzare questo approccio? forse altre alternative più efficienti?

  2. Perché l'utilizzo di by = .I non funziona nello stesso modo?

    dt[ , sdd := sd(.SD[, -1, with=FALSE]), by = .I ]

+2

In questo caso, è possibile utilizzare 'Riduci (" + ", dt [, 2: 4, con = FALSE])' a (1) _not_ loop per righe e (2) _non_ convertire in "matrice". Per altre operazioni su riga, si potrebbe considerare simile alle operazioni "Riduci" per evitare di applicare una funzione a ciascuna riga o, eventualmente, memorizzare i dati come "matrice" e utilizzare "matrice" - funzioni specifiche/efficienti –

+2

..con 'sd', anche, guardando [qui] (http://stackoverflow.com/questions/25099825/row-wise-variance-of-a-matrix-in-r) e [qui] (http: // stackoverflow .com/questions/17549762/is-there-such-colsd-in-r), un'opzione sembra essere 'sqrt (rowSums ((dt [, 2: 4, with = FALSE] - Reduce (" + ", dt [, 2: 4, con = FALSE])/3)^2)/(3 - 1)) ' –

+0

Non so perché' di = .I' non dà errore, ma non è equivalente a ' 1: nrow (dt) '- Scriverò un bug report se fossi in te – eddi

risposta

10

1) Bene, una ragione per non utilizzare, almeno per l'esempio rowsums è la prestazione, e la creazione di una colonna inutile. Confronta all'opzione f2 sotto, che è quasi 4 volte più veloce e non ha bisogno di colonna rowpos:

dt <- data.table(V0 =LETTERS[c(1,1,2,2,3)], V1=1:5, V2=3:7, V3=5:1) 
f1 <- function(dt){ 
    dt[, rowpos := .I] 
    dt[ , sdd := rowSums(.SD[, 2:4, with=FALSE]), by = rowpos ] } 
f2 <- function(dt){dt[, sdd := rowSums(dt[, 2:4, with=FALSE])]} 

library(microbenchmark) 
microbenchmark(f1(dt),f2(dt)) 
# Unit: milliseconds 
# expr  min  lq  mean median  uq  max neval cld 
# f1(dt) 3.669049 3.732434 4.013946 3.793352 3.972714 5.834608 100 b 
# f2(dt) 1.052702 1.085857 1.154132 1.105301 1.138658 2.825464 100 a 

2) Sulla tua seconda domanda, anche se dt[, sdd := sum(.SD[, 2:4, with=FALSE]), by = .I] non funziona, dt[, sdd := sum(.SD[, 2:4, with=FALSE]), by = 1:NROW(dt)] funziona perfettamente. Dato che secondo ?data.table".I è un vettore intero uguale a seq_len (nrow (x))", ci si potrebbe aspettare che questi siano equivalenti. La differenza, tuttavia, è che .I è per l'uso in j, non in by, perché il valore viene restituito da by anziché valutato in anticipo.

Potrebbe anche essere previsto (si veda il commento alla domanda precedente da @eddi) che by = .I dovrebbe solo generare un errore. Ma questo non si verifica, perché il caricamento del pacchetto data.table crea un oggetto .I nello spazio dei nomi data.table accessibile dall'ambiente globale e il cui valore è NULL. È possibile verificare ciò digitando .I al prompt dei comandi. (Si noti, lo stesso vale per .SD, .EACHI, .N, .GRP e .BY)

.I 
# Error: object '.I' not found 
library(data.table) 
.I 
# NULL 
data.table::.I 
# NULL 

Il risultato di questo è che il comportamento di by = .I è equivalente a by = NULL.

3) Anche se abbiamo già visto nella parte 1 che nel caso di rowSums che loop già row-wise efficiente, ci sono modi molto superiori rispetto creare la colonna rowpos. Ma per quanto riguarda il ciclo quando non abbiamo una funzione veloce per la riga?

Benchmarking le by = rowpos e by = 1:NROW(dt) versioni contro un ciclo for con set() è informativo qui, e dimostra che la versione loop è più veloce di una delle by = approcci:

f.rowpos <- function(){ 
    dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1) 
    dt[, rowpos := .I] 
    dt[ , sdd := sum(.SD[, 2:4, with=FALSE]), by = rowpos ][] 
} 

f.nrow <- function(){ 
    dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1) 
    dt[, sdd := sum(.SD[, 2:4, with=FALSE]), by = 1:NROW(dt) ][] 
} 

f.forset<- function(){ 
    dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1) 
    dt[, sdd:=0L] 
    for (i in 1L:NROW(dt)) { 
    set(dt, i, 5L, sum(dt[i, 2:4])) 
    } 
    dt 
} 

microbenchmark(f.rowpos(),f.nrow(), f.forset(), times = 5) 
Unit: seconds 
     expr  min  lq  mean median  uq  max neval cld 
f.rowpos() 4.465371 4.503614 4.510916 4.505922 4.521629 4.558042  5 b 
    f.nrow() 4.499120 4.499920 4.541131 4.558701 4.571267 4.576647  5 b 
f.forset() 2.540556 2.603505 2.654036 2.606108 2.750719 2.769292  5 a 

Quindi, in conclusione, anche in situazioni in cui non esiste una funzione ottimizzata come rowSums che già funziona per riga, ci sono sempre alternative all'utilizzo di una colonna rowpos che sono più veloci, mentre non richiedono la creazione di una colonna ridondante.

Problemi correlati