2013-03-28 19 views
18

Sto cercando un modo per applicare in modo efficiente una funzione a ciascuna riga di data.table. Consideriamo la seguente tabella di dati:Applicazione di una funzione a ciascuna riga di un data.table

library(data.table) 
library(stringr) 

x <- data.table(a = c(1:3, 1), b = c('12 13', '14 15', '16 17', '18 19')) 
> x 
    a  b 
1: 1 12 13 
2: 2 14 15 
3: 3 16 17 
4: 1 18 19 

Diciamo voglio dividere ogni elemento della colonna b dallo spazio (ottenendo così due righe per ogni riga dei dati originali) e unire le tabelle dati risultanti. Per l'esempio di cui sopra, ho bisogno il seguente risultato:

a V1 
1: 1 12 
2: 1 13 
3: 2 14 
4: 2 15 
5: 3 16 
6: 3 17 
7: 1 18 
8: 1 19 

Il seguente funzionerebbe se colonna a ha solo valori unici:

x[, list(str_split(b, ' ')[[1]]), by = a] 

Il seguenti quasi lavori (a meno che non ci sono alcuni righe identiche nella tabella dati originale), ma è brutto quando x ha molte colonne e copia la colonna b nel risultato, che vorrei evitare.

>  x[, list(str_split(b, ' ')[[1]]), by = list(a,b)] 
    a  b V1 
1: 1 12 13 12 
2: 1 12 13 13 
3: 2 14 15 14 
4: 2 14 15 15 
5: 3 16 17 16 
6: 3 16 17 17 
7: 1 18 19 18 
8: 1 18 19 19 

Quale sarebbe il modo più efficiente e idiomatico per risolvere questo problema?

risposta

14

ne dite:

x 
    a  b 
1: 1 12 13 
2: 2 14 15 
3: 3 16 17 
4: 1 18 19 

x[,list(a=rep(a,each=2), V1=unlist(strsplit(b," ")))] 
    a V1 
1: 1 12 
2: 1 13 
3: 2 14 
4: 2 15 
5: 3 16 
6: 3 17 
7: 1 18 
8: 1 19 

soluzione generalizzata dato commento:

x[,{s=strsplit(b," ");list(a=rep(a,sapply(s,length)), V1=unlist(s))}] 
+0

Grazie Matthew - questo funziona nel mio particolare esempio (esattamente due componenti in ogni b, separati dallo spazio) ma non funzionerebbero in un caso più generale, dove ogni b può avere da 1 a 10 componenti. Il che dimostra che è difficile specificare con precisione la tua domanda alcune volte :). –

+0

@VictorK. Ecco qua. –

+0

Matt, questa è una soluzione perfetta che ha risparmiato un sacco di tempo ed eseguito in modo abbastanza efficiente. Mostra che il tuo DT deve davvero sostituire DF in r-base. Citerò questo nella mia lezione di analisi dei big data. Una domanda, come possiamo renderla ancora più efficiente eseguendola su più core in parallelo? Ho controllato htop e un core run. –

2

Una possibilità potrebbe essere quella di aggiungere un numero di riga

x[, r := 1:nrow(x)] 

e poi di gruppo da r:

x[, list(a, str_split(b, ' ')[[1]]), by = r] 

mi chiedo se ci sono soluzioni migliori?

+3

forse più idiomatica, si potrebbe includere una chiamata a 'rownames' in 'by' (o meglio,' keyby'): 'x [, lista (str_split (b, '') [[1]]), keyby = lista (a, rownames (x))]'. –

+0

Sì, mi piace. Lo accetterò volentieri se lo pubblichi come risposta. Non sono sicuro di aver bisogno di 'keyby' però (dato che voglio solo passare attraverso la tabella dati una volta) e non hai bisogno di' a' nella chiave - solo i rownames (x) dovrebbero essere sufficienti per il mio scopo. –

2

L'approccio più efficace e idiomatico è avere una funzione vettoriale.

In questo caso, una sorta di regex farà quello che vuoi

x[, V1 := gsub(" [[:alnum:]]*", "", b)] 

    a  b V1 
1: 1 12 13 12 
2: 2 14 15 14 
3: 3 16 17 16 
4: 1 18 19 18 

Se si desidera restituire la componente di ogni divisione, e sai che ci sono due in ciascuno di essi, è possibile utilizzare Map per costringere il risultato strsplit nella forma corretta

x[, c('b1','b2') := do.call(Map, c(f = c, strsplit(b, ' ')))] 



x 
    a  b b1 b2 
1: 1 12 13 12 13 
2: 2 14 15 14 15 
3: 3 16 17 16 17 
4: 1 18 19 18 19 
+0

Probabilmente non ho spiegato cosa voglio correttamente. Quello di cui ho bisogno è il risultato in fondo alla mia domanda, ma senza colonna 'b'. Nel mio particolare esempio, ogni riga nella tabella dati originale dovrebbe produrre due righe nel risultato, poiché ogni valore in 'b' si divide in due sottostringhe. –

+0

@VictorK vedi la mia modifica ... – mnel

+0

@mnel sebbene sia probabilmente un formato più ragionevole, questo non porta a termine il risultato desiderato dall'OP. –

0

Guardando ingresso e uscita desiderata, questo dovrebbe funzionare -

x <- data.frame(a=c(1,2,3,1),b=c("12 13","14 15","16 17","18 19")) 
data.frame(a=rep(x$a,each=2), new_b=unlist(strsplit(as.character(x$b)," "))) 
+0

Questo non restituisce l'output richiesto (avviso di ordinamento di 'a'). –

+0

Oh ok. Quello è facile da risolvere :) – Nishanth

+0

e per generalizzare il risultato sostituire each = 2 di - each = length (unlist (strsplit (as.character (x $ b)))) – Nishanth

1

L'approccio dplyr/tidyr funziona anche con le tabelle di dati.

library(dplyr) 
library(tidyr) 
x %>% 
    separate(b, into = c("b1", "b2")) %>% 
    gather(b, "V1", b1:b2) %>% 
    arrange(V1) %>% 
    select(a, V1) 

Oppure, con i moduli standard di valutazione:

x %>% 
    separate_("b", into = c("b1", "b2")) %>% 
    gather_("b", "V1", c("b1", "b2")) %>% 
    arrange_(~ V1) %>% 
    select_(~ a, ~ V1) 

Il caso di un diverso numero di valori nella colonna b è solo leggermente più complicato.

library(stringr) 

x2 <- data.table(
    a = c(1:3, 1), 
    b = c('12 13', '14', '15 16 17', '18 19') 
) 

n <- max(str_count(x2$b, " ")) + 1 
b_cols <- paste0("b", seq_len(n)) 
x2 %>% 
    separate_("b", into = b_cols, extra = "drop") %>% 
    gather_("b", "V1", b_cols) %>% 
    arrange_(~ V1) %>% 
    select_(~ a, ~ V1) 
1
x[, .(a,strsplit(b,' ')), by=1:nrow(x)] 

by=nrow(x) è un modo semplice per imporre 1 riga per by-gruppo

2
x[, .(a,strsplit(b,' ')), by = .I] 

sembra più estetico

Problemi correlati