Lorsque vous utilisez l'opérateur de conduite %>%
avec les paquets tels que dplyr
, ggvis
, dycharts
, etc, comment dois - je 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?
Réponses:
Voici un exemple rapide qui tire parti de
.
etifelse
:X<-1 Y<-T X %>% add(1) %>% { ifelse(Y ,add(.,1), . ) }
Dans le
ifelse
, ifY
estTRUE
if ajoutera 1, sinon il retournera simplement la dernière valeur deX
. Le.
est un stand-in qui indique à la fonction où va la sortie de l'étape précédente de la chaîne, donc je peux l'utiliser sur les deux branches.Modifier Comme @BenBolker l'a souligné, vous ne voudrez peut-être pas
ifelse
, voici donc uneif
version.X %>% add(1) %>% {if(Y) add(.,1) else .}
Merci à @Frank d'avoir indiqué que je devrais utiliser des
{
accolades autour de mes déclarationsif
etifelse
pour continuer la chaîne.la source
ifelse
ne semble pas naturel pour le flux de contrôle.{}
. Par exemple, si vous ne les avez pas ici, de mauvaises choses se produisent (juste une impressionY
pour une raison quelconque):X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
add
rendrait l'exemple plus clair.X %>% add(1*Y)
mais bien sûr, cela ne répond pas à la question d'origine{}
est que vous devez référencer l'argument précédent du tube dplyr (également appelé LHS) avec le point (.) - sinon le bloc conditionnel ne reçoit pas le. argument!Je pense que c'est un cas pour
purrr::when
. Résumons quelques nombres si leur somme est inférieure à 25, sinon renvoyez 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. Placez la condition à gauche~
et l'action à droite. Ci-dessus, nous n'avons utilisé qu'une seule condition (puis un autre cas), mais vous pouvez avoir plusieurs conditions.Vous pouvez facilement l'intégrer dans un tuyau plus long.
la source
Voici une variation de la réponse fournie par @JohnPaul. Cette variante utilise la
`if`
fonction au lieu d'uneif ... else ...
instruction composée .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 du
`if`
fonction, ni autour d'uneifelse
fonction, uniquement autour de l'if ... else ...
instruction. Cependant, si l'espace réservé de point n'apparaît que dans un appel de fonction imbriquée, alors magrittr dirigera par défaut le côté gauche dans le premier argument du côté droit. Ce comportement est remplacé en plaçant 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é point est imbriqué dans un appel de fonction les deux fois qu'il apparaît dans la
`if`
fonction, car. + 1
et. + 2
sont interprétés comme`+`(., 1)
et`+`(., 2)
, respectivement. Ainsi, la première expression renvoie le résultat de`if`(1, TRUE, 1 + 1, 1 + 2)
, (curieusement,`if`
ne se plaint pas d'arguments inutilisés supplémentaires), et la deuxième expression renvoie le résultat`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 tube magrittr traite l'espace réservé de point, consultez le fichier d'aide de
%>%
, en particulier la section "Utilisation du point à des fins secondaires".la source
`ìf`
etifelse
? sont-ils identiques dans leur comportement?if
etifelse
n'est pas identique. Laifelse
fonction est une vectoriséeif
. Si vous fournissez à laif
fonction un vecteur logique, elle affichera un avertissement et n'utilisera que le premier élément de ce vecteur logique. Comparez`if`(c(T, F), 1:2, 3:4)
avecifelse(c(T, F), 1:2, 3:4)
.X %>% { ifelse(Y, .+1, .+2) }
Il me semblerait plus facile de m'éloigner un peu des tuyaux (même si je serais intéressé à 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
Il s'agit d'une légère modification de la réponse de @ JohnPaul (vous ne voudrez peut-être pas vraiment
ifelse
, qui évalue ses deux arguments et est vectorisée). Ce serait bien de modifier ceci pour revenir.
automatiquement si la condition est fausse ... ( attention : je pense que cela fonctionne mais je n'ai pas vraiment testé / trop réfléchi ...)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
la source
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 alors j'ai conçu la fonctionpif
(pipe if), voir le code et la 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 l'entrée est renvoyée inchangée 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) # a b # 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 theforestecologist 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 } }
la source