2016-04-11 18 views
1

Entschuldigung im Voraus, wenn dies ein Duplikat ist, aber ich habe eine Menge Zeit mit der Suche verbracht und habe nichts gefunden, was mein Problem betrifft.data.table r erstellen neue Spalte mit Bezug auf Spalte

Ich habe eine Spalte in einer data.table, die den Namen einer Spalte enthält, die ich verwenden möchte, um eine neue Spalte zu erstellen. Das heißt, ich möchte für jede Zeile eine andere Spalte basierend auf den Werten in einer Spalte nachschlagen.

Ich habe versucht, get() verwenden, aber das hat nicht funktioniert:

d<-data.table(A=1:10,B=11:20,Ref_Col=rep(c("A","B"),5)) 
d[,new_col:=get(Ref_Col)] 

Die unter der Ausgang Ich mag würde:

 A B Ref_Col new_col 
1: 1 11  A  1 
2: 2 12  B  12 
3: 3 13  A  3 
4: 4 14  B  14 
5: 5 15  A  5 
6: 6 16  B  16 
7: 7 17  A  7 
8: 8 18  B  18 
9: 9 19  A  9 
10: 10 20  B  20 

Jede Hilfe sehr geschätzt.

+0

Versuchen 'd [, new_col: = ifelse (Ref_Col == "A" , A, B)] ' – nicola

+0

Danke für die schnelle Antwort nicola. Ich hätte in meiner Frage erwähnen sollen, dass es tatsächlich mehr als nur zwei Spalten in der data.table (~ 30) gibt. Es wäre also unpraktisch, wenn ich mit jedem von ihnen etwas tun würde. – mat

+4

Versuchen Sie: 'd [, new_col: = get (Ref_Col), durch = Ref_Col]' – Arun

Antwort

3

Mein erster Eindruck beim Lesen Ihrer Frage war, dass ein index matrix wäre perfekt hier. Ich habe eine Lösung entwickelt, die auf dieser Idee basiert, aber ich muss Sie warnen, es war viel mehr involviert, als man hoffen würde.

Hier ist die ganze Sache:

d[,new_col:= 
    as.matrix(d[,unique(d[,Ref_Col]),with=F])[ 
     matrix(c(seq_len(nrow(d)),match(Ref_Col,unique(Ref_Col))),nrow(d)) 
    ] 
]; 
##  A B Ref_Col new_col 
## 1: 1 11  A  1 
## 2: 2 12  B  12 
## 3: 3 13  A  3 
## 4: 4 14  B  14 
## 5: 5 15  A  5 
## 6: 6 16  B  16 
## 7: 7 17  A  7 
## 8: 8 18  B  18 
## 9: 9 19  A  9 
## 10: 10 20  B  20 

Lassen Sie uns das brechen ein Stück zu einer Zeit:


c(seq_len(nrow(d)),match(Ref_Col,unique(Ref_Col))) 

Zuerst habe ich den Vektor konstruieren, die die zugrunde liegenden Daten des Index umfassen wird Matrix. Die linke Spalte enthält die tiefgestellten Zeilen, die rechte Spalte enthält die tiefgestellten Spalten. Eine Beschränkung von Matrizen besteht nun darin, dass sie keine heterogenen Typen enthalten können. Daher müssen wir zwischen Integer-Indizes und Charakternamen wählen. Da Ihre data.table keine Zeilennamen hat, müssen wir ganzzahlige Indizes verwenden (und ganzzahlige Indizes werden wahrscheinlich schneller sein). Die Zeilenindizes sind leicht zu konstruieren; es ist nur die Sequenz von 1 bis nrow(d). Die Spaltenindizes müssen match() von den Ref_Col Werten zu den Spaltennamen des Objekts sein, das wir indizieren werden. Um voraus zu springen, indexieren wir nicht d, sondern eine Matrix, die nur aus den Spalten besteht, die mindestens einmal von der Spalte Ref_Col referenziert werden. Daher basieren die korrekten Spaltenindizes auf der Position des Spaltennamens innerhalb des Vektors unique(Ref_Col).


matrix(...,nrow(d)) 

Der nächste Schritt ist natürlich eine Matrix aus dem zugrundeliegenden Datenvektor zu bilden.


as.matrix(d[,unique(d[,Ref_Col]),with=F])[...] 

Leider data.table momentan keine Indizierung mit einem Index-Matrix unterstützen. Um dieses Problem zu lösen, müssen wir auf einen Datentyp zurückgreifen, der die Indexierung mit einer Indexmatrix unterstützt. Die zwei logischsten Auswahlen sind ein Datenrahmen oder eine Matrix. Ich ging mit einer Matrix, da die Spalte sowieso einen einzelnen Vektor-Typ umfassen muss, so dass das Anwenden auf eine Matrix (und daher das Abflachen aller Bezugsspalten auf einen einzigen Typ) kein Problem darstellt. Ich dachte kurz darüber nach, da eine data.table bereits ein gültiger data.frame ist (dh sie "erbt" von data.frame unter Rs Pseudo-OOP-Paradigma), und da R eine "copy-on-modify" -Optimierung unter der Haube verwendet Es könnte weniger kostspielig sein, auf data.frame zu kopieren und hoffentlich eine Kopie zu vermeiden, aber wenn tracemem() ausgeführt wird, wird R tatsächlich die gesamte data.table kopieren, wenn sie in data.frame umgewandelt wird.(Update: Und ich habe gerade festgestellt, dass R intern eine data.frame immer zu einer Matrix ehert, kurz bevor es von einer Indexmatrix indexiert wird. Daher würde ein Wechsel zu data.frame vor der Indizierung mit einer Indexmatrix nichts kaufen über die Matrix direkt zu erzwingen.) Also ging ich einfach mit as.matrix(). Ja, es kopiert immer noch Daten in diesem Fall, aber es wird zumindest weniger Daten kopieren, da wir zuerst nur die Referenzspalten mit unique(d[,Ref_Col]) indizieren können (was with=F erfordert). Schließlich


d[,new_col:=...] 

, können wir die neue Spalte aus dem Ergebnis des Anlegens der Indexmatrix zur kombinierten referent Matrix zuzuordnen.


Leistung

Ich habe einige Eckwerte:

library(data.table); 
library(microbenchmark); 

chinsoon <- function(d) { d[,id:=seq_along(Ref_Col)]; temp <- melt(d, meas=unique(d$Ref_Col), value.name="new_col")[Ref_Col==variable,]; setkey(d, id, Ref_Col); setkey(temp, id, Ref_Col); d[temp][ ,`:=`(id = NULL, variable = NULL)][]; }; 
bgoldst <- function(d) d[,new_col:=as.matrix(d[,unique(d[,Ref_Col]),with=F])[matrix(c(seq_len(nrow(d)),match(Ref_Col,unique(Ref_Col))),nrow(d))]]; 
symbolix <- function(d) { refs <- unique(d[, Ref_Col]); for(i in refs) d[ Ref_Col == i, eval(parse(text = paste0("new_col := ", i)))][ ]; d; }; 
arun <- function(d) d[,new_col:=get(Ref_Col),Ref_Col]; 

N <- 100L; d <- data.table(A=seq(1L,N%/%2),B=seq(N%/%2+1L,N),Ref_Col=rep(c('A','B'),N%/%2L)); 
identical(bgoldst(copy(d)),chinsoon(copy(d))); 
## [1] TRUE 
identical(bgoldst(copy(d)),{ x <- symbolix(copy(d)); attr(x,'index') <- NULL; x; }); ## irrelevant index attribute difference 
## [1] TRUE 
identical(bgoldst(copy(d)),arun(copy(d))); 
## [1] TRUE 

N <- 100L; d <- data.table(A=seq(1L,N%/%2),B=seq(N%/%2+1L,N),Ref_Col=rep(c('A','B'),N%/%2L)); 
microbenchmark(chinsoon(copy(d)),bgoldst(copy(d)),symbolix(copy(d)),arun(copy(d))); 
## Unit: microseconds 
##    expr  min  lq  mean median  uq  max neval 
## chinsoon(copy(d)) 2444.896 2516.955 2941.2385 2597.1400 3501.8410 6343.812 100 
## bgoldst(copy(d)) 1713.608 1790.799 2137.4168 1837.4135 2472.6930 4599.841 100 
## symbolix(copy(d)) 2175.901 2275.972 2769.9504 2354.8740 3173.6170 13897.454 100 
##  arun(copy(d)) 635.921 685.743 862.7615 722.7345 951.5295 4414.667 100 

N <- 1e4L; d <- data.table(A=seq(1L,N%/%2),B=seq(N%/%2+1L,N),Ref_Col=rep(c('A','B'),N%/%2L)); 
microbenchmark(chinsoon(copy(d)),bgoldst(copy(d)),symbolix(copy(d)),arun(copy(d))); 
## Unit: microseconds 
##    expr  min  lq  mean median  uq  max neval 
## chinsoon(copy(d)) 4603.262 4999.6975 7194.594 6277.311 7162.555 49217.352 100 
## bgoldst(copy(d)) 2511.609 2600.5610 3371.723 2682.029 3979.529 6738.964 100 
## symbolix(copy(d)) 2645.893 2761.1450 3588.282 2959.789 4190.149 15062.810 100 
##  arun(copy(d)) 770.204 849.5345 1048.795 880.753 1126.653 2831.495 100 

N <- 1e5L; d <- data.table(A=seq(1L,N%/%2),B=seq(N%/%2+1L,N),Ref_Col=rep(c('A','B'),N%/%2L)); 
microbenchmark(chinsoon(copy(d)),bgoldst(copy(d)),symbolix(copy(d)),arun(copy(d))); 
## Unit: milliseconds 
##    expr  min  lq  mean median  uq  max neval 
## chinsoon(copy(d)) 27.114512 32.982772 59.00385 70.976359 78.864641 131.06167 100 
## bgoldst(copy(d)) 9.732538 11.673015 19.02450 13.396672 16.624600 66.72976 100 
## symbolix(copy(d)) 6.787716 8.509448 11.07309 9.057487 10.523269 55.60692 100 
##  arun(copy(d)) 2.127149 2.380748 3.32179 2.813746 3.930136 6.83604 100 

So die Schlussfolgerung ist, dass meine Lösung etwas schneller für kleine Daten, aber da die Daten progressiv bekommt größer, Symbolix-Lösung wird deutlich schneller. Wir können mit einem hohen Maß an Vertrauen erraten, dass dies darauf zurückzuführen ist, dass die Referenzspalten kopiert werden müssen, um sie mit einer Indexmatrix zu indizieren, während Symbolix den intelligenteren Ansatz verwendet, einfach die Referenzspalten zu durchlaufen und sie zu indexieren zu einer Zeit. Dies ist ein Fall in R, wo Schleifen besser ist als Vektorisierung. +1 zu Symbolix.

Aktualisierung: Whoooooaaaaa! Nachdem ich Aruns Lösung hinzugefügt habe, bin ich erstaunt, wie viel schneller es ist, zusätzlich dazu, kürzer und eleganter zu sein als der Rest. Spiel, Satz und Spiel zu Arun.

Ich erinnere mich an die Linie Furious Aktivität ist kein Ersatz für das Verständnis.

+2

+10 für die Erklärungen, Aufwand & Benchmarking! – SymbolixAU

+1

upvoted für die Benckmarks! – chinsoon12

+0

Ich denke, wir haben hier heute alle etwas gelernt. danke @arun – SymbolixAU

2

Wie wäre es mit dem Schmelzen der ursprünglichen data.table dann Filter?

melt(d, meas=unique(d$Ref_Col), value.name="new_col")[Ref_Col==variable,] 

Die oben genannten erzeugen eine Teilmenge der OP-Ausgabespalten.


EDIT: identisch zu reproduzieren data.table

d[,id:=seq_along(Ref_Col)] 
temp <- melt(d, meas=unique(d$Ref_Col), value.name="new_col")[Ref_Col==variable,] 
setkey(d, id, Ref_Col) 
setkey(temp, id, Ref_Col) 
d[temp][ ,`:=`(id = NULL, variable = NULL)][] 
+0

netter Ansatz.Und um den Aufwand für das Setzen der Schlüssel zu vermeiden: 'd [, id: = seq_along (Ref_Col)] [schmelzen (d, meas = eindeutig (d $ Ref_Col), value.name =" new_col ") [Ref_Col == Variable ,], on = c ("id", "Ref_Col")] ' – SymbolixAU

2

einer Schleife ...

library(data.table) 
d<-data.table(A=1:10,B=11:20,Ref_Col=rep(c("A","B"),5)) 

refs <- unique(d[, Ref_Col]) 

for(i in refs) d[ Ref_Col == i, eval(parse(text = paste0("new_col := ", i)))][ ] 
d 
#  A B Ref_Col new_col 
# 1: 1 11  A  1 
# 2: 2 12  B  12 
# 3: 3 13  A  3 
# 4: 4 14  B  14 
# 5: 5 15  A  5 
# 6: 6 16  B  16 
# 7: 7 17  A  7 
# 8: 8 18  B  18 
# 9: 9 19  A  9 
# 10: 10 20  B  20 
Verwandte Themen