Ao usar o operador pipe %>%
com pacotes tais como dplyr
, ggvis
, dycharts
, etc, como eu faço um passo condicionalmente? Por exemplo;
step_1 %>%
step_2 %>%
if(condition)
step_3
Essas abordagens não parecem funcionar:
step_1 %>%
step_2
if(condition) %>% step_3
step_1 %>%
step_2 %>%
if(condition) step_3
Existe um longo caminho:
if(condition)
{
step_1 %>%
step_2
}else{
step_1 %>%
step_2 %>%
step_3
}
Existe uma maneira melhor sem toda a redundância?
Respostas:
Aqui está um exemplo rápido que tira proveito de
.
eifelse
:X<-1 Y<-T X %>% add(1) %>% { ifelse(Y ,add(.,1), . ) }
No
ifelse
, ifY
isTRUE
if adicionará 1, caso contrário, apenas retornará o último valor deX
. O.
é um substituto que informa à função para onde vai a saída da etapa anterior da cadeia, para que eu possa usá-la nos dois ramos.Editar Como @BenBolker apontou, você pode não querer
ifelse
, então aqui está umaif
versão.X %>% add(1) %>% {if(Y) add(.,1) else .}
Agradeço a @Frank por apontar que eu deveria usar
{
colchetes ao redor de minhas instruçõesif
eifelse
para continuar a cadeia.fonte
ifelse
parece não natural para o fluxo de controle.{}
. Por exemplo, se você não os tem aqui, coisas ruins acontecem (apenas imprimindoY
por algum motivo):X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
add
tornaria o exemplo mais claro.X %>% add(1*Y)
mas é claro que não responde à pergunta original{}
é que você deve fazer referência ao argumento anterior do tubo dplyr (também chamado LHS) com o ponto (.) - caso contrário, o bloco condicional não receberá o. argumento!Acho que é um caso para
purrr::when
. Vamos somar alguns números se a soma deles for inferior a 25, caso contrário, retorne 0.library("magrittr") 1:3 %>% purrr::when(sum(.) < 25 ~ sum(.), ~0 ) #> [1] 6
when
retorna o valor resultante da ação da primeira condição válida. Coloque a condição à esquerda de~
e a ação à direita dele. Acima, usamos apenas uma condição (e depois um outro caso), mas você pode ter muitas condições.Você pode facilmente integrar isso em um tubo mais longo.
fonte
Aqui está uma variação da resposta fornecida por @JohnPaul. Esta variação usa a
`if`
função em vez de umaif ... else ...
instrução composta .library(magrittr) X <- 1 Y <- TRUE X %>% `if`(Y, . + 1, .) %>% multiply_by(2) # [1] 4
Observe que, neste caso, as chaves não são necessárias em torno da
`if`
função, nem em torno de umaifelse
função - apenas em torno daif ... else ...
instrução. No entanto, se o marcador de posição de ponto aparecer apenas em uma chamada de função aninhada, então magrittr irá, por padrão, canalizar o lado esquerdo para o primeiro argumento do lado direito. Esse comportamento é anulado colocando a expressão entre chaves. Observe a diferença entre essas duas cadeias:X %>% `if`(Y, . + 1, . + 2) # [1] TRUE X %>% {`if`(Y, . + 1, . + 2)} # [1] 4
O marcador de posição de ponto está aninhado em uma chamada de função nas duas vezes em que aparece no
`if`
função, pois. + 1
e. + 2
são interpretados como`+`(., 1)
e`+`(., 2)
, respectivamente. Portanto, a primeira expressão está retornando o resultado de`if`(1, TRUE, 1 + 1, 1 + 2)
, (estranhamente,`if`
não reclama sobre argumentos extras não utilizados), e a segunda expressão está retornando o resultado de`if`(TRUE, 1 + 1, 1 + 2)
, que é o comportamento desejado neste caso.Para obter mais informações sobre como o operador de tubo magrittr trata o marcador de posição de ponto, consulte o arquivo de ajuda para
%>%
, em particular a seção sobre "Usando o ponto para finalidades secundárias".fonte
`ìf`
eifelse
? eles são idênticos em comportamento?if
eifelse
não é idêntico. Aifelse
função é vetorizadaif
. Se você fornecer àif
função um vetor lógico, ela imprimirá um aviso e usará apenas o primeiro elemento desse vetor lógico. Compare`if`(c(T, F), 1:2, 3:4)
comifelse(c(T, F), 1:2, 3:4)
.X %>% { ifelse(Y, .+1, .+2) }
Parece-me mais fácil me afastar um pouco dos canos (embora eu esteja interessado em ver outras soluções), por exemplo:
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
Esta é uma pequena modificação da resposta de @JohnPaul (você pode realmente não querer
ifelse
, que avalia ambos os argumentos e é vetorizada). Seria bom modificar isso para retornar.
automaticamente se a condição for falsa ... ( atenção : acho que isso funciona, mas realmente não testei / pensei muito sobre isso ...)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
fonte
iff()
retorna um erro quandoy
é diferente de.
.Eu gosto
purrr::when
e as outras soluções básicas fornecidas aqui são ótimas, mas eu queria algo mais compacto e flexível, então projetei a funçãopif
(pipe if), veja o código e o doc no final da resposta.Os argumentos podem ser expressões de funções (a notação de fórmula é suportada) e a entrada é retornada inalterada por padrão se a condição for
FALSE
.Usado em exemplos de outras respostas:
## 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
Outros exemplos:
## 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
Função
#' 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 } }
fonte