dplyr mutate z wartościami warunkowymi

87

W dużej ramce danych („myfile”) z czterema kolumnami muszę dodać piątą kolumnę z wartościami warunkowo opartymi na pierwszych czterech kolumnach.

Preferuj odpowiedzi z dplyri mutate, głównie ze względu na szybkość w dużych zbiorach danych.

Moja ramka danych wygląda następująco:

  V1 V2 V3 V4
1  1  2  3  5
2  2  4  4  1
3  1  4  1  1
4  4  5  1  3
5  5  5  5  4
...

Wartości z piątej kolumny (V5) są oparte na pewnych regułach warunkowych:

if (V1==1 & V2!=4) {
  V5 <- 1
} else if (V2==4 & V3!=1) {
  V5 <- 2
} else {
  V5 <- 0
}

Teraz chcę użyć mutatefunkcji, aby użyć tych reguł we wszystkich wierszach (aby uniknąć powolnych pętli). Coś takiego (i tak, wiem, że to nie działa!):

myfile <- mutate(myfile, if (V1==1 & V2!=4){V5 = 1}
    else if (V2==4 & V3!=1){V5 = 2}
    else {V5 = 0})

Powinien to być wynik:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

Jak to zrobić w dplyr?

rdatasculptor
źródło
Przydatne jest stwierdzenie, czy wszystkie wersje V1..4 są liczbami całkowitymi (a nie współczynnikiem, logiką, ciągiem lub zmiennoprzecinkiem)? a czy zależy Ci na prawidłowej obsłudze NA, ( NaN, +Inf, -Inf)?
smci
Jeśli prędkość wydaje się być problemem do preferowania dplyr, lepiej skorzystałbym z data.table.
Valentin

Odpowiedzi:

105

Spróbuj tego:

myfile %>% mutate(V5 = (V1 == 1 & V2 != 4) + 2 * (V2 == 4 & V3 != 1))

dający:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

albo to:

myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, ifelse(V2 == 4 & V3 != 1, 2, 0)))

dający:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

Uwaga

Zaproponuj lepszą nazwę ramki danych. myfile sprawia, że ​​wygląda na to, że przechowuje nazwę pliku.

Powyżej użyłem tego wejścia:

myfile <- 
structure(list(V1 = c(1L, 2L, 1L, 4L, 5L), V2 = c(2L, 4L, 4L, 
5L, 5L), V3 = c(3L, 4L, 1L, 1L, 5L), V4 = c(5L, 1L, 1L, 3L, 4L
)), .Names = c("V1", "V2", "V3", "V4"), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5"))

Aktualizacja 1 Ponieważ pierwotnie opublikowano dplyr, zmienił się %.%na, %>%więc odpowiednio zmodyfikował odpowiedź.

Update 2 dplyr ma teraz case_wheninne rozwiązanie:

myfile %>% 
       mutate(V5 = case_when(V1 == 1 & V2 != 4 ~ 1, 
                             V2 == 4 & V3 != 1 ~ 2,
                             TRUE ~ 0))
G. Grothendieck
źródło
Wypróbowałem twoje drugie rozwiązanie. Otrzymałem ten błąd: Błąd w mutate_impl (.data, named_dots (...), environment ()): REAL () można zastosować tylko do „numerycznej”, a nie „logicznej”. Czy wiesz, co się dzieje?
rdatasculptor
5
Odkryłem sposób, który pozwala nie zagnieżdżać ifelsestwierdzeń:myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex,
31

Za pomocą dplyr 0.7.2możesz skorzystać z bardzo przydatnej case_whenfunkcji:

x=read.table(
 text="V1 V2 V3 V4
 1  1  2  3  5
 2  2  4  4  1
 3  1  4  1  1
 4  4  5  1  3
 5  5  5  5  4")
x$V5 = case_when(x$V1==1 & x$V2!=4 ~ 1,
                 x$V2==4 & x$V3!=1 ~ 2,
                 TRUE ~ 0)

Wyrażony za pomocą dplyr::mutatedaje:

x = x %>% mutate(
     V5 = case_when(
         V1==1 & V2!=4 ~ 1,
         V2==4 & V3!=1 ~ 2,
         TRUE ~ 0
     )
)

Należy pamiętać, że NAnie są one traktowane specjalnie, ponieważ mogą wprowadzać w błąd. Funkcja zwróci NAtylko wtedy, gdy żaden warunek nie zostanie spełniony. Jeśli umieścisz linię z TRUE ~ ..., tak jak w moim przykładzie, wartość zwracana nigdy nie będzie NA.

Dlatego musisz wyraźnie powiedzieć, case_whenaby umieścić NAtam, gdzie należy, dodając stwierdzenie takie jak is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Podpowiedź: dplyr::coalesce()funkcja może się tu czasem przydać!

Ponadto, należy pamiętać, że NAsam będzie zazwyczaj nie praca, trzeba umieścić specjalne NAwartości: NA_integer_, NA_character_lub NA_real_.

Dan Chaltiel
źródło
1
Było to znacznie szybsze niż deratedFactor.
Fato39
12

Wygląda na to, że derivedFactorz mosaicpakietu został zaprojektowany do tego. W tym przykładzie wyglądałoby to mniej więcej tak:

library(mosaic)
myfile <- mutate(myfile, V5 = derivedFactor(
    "1" = (V1==1 & V2!=4),
    "2" = (V2==4 & V3!=1),
    .method = "first",
    .default = 0
    ))

(Jeśli chcesz, aby wynik był liczbowy zamiast współczynnika, zawiń derivedFactorgo as.numeric.)

Zauważ, że .defaultopcja połączona z .method = "first"ustawia warunek „else” - to podejście jest opisane w pliku pomocy dla derivedFactor.

Jake Fisher
źródło
Możesz również zapobiec byciu czynnikiem, używając .asFactor = Fopcji lub używając (podobnej) derivedVariablefunkcji w tym samym pakiecie.
Jake Fisher
Wygląda na to, że recodedplyr 0.5 to zrobi. Jednak jeszcze tego nie zbadałem. Zobacz blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher
To było powolne w przypadku moich danych z wierszami 1e6.
Fato39
3
@ Fato39 Tak, mosaic::derivedFactorrodzina funkcji działa bardzo wolno. Jeśli dowiesz się dlaczego, odpowiedz na moje pytanie SO dotyczące tego: stackoverflow.com/questions/33787691/… . Cieszę się, widząc po Twoim drugim komentarzu, który dplyr::case_whenjest szybszy - będę musiał się na to przełączyć.
Jake Fisher,
Próbuję wykonać następujące polecenie, biblioteka (mozaika) VENEZ.FINAL2 <- mutate (VENEZ, SEX = derFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")), ale to nie działa, po prostu rozwiąż warunek VENEZ.FINAL2 <- mutate (VENEZ, SEX =ivedFactor ("M" = (CATEGORY == "BULL Czy możesz mi pomóc? Wielkie dzięki!
Johanna Ramirez