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.
Versuchen 'd [, new_col: = ifelse (Ref_Col == "A" , A, B)] ' – nicola
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
Versuchen Sie: 'd [, new_col: = get (Ref_Col), durch = Ref_Col]' – Arun