R Evaluation conditionnelle lors de l’utilisation de l’opérateur de canal%>%

Lorsque vous utilisez l’opérateur de pipe %>% avec des paquets tels que dplyr , ggvis , dycharts , etc., comment faire une étape conditionnelle? Par exemple;

 step_1 %>% step_2 %>% if(condition) step_3 

Ces approches ne semblent pas fonctionner:

 step_1 %>% step_2 if(condition) %>% step_3 step_1 %>% step_2 %>% if(condition) step_3 

Il y a un long chemin:

 if(condition) { step_1 %>% step_2 }else{ step_1 %>% step_2 %>% step_3 } 

Y a-t-il un meilleur moyen sans toute la redondance?

Voici un exemple rapide qui tire parti de la . et ifelse :

 X< -1 Y<-T X %>% add(1) %>% { ifelse(Y ,add(1), . ) } 

Dans l’ ifelse , si Y est TRUE si va append 1, sinon il retournera simplement la dernière valeur de X Le . est un stand-in qui indique la fonction où la sortie de l’étape précédente de la chaîne va, donc je peux l’utiliser sur les deux twigs.

Modifier Comme @BenBolker l’a souligné, vous pourriez ne pas vouloir ifelse , alors voici une version if .

 X %>% add(1) %>% {if(Y) add(1) else .} 

Merci à @Frank d’avoir souligné que je devrais utiliser des accolades autour de mes instructions if et ifelse pour continuer la chaîne.

Je pense que c’est un cas pour purrr::when . Résumons quelques chiffres si leur sum est inférieure à 25, sinon retourne 0.

 library("magrittr") 1:3 %>% purrr::when(sum(.) < 25 ~ sum(.), ~0 ) #> [1] 6 

when renvoie la valeur résultant de l’action de la première condition valide. Mettez la condition à gauche de ~ et l’action à droite. Ci-dessus, nous n’avons utilisé qu’une seule condition (et ensuite un autre cas), mais vous pouvez avoir plusieurs conditions.

Vous pouvez facilement intégrer cela dans un tuyau plus long.

Il me semblerait plus facile de reculer un peu (même si cela m’intéresserait de voir d’autres solutions), par exemple:

 library("dplyr") z < - data.frame(a=1:2) z %>% mutate(b=a^2) -> z2 if (z2$b[1]>1) { z2 %>% mutate(b=b^2) -> z2 } z2 %>% mutate(b=b^2) -> z3 

Ceci est une légère modification de la réponse de @ JohnPaul (vous pourriez ne pas vouloir vraiment ifelse , qui évalue ses deux arguments et est vectorisé). Ce serait bien de modifier cela pour revenir . automatiquement si la condition est fausse … ( attention : je pense que cela fonctionne mais que je n’ai pas vraiment testé / pensé…)

 iff < - function(cond,x,y) { if(cond) return(x) else return(y) } z %>% mutate(b=a^2) %>% iff(cond=z2$b[1]>1,mutate(.,b=b^2),.) %>% mutate(b=b^2) -> z4 

Voici une variante de la réponse fournie par @JohnPaul. Cette variante utilise la fonction `if` au lieu d’une instruction composée if ... else ...

 library(magrittr) X < - 1 Y <- TRUE X %>% `if`(Y, . + 1, .) %>% multiply_by(2) # [1] 4 

Notez que dans ce cas, les accolades ne sont pas nécessaires autour de la fonction `if` , ni autour d’une fonction ifelse – uniquement autour de l’instruction if ... else ... Cependant, si l’espace réservé apparaît uniquement dans un appel de fonction nested, magrittr dirigera par défaut le côté gauche dans le premier argument du côté droit. Ce comportement est remplacé par la mise en forme de l’expression entre accolades. Notez la différence entre ces deux chaînes:

 X %>% `if`(Y, . + 1, . + 2) # [1] TRUE X %>% {`if`(Y, . + 1, . + 2)} # [1] 4 

L’espace réservé est nested dans un appel de fonction à chaque fois qu’il apparaît dans la fonction `if` , depuis . + 1 . + 1 et . + 2 . + 2 sont respectivement interprétés comme `+`(., 1) et `+`(., 2) . Donc, la première expression retourne le résultat de `if`(1, TRUE, 1 + 1, 1 + 2) , (curieusement, `if` ne se plaint pas des arguments inutilisés supplémentaires), et la deuxième expression retourne le résultat de `if`(TRUE, 1 + 1, 1 + 2) , qui est le comportement souhaité dans ce cas.

Pour plus d’informations sur la manière dont l’opérateur de canalisation magrittr traite l’espace réservé, consultez le fichier d’aide pour %>% , en particulier la section “Utilisation du point à des fins secondaires”.

J’aime purrr::when et les autres solutions de base fournies ici sont toutes excellentes mais je voulais quelque chose de plus compact et flexible, donc j’ai conçu la fonction pif (pipe if), voir code et doc à la fin de la réponse.

Les arguments peuvent être soit des expressions de fonctions (la notation de formule est prise en charge), et input est renvoyé inchangé par défaut si la condition est FALSE .

Utilisé sur des exemples d’autres réponses:

 ## from Ben Bolker data.frame(a=1:2) %>% mutate(b=a^2) %>% pif(~b[1]>1, ~mutate(.,b=b^2)) %>% mutate(b=b^2) # ab # 1 1 1 # 2 2 16 ## from Lorenz Walthert 1:3 %>% pif(sum(.) < 25,sum,0) # [1] 6 ## from clbieganek 1 %>% pif(TRUE,~. + 1) %>% `*`(2) # [1] 4 # from theforestcologist 1 %>% `+`(1) %>% pif(TRUE ,~ .+1) # [1] 3 

Autres exemples:

 ## using functions iris %>% pif(is.data.frame, dim, nrow) # [1] 150 5 ## using formulas iris %>% pif(~is.numeric(Species), ~"numeric :)", ~paste(class(Species)[1],":(")) # [1] "factor :(" ## using expressions iris %>% pif(nrow(.) > 2, head(.,2)) # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 5.1 3.5 1.4 0.2 setosa # 2 4.9 3.0 1.4 0.2 setosa ## careful with expressions iris %>% pif(TRUE, dim, warning("this will be evaluated")) # [1] 150 5 # Warning message: # In inherits(false, "formula") : this will be evaluated iris %>% pif(TRUE, dim, ~warning("this won't be evaluated")) # [1] 150 5 

Fonction

 #' Pipe friendly conditional operation #' #' Apply a transformation on the data only if a condition is met, #' by default if condition is not met the input is returned unchanged. #' #' The use of formula or functions is recommended over the use of expressions #' for the following reasons : #' #' \itemize{ #' \item If \code{true} and/or \code{false} are provided as expressions they #' will be evaluated wether the condition is \code{TRUE} or \code{FALSE}. #' Functions or formulas on the other hand will be applied on the data only if #' the relevant condition is met #' \item Formulas support calling directly a column of the data by its name #' without \code{x$foo} notation. #' \item Dot notation will work in expressions only if `pif` is used in a pipe #' chain #' } #' #' @param x An object #' @param p A predicate function, a formula describing such a predicate function, or an expression. #' @param true,false Functions to apply to the data, formulas describing such functions, or expressions. #' #' @return The output of \code{true} or \code{false}, either as expressions or applied on data as functions #' @export #' #' @examples #'# using functions #'pif(iris, is.data.frame, dim, nrow) #'# using formulas #'pif(iris, ~is.numeric(Species), ~"numeric :)",~paste(class(Species)[1],":(")) #'# using expressions #'pif(iris, nrow(iris) > 2, head(iris,2)) #'# careful with expressions #'pif(iris, TRUE, dim, warning("this will be evaluated")) #'pif(iris, TRUE, dim, ~warning("this won't be evaluated")) pif < - function(x, p, true, false = identity){ if(!requireNamespace("purrr")) stop("Package 'purrr' needs to be installed to use function 'pif'") if(inherits(p, "formula")) p <- purrr::as_mapper( if(!is.list(x)) p else update(p,~with(...,.))) if(inherits(true, "formula")) true <- purrr::as_mapper( if(!is.list(x)) true else update(true,~with(...,.))) if(inherits(false, "formula")) false <- purrr::as_mapper( if(!is.list(x)) false else update(false,~with(...,.))) if ( (is.function(p) && p(x)) || (!is.function(p) && p)){ if(is.function(true)) true(x) else true } else { if(is.function(false)) false(x) else false } }