2013-03-28 3 views
18

Ich suche nach einer Möglichkeit, eine Funktion effizient auf jede Zeile von data.table anzuwenden. Lassen Sie sich die folgende Datentabelle berücksichtigen:Anwenden einer Funktion auf jede Zeile einer 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 

Lassen Sie sich sagen, dass ich jedes Element der Spalte b durch Raum geteilt werden soll (was somit zwei Zeilen für jede Zeile in den Originaldaten) und kommen Sie mit den resultierenden Datentabellen. Für das obige Beispiel, ich brauche das folgende Ergebnis:

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 

Die folgende funktionieren würde, wenn Spalte a nur eindeutige Werte hat:

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

Die folgende fast Werke (es sei denn, es gibt einige, identische Zeilen in der Originaldatentabelle), ist aber hässlich, wenn x viele Spalten hat und Spalte b in das Ergebnis kopiert, was ich gerne vermeiden würde.

>  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 

Was wäre der effizienteste und idiomatische Weg, um dieses Problem zu lösen?

Antwort

14

Wie wäre:

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 

Generalized Lösung gegeben Kommentar:

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

Dank Matthew - das funktioniert in meinem speziellen Beispiel (genau zwei Komponenten in jedem b, getrennt durch den Raum), würde aber nicht in einem allgemeineren Fall funktionieren, in dem jedes b 1 bis 10 Komponenten haben kann. Was zeigt, dass es schwierig ist, Ihre Frage genau zu spezifizieren :). –

+0

@VictorK. Da gehst du. –

+0

Matt, das ist eine perfekte Lösung, die viel Zeit gespart hat und sehr effizient arbeitet. Es zeigt, dass dein DT wirklich DF in r-base ersetzen muss. Ich zitiere dies in meiner Big Data Analytics-Klasse. Eine Frage, wie können wir es noch effizienter machen, indem wir es parallel auf Multi-Cores laufen lassen? Ich habe htop geprüft und ein Kern läuft. –

2

Eine Möglichkeit wäre, eine Zeilennummer

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

hinzufügen und dann die Gruppe von r:

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

Ich frage mich, ob es bessere Lösungen?

+3

vielleicht mehr idiomatische, könnten Sie einen Anruf zu 'rownames' enthalten in 'by' (oder besser:' keyby'): 'x [, Liste (str_split (b, '') [[1]]), keyby = Liste (a, rownames (x))]'. –

+0

Ja, ich mag es. Ich werde es gerne annehmen, wenn du es als Antwort postest. Ich bin mir nicht sicher, ob ich 'keyby' brauche (da ich nur einmal durch die Datentabelle gehen will) und du brauchst' a' nicht im Schlüssel - nur rownames (x) sollte für meinen Zweck ausreichen. –

2

Der effektivste und idiomatische Ansatz ist eine vektorisierte Funktion.

In diesem Fall wird eine Art regex tun, was Sie

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 

wollen Wenn Sie die einzelnen Teilelement zurückkehren wollen, und Sie wissen, gibt es zwei in jeder, Sie Map verwenden können, um coerce

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

Ich habe wahrscheinlich nicht erklärt, was ich will. Was ich brauche, ist das Ergebnis am Ende meiner Frage, aber ohne Spalte "b". In meinem speziellen Beispiel sollte jede Zeile in der Originaldatentabelle zwei Zeilen im Ergebnis ergeben, da jeder Wert in "b" in zwei Teilzeichenfolgen aufgeteilt wird. –

+0

@VictorK sehe meine Bearbeitung ... – mnel

+0

@Mnel obwohl wohl ein sinnvolles Format, dies nicht OP gewünschten Ergebnis zu erreichen. –

0

am Eingang das Ergebnis von strsplit in die richtige Form der Suche und der gewünschten Ausgabe, sollte diese Arbeit -

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

Das gibt die angeforderte Ausgabe nicht zurück (beachten Sie die Reihenfolge von 'a'). –

+0

Oh ok. Das ist einfach zu beheben :) – Nishanth

+0

und um das Ergebnis zu verallgemeinern, ersetzen Sie jedes = 2 von - each = Länge (unlist (strsplit (as.character (x $ b)))) – Nishanth

1

Der dplyr/tidyr Ansatz auch mit Datentabellen funktioniert.

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

Oder die Standard-Bewertungsbögen mit:

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

Der Fall einer unterschiedlichen Anzahl von Werten in der b Spalte ist nur etwas komplizierter.

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) eine einfache Art und Weise ist 1 Zeile zu zwingen, pro by-Gruppe

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

sieht mehr estetic