2013-06-05 18 views
25

Sto cercando di trovare un modo elegante per utilizzare l'assegnazione := per sostituire più colonne contemporaneamente in un data.table applicando una funzione condivisa. Un tipico utilizzo di ciò potrebbe essere l'applicazione di una funzione di stringa (ad esempio, gsub) a tutte le colonne di caratteri in una tabella. Non è difficile estendere il modo data.frame a un data.table, ma sto cercando un metodo coerente con il modo di fare data.table.Assegnazione elegante di più colonne in data.table con lapply()

Ad esempio:

library(data.table) 

m <- matrix(runif(10000), nrow = 100) 
df <- df1 <- df2 <- df3 <- as.data.frame(m) 
dt <- as.data.table(df) 
head(names(df)) 
head(names(dt)) 

## replace V20-V100 with sqrt 

# data.frame approach 
# by column numbers 
df1[20:100] <- lapply(df1[20:100], sqrt) 
# by reference to column numbers 
v <- 20:100 
df2[v] <- lapply(df2[v], sqrt) 
# by reference to column names 
n <- paste0("V", 20:100) 
df3[n] <- lapply(df3[n], sqrt) 

# data.table approach 
# by reference to column names 
n <- paste0("V", 20:100) 
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt) 

ho capito che è più efficiente per ciclare su un vettore di nomi di colonna che utilizzano := assegnare:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE] 

non mi piace questo perché ho don' t come riferimento allo data.table in un'espressione j. So anche che posso usare := per assegnare con lapply dato che so i nomi delle colonne: (. Si potrebbe estendere questo costruendo un'espressione con i nomi di colonna sconosciuti)

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)] 

seguito sono elencate le idee Ci ho provato, ma non sono riuscito a farli funzionare. Sto commettendo un errore o c'è un altro approccio che mi manca?

# possible data.table approaches? 
# by reference to column names; assignment works, but not lapply 
n <- paste0("V", 20:100) 
dt[, n := lapply(n, sqrt), with = FALSE] 
# by (smaller for example) list; lapply works, but not assignment 
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)] 
# by reference to list; neither assignment nor lapply work 
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")")) 
dt[, eval(l) := lapply(eval(l), sqrt)] 

risposta

30

Sì, hai ragione in questione qui:

Capisco che è più efficiente eseguire il looping su un vettore di nomi di colonne usando per assegnare:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

parte: notare che il nuovo modo di fare che è:

for (col in paste0("V", 20:100)) 
    dt[ , (col) := sqrt(dt[[col]])] 

perché il with = FALSE non era facile da leggere se si riferiva al LHS e RHS di :=. Fine a parte.

Come sapete, è efficiente perché fa ogni colonna una per una, quindi la memoria di lavoro è necessaria solo per una colonna alla volta.Questo può fare la differenza tra il suo funzionamento e il suo fallimento con il temuto errore di memoria insufficiente.

Il problema con lapply su RHS di := è che l'RHS (lapply) viene valutato per primo; Ad esempio, viene creato il risultato per le 80 colonne. Questo è il valore di 80 colonne di nuova memoria che deve essere allocata e popolata. Quindi hai bisogno di 80 colonne di RAM libera per il successo dell'operazione. L'utilizzo della RAM domina rispetto all'operazione istantanea successiva dell'assegnazione (plonking) di quelle 80 nuove colonne negli slot del puntatore della colonna data.table.

Come indicato da @Frank, se si dispone di molte colonne (ad esempio 10.000 o più), il piccolo overhead di dispacciamento per il metodo [.data.table inizia a sommarsi). Per eliminare l'overhead che esiste data.table::set che in ?set è descritto come "loopable" . Io uso un ciclo for per questo tipo di operazione. È il modo più veloce ed è abbastanza facile da scrivere e leggere.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(dt[[col]])) 

Anche se con soli 80 colonne, è improbabile che la materia. (Nota: potrebbe essere più comune eseguire il ciclo set su un numero elevato di righe rispetto a un numero elevato di colonne.) Tuttavia, il loop set non risolve il problema del riferimento ripetuto al nome del simbolo dt che hai menzionato nella domanda:

Non mi piace perché non mi piace il riferimento data.table nell'espressione aj.

Concordato. Quindi il meglio che posso fare è ripristinare il ciclo di := ma utilizzare get.

for (col in paste0("V", 20:100)) 
    dt[, (col) := sqrt(get(col))] 

Tuttavia, temo che l'utilizzo get in j può essere inefficiente. È necessario un benchmarking, #1380. Inoltre, forse è confuso usare get() sul RHS ma non sul LHS. Per far fronte a quello che potevamo zucchero LHS e consentire get() pure, #1381:

for (col in paste0("V", 20:100)) 
    dt[, get(col) := sqrt(get(col))] 

Inoltre, forse value di set potrebbe essere eseguito nel campo di applicazione della DT, #1382.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(get(col)) 
+1

Grazie mille per aver messo le parentesi intorno a 'col'. Fino a quando non ho ricordato quel trucco, stavo ricevendo una colonna chiamata "col". – Farrel

7

È questo quello che stai cercando?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x)) , .SDcols=20:100] 

Ho sentito dire che l'uso di .SD non è così efficace perché rende una copia del tavolo in anticipo, ma se il tavolo non è enorme (ovviamente questo è relativo a seconda delle specifiche del sistema) Dubito farà molta differenza.

+4

mi è stato detto che [ 'set' può anche accelerare le operazioni in questo modo] (http://stackoverflow.com/questions/16846380/how-to-apply-same-function-to-every specificate con-column-in-a-data-table/16846530 # 16846530). – Frank

+0

@Frank +1 per quella risposta, e ho segnato il libro per riferimento futuro. Non penserei di usare un ciclo 'for' qui. Intelligente. –

+0

@Frank, non conoscevo l'approccio 'for' loop +' set'. Dovrò pensare di usarlo in futuro. –

13

Questi dovrebbero funzionare se si desidera fare riferimento alle colonne in base al nome della stringa:

n = paste0("V", 20:100) 
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})] 

o

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})] 
+2

Un ulteriore) è necessario nell'ultima riga: 'dt [, (n): = lapply (n, function (x) {sqrt (dt [[x]])})] ' – HywelMJ

+0

@HywelMJ grazie, corretto – eddi

+0

Forse aggiungi anche un'opzione' .SDcols', come 'dt [, (n): = lapply (.SD, sqrt), .SDcols = n] '? Hmm ..a pensarci, forse già Simone aveva già fatto qualcosa di simile. –

Problemi correlati