2016-05-24 13 views
5

Ho cercato il modo canonico di fare quello che sto provando ma mi sembra che abbia poca fortuna a trovare qualcosa di funzionante che sia veloce ed elegante. In breve, ho una grande tabella con più colonne di valore e voglio moltiplicare ciascuna per un fattore corrispondente da una tabella di ricerca. Non riesco a capire come passare dinamicamente in quali colonne voglio moltiplicare per i valori di ricerca, o come fare riferimento ai valori di ricerca in generale al di fuori delle espressioni di base.Assegnazione rapida di data.table di più colonne per gruppo dalla ricerca

Ecco il mio esempio, l'ho impostato con 3 milioni di righe con 10 colonne di valori, questo non richiede troppo tempo ed è in qualche modo rappresentativo della dimensione dei dati (questo verrà implementato come parte di un ciclo molto più grande , quindi l'enfasi sulle prestazioni). C'è anche una tabella di ricerca con 6 livelli e alcuni moltiplicatori assortiti per le nostre colonne value_1: value_10.

library(data.table) 

setsize <- 3000000 
value_num <- 10 
factors <- c("factor_a", "factor_b", "factor_c", "factor_d", "factor_e", "factor_f") 
random <- data.table(replicate(10, sample(factors, size = setsize, replace = T)) 
        , replicate(10, rnorm(setsize, mean = 700, sd = 50))) 
lookup <- data.table("V1" = factors, replicate(10, seq(.90, 1.5, length.out = length(factors)))) 
wps <- paste("value", c(1:10), sep = "_") 
names(random)[11:20] <- wps 
names(lookup)[2:11] <- wps 
setkeyv(random, "V1") 
setkeyv(lookup, "V1") 

Soluzione 1: E 'abbastanza veloce ma non riesco a capire come fare riferimento genericamente alle i-colonne come i.value_1 modo che io possa passare in un ciclo o meglio ancora applicarle tutte contemporaneamente.

f <- function() { 
    random[lookup, value_1 := value_1 * i.value_1, by = .EACHI] 
    random[lookup, value_2 := value_2 * i.value_2, by = .EACHI] 
    random[lookup, value_3 := value_3 * i.value_3, by = .EACHI] 
    random[lookup, value_4 := value_4 * i.value_4, by = .EACHI] 
    random[lookup, value_5 := value_5 * i.value_5, by = .EACHI] 
    random[lookup, value_6 := value_6 * i.value_6, by = .EACHI] 
    random[lookup, value_7 := value_7 * i.value_7, by = .EACHI] 
    random[lookup, value_8 := value_8 * i.value_8, by = .EACHI] 
    random[lookup, value_9 := value_9 * i.value_9, by = .EACHI] 
    random[lookup, value_10 := value_10 * i.value_10, by = .EACHI] 
} 

system.time(f()) 

    user system elapsed 
    0.184 0.000 0.181 

Soluzione 2: Dopo non ho potuto ottenere una soluzione da 1 a essere generico, ho provato un approccio basato set(). Tuttavia, nonostante mi consenta di specificare le colonne del valore target nel vettore di caratteri wps, in realtà è molto più lento di quanto sopra. So che sto usando male, ma non sono sicuro di come migliorarlo per rimuovere tutto il sovraccarico [.data.table.

idx_groups <- random[,.(rowstart = min(.I), rowend = max(.I)), by = key(random)][lookup] 
system.time(
for (i in 1:nrow(idx_groups)){ 
    rows <- idx_groups[["rowstart"]][i]:idx_groups[["rowend"]][i] 
    for (j in wps) { 
    set(random, i=rows, j=j, value= random[rows][[j]] * idx_groups[[j]][i]) 
    } 
}) 

    user system elapsed 
    3.940 0.024 3.967 

Qualsiasi consiglio su come strutturare meglio queste operazioni sarebbe apprezzato.

Edit: Sono molto frustrato con me stesso per non aver provare questa soluzione ovvia prima di inviare questa domanda:

system.time(
for (col in wps){ 
    random[lookup, (col) := list(get(col) * get(paste0("i.", col))), by = .EACHI, with = F] 
}) 

    user system elapsed 
    1.600 0.048 1.652 

che sembra fare quello che voglio con relativa rapidità. Tuttavia è ancora 10 volte più lento della prima soluzione di cui sopra (sono sicuro a causa del ripetuto get()) quindi sono ancora aperto al consiglio.

Modifica 2: sostituzione get() con eval(parse(text=col)) sembra aver fatto il trucco.

system.time(
for (col in wps){ 
    random[lookup, (col) := list(eval(parse(text=col)) * eval(parse(text=paste0("i.", col)))), by = .EACHI, with = F] 
}) 
    user system elapsed 
    0.184 0.000 0.185 

Modifica 3: sono state fornite diverse risposte di lavoro soddisfacenti. La soluzione di Rafael è probabilmente la migliore nel caso generale, anche se noterò che potrei spremere ancora qualche millisecondo dalla costruzione delle chiamate raccomandata da Jangorecki in cambio di una funzione di aiuto dall'aspetto piuttosto intimidatorio. L'ho segnato come risposta, grazie per l'aiuto di tutti.

+0

direi usando 'mget' invece di' eval (parse (...)) 'dovrebbe raggiungere lo stesso risultato (ma non lo ha testato). Se hai risposto alla tua domanda, per favore pubblica la soluzione corretta come "risposta" (non come modifica). THX :-) –

+1

Questa domanda può essere utile: https://stackoverflow.com/questions/30468455/dynamically-build-call-for-lookup-multiple-columns - puoi provare l'ultima soluzione da lì, credo che dovrebbe essere il più efficiente, in quanto evita i campi di analisi e materializzazione da "get". Sentiti libero di rispondere autonomamente se trovi una soluzione più veloce/migliore fornita al momento. – jangorecki

+0

R Yoda, sfortunatamente 'mget' non funziona per me come' eval (parse (...)) 'fa. Jangorecki, grazie per il link! Penso che la tua ultima soluzione sia la più veloce che ho visto e la possibilità di ispezionare l'espressione J rende un po 'più intuitivo ciò che sta accadendo. Pubblicherò la mia versione di esso come risposta a questa domanda. – etrippler

risposta

4

è anche possibile utilizzare lapply:

cols <- noquote(paste0("value_",1:10)) 

random[lookup, (cols) := lapply (cols, function(x) get(x) * get(paste0("i.", x))), by = .EACHI ] 

Nel caso in cui il set di dati è troppo grande e si desidera visualizzare una barra di avanzamento della vostra attività, è possibile utilizzare pblapply:

library(pbapply) 

random[lookup, (cols) := pblapply(cols, function(x) get(x) * get(paste0("i.", x))), by = .EACHI ] 
+0

Questo è solo alcuni millisecondi più lento rispetto alla costruzione della chiamata ed evita la necessità di una funzione di supporto, quindi grazie per la risposta. Sono curioso del motivo per cui hai incluso 'with = F', però, sembra che non sia necessario e non influenzi i tempi? – etrippler

+0

@etrippler, sono felice di aiutare. Hai ragione, non c'è bisogno di usare 'con = F'. [Ecco una spiegazione su quando usarlo] (https://rawgit.com/wiki/Rdatatable/data.table/vignettes/datatable-intro.html) –

+1

Utilizzare 'Map' qui è molto più bello:' (cols) : = Map (\ '* \', mget (cols), mget (icols)) 'dove' icols = paste0 ("i.", Cols) '. – Arun

2

Grazie a jangorecki per aver indicato la sua risposta here, che crea dinamicamente l'espressione J utilizzando una funzione di supporto e quindi valuta tutto in una volta. Evita l'overhead di parsing/get e sembra essere la soluzione più veloce che otterrò.Mi piace anche la possibilità di specificare manualmente la funzione chiamata (alcune istanze potrei volere / anziché *) e controllare l'espressione J prima che venga valutata.

batch.lookup = function(x) { 
    as.call(list(as.name(":="),x 
       ,as.call(c(
       list(as.name("list")), 
       sapply(x, function(x) call("*", as.name(x), as.name(paste0("i.",x))), simplify=FALSE) 
       )) 
)) 
} 

print(batch.lookup(wps)) 

`:=`(c("value_1", "value_2", "value_3", "value_4", "value_5", 
"value_6", "value_7", "value_8", "value_9", "value_10"), list(value_1 = value_1 * 
    i.value_1, value_2 = value_2 * i.value_2, value_3 = value_3 * 
    i.value_3, value_4 = value_4 * i.value_4, value_5 = value_5 * 
    i.value_5, value_6 = value_6 * i.value_6, value_7 = value_7 * 
    i.value_7, value_8 = value_8 * i.value_8, value_9 = value_9 * 
    i.value_9, value_10 = value_10 * i.value_10)) 

system.time(
    random[lookup, eval(batch.lookup(wps)), by = .EACHI]) 

    user system elapsed 
    0.14 0.04 0.18 
3

Si tratta di circa 2 volte più lento di testo in costruzione parsing/chiamata, ma è un po 'più leggibile:

random[lookup, (wps) := Map('*', mget(wps), mget(paste0('i.', wps))), by = .EACHI] 
Problemi correlati