Mutazione dinamica di più colonne durante il condizionamento su righe specifiche


11

So che ci sono molte domande simili qui intorno, ma nessuna sembra affrontare il problema preciso che sto riscontrando.

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

Voglio azzerare i valori delle colonne dei valori per le righe in cui Key == "A" I nomi delle colonne sono referenziati attraverso un grep:

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

Normalmente per ottenere quello che voglio in questo caso lo userei data.tablecosì:

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

E l'output desiderato è così:

  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

Tuttavia, questa volta ho bisogno di usare dplyrcome sto lavorando a un progetto di squadra in cui tutti lo usano. I dati che ho appena fornito sono illustrativi e i miei dati reali sono> 5m righe con 16 colonne di valori da aggiornare. L'unica soluzione che ho potuto trovare è usare in mutate_atquesto modo:

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

Tuttavia, questo sembra essere estremamente lento sui miei dati reali. Speravo di trovare una soluzione più elegante e, soprattutto, più veloce.

Ho provato molte combinazioni usando map, annullando la citazione usando !!, usando gete :=(che può essere fastidiosamente mascherato da in :=in data.table) ecc., Ma penso che la mia comprensione di come questi lavori semplicemente non sia abbastanza profonda per costruire una soluzione valida.


6
quanto tempo ci vuole? df [df $ chiave == "A", colli] <- 0. Vedo che è lento perché stai chiamando ifelse e esegui il ciclo su colonne e righe.
Stupido lupo,

StupidWolf, in realtà è molto veloce con i miei dati, pur essendo molto compatto ed elegante. Grazie. Sentiti libero di aggiungerlo come risposta se lo desideri.
Livio

Ok, posso mostrarti un'altra soluzione per
aggirarlo

Risposte:


9

Con questo comando dplyr,

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

Stai effettivamente valutando l'istruzione df $ Key == "A", n volte, dove n = il numero di colonne che hai.

Una soluzione è quella di pre-definire le righe che si desidera modificare:

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

Un modo più pulito e migliore, sottolineato correttamente da @IceCreamToucan (vedi commenti sotto), è quello di utilizzare la funzione di sostituzione, mentre le passa i parametri extra:

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

Possiamo mettere alla prova tutti questi approcci e penso che dplyr e data.table siano comparabili.

#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

4
argomenti aggiuntivi da mutare vengono valutati una volta e passati come parametro alla funzione fornita (simile ad es. lapply), quindi puoi farlo senza creare esplicitamente la variabile temp idx comedf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan

Grazie per averlo segnalato @IceCreamToucan, non lo sapevo. Sì, la funzione di sostituzione è ancora migliore e meno goffa di me. Lo includerò nella risposta se non ti dispiace? (merito a te ovviamente).
StupidWolf,

Dopo aver testato sulla mia macchina, sembra che il replacemetodo sia un po 'più lento del idxmetodo originale .
IceCreamToucan,

1
Inoltre penso che dplyr::if_else()sia più veloce della base ifelse().
sindri_baldur,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.