dplyr mutiert mit bedingten Werten

88

In einem großen Datenrahmen ("myfile") mit vier Spalten muss ich eine fünfte Spalte mit Werten hinzufügen, die bedingt auf den ersten vier Spalten basieren.

Bevorzugen Sie Antworten mit dplyrund mutate, hauptsächlich wegen der Geschwindigkeit in großen Datenmengen.

Mein Datenrahmen sieht folgendermaßen aus:

  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
...

Die Werte der fünften Spalte (V5) basieren auf einigen bedingten Regeln:

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

Jetzt möchte ich die mutateFunktion verwenden, um diese Regeln für alle Zeilen zu verwenden (um langsame Schleifen zu vermeiden). So etwas (und ja, ich weiß, dass es so nicht funktioniert!):

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

Dies sollte das Ergebnis sein:

  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

Wie geht das dplyr?

rdatasculptor
quelle
Es ist nützlich anzugeben, ob V1..4 alle Ganzzahlen sind (nicht Faktor, Logik, Zeichenfolge oder Float). und kümmert es dich um die richtige Handhabung NA, ( NaN, +Inf, -Inf)?
smci
Wenn Geschwindigkeit ein Problem für den Vorzug zu sein scheint dplyr, dann würde ich besser verwenden data.table.
Valentin

Antworten:

108

Versuche dies:

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

Geben:

  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

oder dieses:

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

Geben:

  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

Hinweis

Schlagen Sie vor, dass Sie einen besseren Namen für Ihren Datenrahmen erhalten. myfile lässt es so aussehen, als ob es einen Dateinamen enthält.

Oben verwendet diese Eingabe:

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"))

Update 1 Da ursprünglich dplyr geändert wurde %.%, wurde %>%die Antwort entsprechend geändert.

Update 2 dplyr hat jetzt case_wheneine andere Lösung:

myfile %>% 
       mutate(V5 = case_when(V1 == 1 & V2 != 4 ~ 1, 
                             V2 == 4 & V3 != 1 ~ 2,
                             TRUE ~ 0))
G. Grothendieck
quelle
Ich habe deine zweite Lösung ausprobiert. Ich habe diesen Fehler erhalten: Fehler in mutate_impl (.data, named_dots (...), environment ()): REAL () kann nur auf eine 'numerische', nicht auf eine 'logische' angewendet werden. Wissen Sie, was falsch läuft?
rdatasculptor
5
Ich habe einen Weg gefunden, der es Ihnen ermöglicht, die ifelseAussagen nicht zu verschachteln :myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex
32

Mit dplyr 0.7.2können Sie die sehr nützliche case_whenFunktion verwenden:

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)

Ausgedrückt mit dplyr::mutategibt es:

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

Bitte beachten Sie, dass diese NAnicht speziell behandelt werden, da dies irreführend sein kann. Die Funktion wird NAnur zurückgegeben, wenn keine Bedingung erfüllt ist. Wenn Sie TRUE ~ ...wie in meinem Beispiel eine Zeile mit setzen , wird der Rückgabewert dann niemals sein NA.

Daher müssen Sie ausdrücklich angeben case_when, NAwo es hingehört, indem Sie eine Anweisung wie hinzufügen is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Hinweis: Die dplyr::coalesce()Funktion kann hier manchmal sehr nützlich sein!

Darüber hinaus beachten Sie bitte , dass NAallein in der Regel nicht arbeiten, müssen Sie besondere setzen NAWerte: NA_integer_, NA_character_oder NA_real_.

Dan Chaltiel
quelle
1
Dies war signifikant schneller als derivativeFactor.
Fato39
12

Es sieht so derivedFactoraus, als ob das mosaicPaket dafür entworfen wurde. In diesem Beispiel würde es ungefähr so ​​aussehen:

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

(Wenn Sie möchten, dass das Ergebnis anstelle eines Faktors numerisch ist, schließen Sie das derivedFactormit einem as.numeric.)

Beachten Sie, dass die .defaultOption in Kombination mit .method = "first"die Bedingung "else" festlegt. Dieser Ansatz wird in der Hilfedatei für beschrieben derivedFactor.

Jake Fisher
quelle
Sie können auch verhindern, dass das Ergebnis ein Faktor ist, indem Sie die .asFactor = FOption oder die (ähnliche) derivedVariableFunktion im selben Paket verwenden.
Jake Fisher
Es sieht so recodeaus, als würde dplyr 0.5 dies tun. Ich habe es aber noch nicht untersucht. Siehe blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher
Dies war langsam für meine Daten mit 1e6 Zeilen.
Fato39
3
@ Fato39 Ja, die mosaic::derivedFactorFunktionsfamilie ist sehr langsam. Wenn Sie herausfinden, warum, beantworten Sie bitte meine SO-Frage dazu: stackoverflow.com/questions/33787691/… . Ich bin froh, dass Ihr anderer Kommentar dplyr::case_whenschneller ist - ich muss darauf umsteigen.
Jake Fisher
Ich versuche den folgenden Befehl, Bibliothek (Mosaik) VENEZ.FINAL2 <- mutieren (VENEZ, SEX = derivativeFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")) aber es funktioniert nicht, lösen Sie einfach die Bedingung VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivativeFactor ("M" = (CATEGORY == "BULL Könnten Sie mir helfen? Vielen Dank!
Johanna Ramirez