Đột biến nhiều cột một cách linh hoạt trong khi điều hòa trên các hàng cụ thể


11

Tôi biết có một số câu hỏi tương tự ở đây, nhưng không ai trong số chúng dường như giải quyết vấn đề chính xác mà tôi đang gặp phải.

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

Tôi muốn zeroise giá trị của các cột giá trị cho các hàng trong đó Key == "A" Các tên cột được tham chiếu qua a grep:

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

Thông thường để đạt được những gì tôi muốn trong trường hợp này, tôi sẽ sử dụng data.tablenhư thế này:

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

Và đầu ra mong muốn là như thế này:

  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

Tuy nhiên lần này tôi cần sử dụng dplyrvì tôi đang làm việc trong một dự án nhóm nơi mọi người sử dụng nó. Dữ liệu tôi vừa cung cấp có tính minh họa và dữ liệu thực của tôi là> 5m hàng với 16 cột giá trị sẽ được cập nhật. Giải pháp duy nhất tôi có thể đưa ra là sử dụng mutate_atnhư thế này:

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

Tuy nhiên, điều này dường như là cực kỳ chậm trên dữ liệu thực của tôi. Tôi đã hy vọng tìm ra một giải pháp thanh lịch hơn và quan trọng hơn là nhanh hơn.

Tôi đã thử nhiều kết hợp bằng cách sử dụng map, bỏ qua việc sử dụng !!, sử dụng get:=(điều khó chịu có thể bị che dấu bởi :=data.table), v.v., nhưng tôi nghĩ rằng sự hiểu biết của tôi về cách thức hoạt động của những công việc này không đủ sâu để xây dựng một giải pháp hợp lệ.


6
Điều này mất bao lâu? df [df $ Key == "A", cols] <- 0. Tôi có thể thấy rằng nó chậm vì bạn đang gọi ifelse và lặp qua các cột và hàng.
St nguWolf

St nguWolf, Điều này thực sự rất nhanh với dữ liệu của tôi, trong khi rất nhỏ gọn và thanh lịch. Cảm ơn. Hãy thêm nó như một câu trả lời nếu bạn muốn.
LiviusI

Ok tôi có thể chỉ cho bạn một giải pháp khác để khắc phục nó ..
StoolWolf

Câu trả lời:


9

Với lệnh dplyr này,

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

Bạn đang thực sự đánh giá câu lệnh df $ Key == "A", n lần, trong đó n = số cột bạn có.

Một cách giải quyết là xác định trước các hàng bạn muốn thay đổi:

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

Một cách sạch hơn và tốt hơn, được chỉ ra chính xác bởi @IceCreamToucan (xem các bình luận bên dưới), là sử dụng chức năng thay thế, trong khi truyền cho nó các tham số bổ sung:

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

Chúng tôi có thể đặt tất cả các phương pháp tiếp cận này để kiểm tra và tôi nghĩ rằng dplyr và data.table có thể so sánh được.

#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
các đối số bổ sung để đột biến được đánh giá một lần và được truyền dưới dạng tham số cho hàm được cung cấp (tương tự như lapply), vì vậy bạn có thể thực hiện việc này mà không cần tạo một biến tạm thời idx nhưdf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan

Cảm ơn bạn đã chỉ ra @IceCreamToucan, tôi không biết điều đó. Yup, chức năng thay thế thậm chí còn tốt hơn và ít vụng về hơn tôi. Tôi sẽ đưa nó vào câu trả lời nếu bạn không phiền chứ? (tín dụng cho bạn tất nhiên).
St nguWolf

Sau khi thử nghiệm trên máy của tôi, có vẻ như replacephương thức này chậm hơn một chút so với idxphương thức ban đầu của bạn .
IceCreamToucan

1
Ngoài ra tôi nghĩ dplyr::if_else()là nhanh hơn cơ sở ifelse().
sindri_baldur
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.