dplyr mute avec des valeurs conditionnelles

87

Dans un grand dataframe ("myfile") avec quatre colonnes, je dois ajouter une cinquième colonne avec des valeurs conditionnellement basées sur les quatre premières colonnes.

Préférez les réponses avec dplyret mutate, principalement en raison de sa rapidité dans les grands ensembles de données.

Mon dataframe ressemble à ceci:

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

Les valeurs de la cinquième colonne (V5) sont basées sur certaines règles conditionnelles:

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

Maintenant, je veux utiliser la mutatefonction pour utiliser ces règles sur toutes les lignes (pour éviter les boucles lentes). Quelque chose comme ça (et oui, je sais que ça ne marche pas comme ça!):

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

Cela devrait être le résultat:

  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

Comment faire cela dplyr?

rdatasculptor
la source
Il est utile de dire si V1..4 sont tous des nombres entiers (pas de facteur, logique, chaîne ou flottant)? et vous souciez-vous de la bonne manipulation NA, ( NaN, +Inf, -Inf)?
smci
Si la vitesse semble être un problème pour préférer dplyr, alors je ferais mieux de l'utiliser data.table.
Valentin

Réponses:

105

Essaye ça:

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

donnant:

  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

ou ca:

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

donnant:

  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

Remarque

Suggérez-vous d'obtenir un meilleur nom pour votre bloc de données. myfile donne l'impression qu'il contient un nom de fichier.

Ci-dessus utilisé cette entrée:

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

Mise à jour 1 Depuis la publication initiale de dplyr a changé %.%pour %>%donc avoir modifié la réponse en conséquence.

La mise à jour 2 dplyr a maintenant case_whenune autre solution:

myfile %>% 
       mutate(V5 = case_when(V1 == 1 & V2 != 4 ~ 1, 
                             V2 == 4 & V3 != 1 ~ 2,
                             TRUE ~ 0))
G. Grothendieck
la source
J'ai essayé votre deuxième solution. J'ai eu cette erreur: Erreur dans mutate_impl (.data, named_dots (...), environment ()): REAL () ne peut être appliqué qu'à un 'numérique', pas à un 'logique' Savez-vous ce qui ne va pas?
rdatasculptor
5
J'ai découvert un moyen qui permet de ne pas imbriquer les ifelsedéclarations:myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex
31

Avec dplyr 0.7.2, vous pouvez utiliser la case_whenfonction très utile :

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)

Exprimé avec dplyr::mutate, il donne:

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

Veuillez noter qu'ils NAne sont pas traités spécialement, car cela peut être trompeur. La fonction ne sera renvoyée NAque si aucune condition ne correspond. Si vous mettez une ligne avec TRUE ~ ..., comme je l'ai fait dans mon exemple, la valeur de retour ne sera alors jamais NA.

Par conséquent, vous devez dire de manière expressive case_whende mettre à NAsa place en ajoutant une instruction comme is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Astuce: la dplyr::coalesce()fonction peut être vraiment utile ici parfois!

De plus, s'il vous plaît noter que NAseuls travaillent habituellement pas, vous devez mettre spéciaux NAvaleurs: NA_integer_, NA_character_ou NA_real_.

Dan Chaltiel
la source
1
Cela a été beaucoup plus rapide que dérivéFactor.
Fato39
12

Il semble que derivedFactorle mosaicpackage a été conçu pour cela. Dans cet exemple, cela ressemblerait à quelque chose comme:

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

(Si vous souhaitez que le résultat soit numérique au lieu d'un facteur, encapsulez le derivedFactoravec un as.numeric.)

Notez que l' .defaultoption combinée avec .method = "first"définit la condition "else" - cette approche est décrite dans le fichier d'aide pour derivedFactor.

Jake Fisher
la source
Vous pouvez également empêcher le résultat d'être un facteur en utilisant l' .asFactor = Foption ou en utilisant la fonction (similaire) derivedVariabledans le même package.
Jake Fisher
Il semble que recodedplyr 0.5 fera cela. Cependant, je n'ai pas encore enquêté dessus. Voir blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher
C'était lent pour mes données avec 1e6 lignes.
Fato39
3
@ Fato39 Oui, la mosaic::derivedFactorfamille de fonctions est très lente. Si vous comprenez pourquoi, veuillez répondre à ma question SO à ce sujet: stackoverflow.com/questions/33787691/… . Je suis heureux de voir d'après votre autre commentaire que dplyr::case_whenc'est plus rapide - je vais devoir passer à cela.
Jake Fisher
J'essaye la commande suivante, bibliothèque (mosaïque) VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivedFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")) mais cela ne fonctionne pas, il suffit de résoudre la condition VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivedFactor ("M" = (CATEGORY == "BULL Pourriez-vous m'aider? Merci beaucoup!
Johanna Ramirez