czy pakiet dplyr może być używany do warunkowego mutowania?

Czy mutat może być użyty, gdy mutacja jest warunkowa (w zależności od wartości określonych wartości kolumn)?

Ten przykład pomaga pokazać, co mam na myśli.

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame")

  a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2

Miałem nadzieję znaleźć rozwiązanie mojego problemu używając pakietu dplyr (i tak Wiem, że to nie kod, który powinien działać, ale myślę, że to wyjaśnia cel) do stworzenia nowej kolumny g:

  library(dplyr)
 df <- mutate(df, if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
        if (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4){g = 3})

Wynik kodu, którego szukam powinien mieć taki wynik w tym konkretnym przykład:

  a b c d e f  g
1 1 1 6 6 1 2  3
2 3 3 3 2 2 3  3
3 4 4 6 4 4 4  3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7  2
7 5 2 5 2 6 5  2
8 1 6 3 6 3 2  3
Czy ktoś ma pomysł jak to zrobić w dplyr? Ta ramka danych to tylko przykład, ramki danych, z którymi mam do czynienia, są znacznie większe. Ze względu na jego szybkość próbowałem użyć dplyr, ale być może są inne, lepsze sposoby na poradzenie sobie z tym problemem?
 117
Author: Matsemann, 2014-06-27

5 answers

ifelse

df %>%
  mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               ifelse(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA)))

Dodano-if_else: zauważ, że w dplyr 0.5 istnieje funkcja if_else zdefiniowana tak, że alternatywą byłoby zastąpienie ifelse przez if_else; jednak zauważ, że ponieważ if_else jest bardziej rygorystyczne niż ifelse (obie nogi warunku muszą mieć ten sam typ), więc NA W takim przypadku musi być zastąpiona przez NA_real_ .

df %>%
  mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               if_else(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA_real_)))

Dodano-case_when ponieważ to pytanie zostało wysłane dplyr dodał case_when więc inna alternatywa byłaby be:

df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
                            a == 0 | a == 1 | a == 4 | a == 3 |  c == 4 ~ 3,
                            TRUE ~ NA_real_))
 139
Author: G. Grothendieck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-18 13:54:00

Ponieważ pytasz o inne lepsze sposoby radzenia sobie z problemem, oto inny sposób za pomocą data.table:

require(data.table) ## 1.9.2+
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]

Uwaga kolejność instrukcji warunkowych jest odwrócona, aby uzyskać g poprawnie. Nie wykonano żadnej kopii g, nawet podczas drugiego zadania-zastąpiono ją na miejscu .

Na większych danych miałoby to lepszą wydajność niż użycie zagnieżdżonego if-else, ponieważ może oceniać zarówno przypadki "tak", jak i " nie " , A zagnieżdżanie może być trudniejsze do odczytania/utrzymania IMHO.


Oto benchmark na relatywnie większych danych:

# R version 3.1.0
require(data.table) ## 1.9.2
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT) 
# [1] 10000000        6
DF <- as.data.frame(DT)

DT_fun <- function(DT) {
    DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
    DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
    mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

BASE_fun <- function(DF) { # R v3.1.0
    transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

system.time(ans1 <- DT_fun(DT))
#   user  system elapsed 
#  2.659   0.420   3.107 

system.time(ans2 <- DPLYR_fun(DF))
#   user  system elapsed 
# 11.822   1.075  12.976 

system.time(ans3 <- BASE_fun(DF))
#   user  system elapsed 
# 11.676   1.530  13.319 

identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE

identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE
Nie wiem, czy to alternatywa, o którą prosiłeś, ale mam nadzieję, że pomoże.
 45
Author: Arun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-20 16:22:47

Dplyr ma teraz funkcję case_when, która oferuje wektoryzowane if. Składnia jest nieco dziwna w porównaniu do mosaic:::derivedFactor , ponieważ nie można uzyskać dostępu do zmiennych w standardowy sposób dplyr i trzeba zadeklarować tryb NA, ale jest to znacznie szybsze niż mosaic:::derivedFactor.

df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                     a %in% c(0,1,3,4) | c == 4 ~ 3L, 
                     TRUE~as.integer(NA)))

EDIT: Jeśli używasz dplyr::case_when() przed wersją 0.7.0 pakietu, musisz poprzedzić nazwy zmiennych '.$' (np. napisz .$a == 1 wewnątrz case_when).

Benchmark : Dla benchmarka (reusing functions from Arun 's post) and reduction sample size:

require(data.table) 
require(mosaic) 
require(dplyr)
require(microbenchmark)

DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)

DPLYR_case_when <- function(DF) {
  DF %>%
  mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                       a %in% c(0,1,3,4) | c==4 ~ 3L, 
                       TRUE~as.integer(NA)))
}

DT_fun <- function(DT) {
  DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
  DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
  mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
                    ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

mosa_fun <- function(DF) {
  mutate(DF, g = derivedFactor(
    "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
    "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
    .method = "first",
    .default = NA
  ))
}

microbenchmark(
  DT_fun(DT),
  DPLYR_fun(DF),
  DPLYR_case_when(DF),
  mosa_fun(DF),
  times=20
)

Daje to:

            expr        min         lq       mean     median         uq        max neval
         DT_fun(DT)   1.503589   1.626971   2.054825   1.755860   2.292157   3.426192    20
      DPLYR_fun(DF)   2.420798   2.596476   3.617092   3.484567   4.184260   6.235367    20
DPLYR_case_when(DF)   2.153481   2.252134   6.124249   2.365763   3.119575  72.344114    20
       mosa_fun(DF) 396.344113 407.649356 413.743179 412.412634 416.515742 459.974969    20
 28
Author: Matifou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-02 19:25:33

Funkcja derivedFactor z pakietu mosaic wydaje się być zaprojektowana do obsługi tego. Używając tego przykładu, wyglądałoby to następująco:

library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
     "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
     "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
     .method = "first",
     .default = NA
     ))

(Jeśli chcesz, aby wynik był liczbowy zamiast współczynnika, możesz zawinąć derivedFactor w wywołanie as.numeric.)

derivedFactor może być również używany do dowolnej liczby warunków.

 13
Author: Jake Fisher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-22 20:17:46

case_when jest teraz całkiem czystą implementacją przypadku w stylu SQL, gdy:

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame") -> df


df %>% 
    mutate( g = case_when(
                a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 )     ~   2,
                a == 0 | a == 1 | a == 4 |  a == 3 | c == 4       ~   3
))

Używając dplyr 0.7.4

Instrukcja: http://dplyr.tidyverse.org/reference/case_when.html

 8
Author: Rasmus Larsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-12 11:03:45