Dynamisches Mutieren mehrerer Spalten beim Konditionieren bestimmter Zeilen

11

Ich weiß, dass es hier einige ähnliche Fragen gibt, aber keine scheint das genaue Problem anzusprechen, das ich habe.

set.seed(4)
df = data.frame(
  Key = c("A", "B", "A", "D", "A"),
  Val1 = rnorm(5),
  Val2 = runif(5),
  Val3 = 1:5
)

Ich möchte die Werte der Wertespalten für die Zeilen auf Null setzen, in denen Key == "A" angegeben ist. Die Spaltennamen werden durch Folgendes referenziert grep:

cols = grep("Val", names(df), value = TRUE)

Normalerweise würde ich Folgendes verwenden, um das zu erreichen, was ich in diesem Fall möchte data.table:

library(data.table)
df = as.data.table(df)
df[Key == "A", (cols) := 0]

Und die gewünschte Ausgabe ist wie folgt:

  Key      Val1       Val2 Val3
1   A  0.000000 0.00000000    0
2   B -1.383814 0.55925762    2
3   A  0.000000 0.00000000    0
4   D  1.437151 0.05632773    4
5   A  0.000000 0.00000000    0

Dieses Mal muss ich es jedoch verwenden, dplyrda ich an einem Teamprojekt arbeite, in dem jeder es verwendet. Die Daten, die ich gerade bereitgestellt habe, sind illustrativ und meine realen Daten sind> 5 Millionen Zeilen mit 16 zu aktualisierenden Wertespalten. Die einzige Lösung, die ich finden könnte, ist folgende mutate_at:

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

Dies scheint jedoch bei meinen realen Daten extrem langsam zu sein. Ich hatte gehofft, eine Lösung zu finden, die eleganter und vor allem schneller ist.

Ich habe viele Kombinationen mit map, ohne Anführungszeichen mit !!, mit getund :=(die ärgerlicherweise durch die :=in data.table maskiert werden können) usw. ausprobiert, aber ich denke, mein Verständnis, wie diese Arbeit einfach nicht tief genug ist, um eine gültige Lösung zu konstruieren.

LiviusI
quelle
6
Wie lange dauert das? df [df $ Key == "A", cols] <- 0. Ich kann sehen, dass es langsam ist, weil Sie ifelse aufrufen und die Spalten und Zeilen durchlaufen.
StupidWolf
StupidWolf, das geht mit meinen Daten eigentlich sehr schnell, ist aber sehr kompakt und elegant. Vielen Dank. Fühlen Sie sich frei, es als Antwort hinzuzufügen, wenn Sie es wünschen.
Livius I.
Ok, ich kann Ihnen eine andere Lösung zeigen, um es zu
umgehen

Antworten:

9

Mit diesem Befehl dplyr

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

Sie werten tatsächlich die Anweisung df $ Key == "A" n-mal aus, wobei n = die Anzahl der Spalten ist, die Sie haben.

Eine Problemumgehung besteht darin, die Zeilen, die Sie ändern möchten, vorab zu definieren:

idx = which(DF$Key=="A")
DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})

Ein sauberer und besserer Weg, auf den @IceCreamToucan richtig hingewiesen hat (siehe Kommentare unten), besteht darin, die Funktion replace zu verwenden und dabei die zusätzlichen Parameter zu übergeben:

DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0)

Wir können all diese Ansätze testen, und ich denke, dplyr und data.table sind vergleichbar.

#simulate data
set.seed(100)
Key = sample(LETTERS[1:3],1000000,replace=TRUE)
DF = as.data.frame(data.frame(Key,matrix(runif(1000000*10),nrow=1000000,ncol=10)))
DT = as.data.table(DF)

cols = grep("[35789]", names(DF), value = TRUE)

#long method
system.time(DF %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(DF$Key == "A", 0, x)))
user  system elapsed 
  0.121   0.035   0.156 

#old base R way
system.time(DF[idx,cols] <- 0)
   user  system elapsed 
  0.085   0.021   0.106 

#dplyr
# define function
func = function(){
       idx = which(DF$Key=="A")
       DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})
}
system.time(func())
user  system elapsed 
  0.020   0.006   0.026

#data.table
system.time(DT[Key=="A", (cols) := 0])
   user  system elapsed 
  0.012   0.001   0.013 
#replace with dplyr
system.time(DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0))
user  system elapsed 
  0.007   0.001   0.008
Blöder Wolf
quelle
4
Zusätzliche zu mutierende Argumente werden einmal ausgewertet und als Parameter an die bereitgestellte Funktion übergeben (ähnlich wie z. B. lapply). Sie können dies also tun, ohne die temporäre Variable idx explizit alsdf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan
Vielen Dank für den Hinweis auf @IceCreamToucan, ich wusste es nicht. Ja, die Ersetzungsfunktion ist noch besser und weniger ungeschickt als ich. Ich werde es in die Antwort aufnehmen, wenn es Ihnen nichts ausmacht? (Gutschrift natürlich).
StupidWolf
Nach dem Testen auf meinem Computer scheint die replaceMethode etwas langsamer zu sein als Ihre ursprüngliche idxMethode.
IceCreamToucan
1
Auch ich denke dplyr::if_else()ist schneller als Basis ifelse().
Sindri_baldur