Динамічно змінюється декілька стовпців під час кондиціонування на конкретні рядки


11

Я знаю, що тут є декілька подібних питань, але, схоже, жодне з них не вирішує точну проблему, яку я маю.

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

Я хочу знецінити значення стовпців значень для рядків, де Key == "A" Імена стовпців посилаються через grep:

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

Зазвичай, щоб досягти того, що я хочу в цьому випадку, я би використовував data.tableтак:

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

І бажаний вихід такий:

  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

Однак цей час мені потрібно використовувати, dplyrколи я працюю над командним проектом, де його використовують усі. Наведені нами дані є ілюстративними, і мої реальні дані> 5м рядків з 16 стовпцями значення, які потрібно оновити. Єдине рішення, яке я міг би придумати, це використовувати mutate_atтаке:

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

Однак, на мої реальні дані це здається надзвичайно повільним. Я сподівався знайти рішення, яке є більш елегантним і, що важливіше, швидшим.

Я спробував багато комбінацій, використовуючи map, знімаючи цитати з використанням !!, використанням getта :=(що дратує може замаскуватися :=в data.table) тощо, але я думаю, що моє розуміння того, як ці роботи просто недостатньо глибокі, щоб побудувати правильне рішення.


6
скільки часу це займе? df [df $ Key == "A", cols] <- 0. Я можу бачити, що це повільно, тому що ви дзвоните ifelse і перекидаєтеся над стовпцями та рядками.
StupidWolf

StupidWolf, це насправді дуже швидко з моїми даними, при цьому дуже компактний і елегантний. Дякую. Ви можете додати його як відповідь, якщо бажаєте.
ЛівійІ

Гаразд, я можу показати вам ще одне рішення, щоб обійти його ..
StupidWolf

Відповіді:


9

За допомогою цієї команди dplyr,

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

Ви фактично оцінюєте вираз df $ Key == "A", n разів, де n = кількість стовпців у вас.

Одна із задач - попередньо визначити рядки, які потрібно змінити:

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

Чистіший і кращий спосіб, на який правильно вказав @IceCreamToucan (див. Коментарі нижче), - це використовувати функцію заміни, передаючи їй додаткові параметри:

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

Ми можемо поставити всі ці підходи для тестування, і я думаю, що dplyr та data.table порівнянні.

#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
додаткові аргументи мутувати обчислюються один раз і передаються в якості параметра функції , представленої (аналогічно , наприклад , lapply), так що ви можете зробити це без явного створення змінної IDX , як тимчасовоїdf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan

Дякую, що вказав на це @IceCreamToucan, я цього не знав. Так, функція заміни ще краща і менш незграбна, ніж я. Я включу його у відповідь, якщо ви не заперечуєте? (кредит вам, звичайно).
StupidWolf

Після тестування на моїй машині, здається, replaceметод трохи повільніше, ніж ваш початковий idxметод.
IceCreamToucan

1
Також я думаю, що dplyr::if_else()це швидше, ніж база ifelse().
sindri_baldur
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.