Bermutasi beberapa kolom secara dinamis sambil mengkondisikan pada baris tertentu

11

Saya tahu ada beberapa pertanyaan serupa di sekitar sini, tetapi tidak satu pun dari mereka yang membahas masalah yang sebenarnya saya alami.

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

Saya ingin memusatkan nilai dari nilai kolom untuk baris di mana Kunci == "A" Nama kolom direferensikan melalui grep:

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

Biasanya untuk mencapai apa yang saya inginkan dalam hal ini saya akan menggunakan data.tableseperti ini:

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

Dan output yang diinginkan adalah seperti ini:

  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

Namun kali ini saya perlu menggunakan dplyrkarena saya sedang mengerjakan proyek tim di mana semua orang menggunakannya. Data yang baru saja saya berikan bersifat ilustratif dan data saya yang sebenarnya adalah> 5m baris dengan 16 kolom nilai yang akan diperbarui. Satu-satunya solusi yang bisa saya lakukan adalah menggunakan mutate_atseperti ini:

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

Namun, ini tampaknya sangat lambat pada data saya yang sebenarnya. Saya berharap menemukan solusi yang lebih elegan dan, yang lebih penting, lebih cepat.

Saya telah mencoba banyak kombinasi menggunakan map, menghapus tanda kutip menggunakan !!, menggunakan getdan :=(yang mengganggu bisa tertutup oleh :=data.table) dll, tapi saya pikir pemahaman saya tentang bagaimana pekerjaan ini tidak cukup dalam untuk membangun solusi yang valid.

LiviusI
sumber
6
Berapa lama waktu yang dibutuhkan? df [df $ Key == "A", cols] <- 0. Saya dapat melihat bahwa itu lambat karena Anda memanggil ifelse dan mengulangi kolom dan baris.
StupidWolf
StupidWolf, Ini sebenarnya sangat cepat dengan data saya, sementara sangat kompak dan elegan. Terima kasih. Silakan menambahkannya sebagai jawaban jika Anda mau.
LiviusI
Ok, saya bisa tunjukkan solusi lain untuk menyiasatinya ..
StupidWolf

Jawaban:

9

Dengan perintah dplyr ini,

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

Anda sebenarnya mengevaluasi pernyataan df $ Key == "A", n kali, di mana n = jumlah kolom yang Anda miliki.

Salah satu penyelesaiannya adalah menentukan terlebih dahulu baris yang ingin Anda ubah:

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

Cara yang lebih bersih dan lebih baik, ditunjukkan dengan benar oleh @IceCreamToucan (lihat komentar di bawah), adalah dengan menggunakan fungsi ganti, sambil memberikan parameter tambahan:

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

Kita dapat menguji semua pendekatan ini, dan saya pikir dplyr dan data.table sebanding.

#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
Bodoh Bodoh
sumber
4
argumen tambahan untuk bermutasi dievaluasi satu kali dan diteruskan sebagai parameter ke fungsi yang disediakan (mirip dengan misalnya lapply), sehingga Anda dapat melakukan ini tanpa secara eksplisit membuat variabel temp idx asdf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan
Terima kasih telah menunjukkannya @IceCreamToucan, saya tidak mengetahuinya. Yup, fungsi ganti bahkan lebih baik, dan kurang canggung dari saya. Saya akan memasukkannya dalam jawaban jika Anda tidak keberatan? (kredit untuk Anda tentu saja).
StupidWolf
Setelah menguji pada komputer saya, tampaknya replacemetode ini sedikit lebih lambat daripada idxmetode awal Anda .
IceCreamToucan
1
Juga saya pikir dplyr::if_else()lebih cepat dari pangkalan ifelse().
sindri_baldur