Dynamiczne mutowanie wielu kolumn podczas warunkowania określonych wierszy


11

Wiem, że jest tu kilka podobnych pytań, ale wydaje się, że żadne z nich nie odnosi się do konkretnego problemu, który mam.

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

Chcę zerować wartości kolumn wartości dla wierszy, w których Key == "A" Do nazw kolumn odwołuje się grep:

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

Zwykle, aby osiągnąć to, co chcę w tym przypadku, użyłbym data.tabletak:

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

A pożądany wynik jest następujący:

  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

Jednak tym razem muszę go wykorzystać dplyr, pracując nad projektem zespołowym, w którym wszyscy go używają. Przedstawione przeze mnie dane mają charakter poglądowy, a moje rzeczywiste dane to> 5 m wierszy z 16 kolumnami wartości do aktualizacji. Jedyne rozwiązanie, jakie mogłem wymyślić, to mutate_attakie:

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

Wydaje się jednak, że tak bardzo powolne w przypadku moich prawdziwych danych. Miałem nadzieję znaleźć rozwiązanie, które byłoby bardziej eleganckie i, co ważniejsze, szybsze.

Próbowałem wielu kombinacji przy użyciu map, bez cytowania przy użyciu !!, przy użyciu geti :=(które irytujące mogą zostać zamaskowane :=w tabeli data.tab) itp., Ale myślę, że moje rozumienie tego, jak te prace nie są po prostu wystarczająco głębokie, aby stworzyć prawidłowe rozwiązanie.


6
jak długo to trwa? df [df $ Key == "A", cols] <- 0. Widzę, że jest powolny, ponieważ wywołujesz ifelse i zapętlasz kolumny i wiersze.
— StupidWolf,

StupidWolf, To jest naprawdę bardzo szybkie z moimi danymi, a jednocześnie jest bardzo kompaktowe i eleganckie. Dzięki. Jeśli chcesz, możesz dodać ją jako odpowiedź.
— LiviusI

Ok, mogę pokazać wam inne rozwiązanie tego problemu.
— StupidWolf,

Odpowiedzi:


9

Za pomocą tego polecenia dplyr

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

Rzeczywiście oceniasz wyrażenie df $ Key == "A", n razy, gdzie n = liczba kolumn, które masz.

Jednym z obejść jest wstępne zdefiniowanie wierszy, które chcesz zmienić:

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

Czystszym i lepszym sposobem, poprawnie wskazanym przez @IceCreamToucan (patrz komentarze poniżej), jest użycie funkcji replace, z jednoczesnym przekazaniem jej dodatkowych parametrów:

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

Możemy przetestować wszystkie te podejścia i uważam, że dplyr i data.table są porównywalne.

#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
dodatkowe argumenty do mutacji są oceniane raz i przekazywane jako parametr do podanej funkcji (podobnie jak np. lapply), więc możesz to zrobić bez jawnego tworzenia zmiennej temp idx asdf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
— IceCreamToucan

Dzięki za zwrócenie uwagi na @IceCreamToucan, nie wiedziałem o tym. Tak, funkcja zamiany jest jeszcze lepsza i mniej niezręczna ode mnie. Dołączę to do odpowiedzi, jeśli nie masz nic przeciwko? (Podziękowania dla ciebie oczywiście).
— StupidWolf,

Po przetestowaniu na mojej maszynie wydaje się, że replacemetoda jest nieco wolniejsza niż oryginalna idx.
— IceCreamToucan

1
Myślę też, że dplyr::if_else()jest szybszy niż baza ifelse().
— sindri_baldur
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.