使用管道运算符% >% 时的条件计算

当对 dplyrggvisdycharts等软件包使用管道操作符 %>%时,如何有条件地执行步骤?例如:

step_1 %>%
step_2 %>%


if(condition)
step_3

这些方法似乎行不通:

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


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

There is a long way:

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

有没有更好的方法,不需要所有的冗余?

48777 次浏览

对我来说,退出管道一点点似乎是最容易的(尽管我有兴趣看到其他的解决方案) ,例如:

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

这是@JohnPaul 的回答的一个小小的修改(您可能不会 真正想要的是 ifelse,它计算它的两个参数 并且是向量化的) 如果条件为假,.将自动..。 (小心: 我认为这是有效的,但还没有真正的测试/思考 about it too much ...)

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

下面是一个利用 .ifelse的快速示例:

X<-1
Y<-T


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

ifelse中,如果 YTRUE,如果将加1,则返回 X的最后一个值。.是一个替身,它告诉函数链的前一步的输出到哪里,因此我可以在两个分支上使用它。

剪辑 正如@BenBolker 指出的,您可能不想要 ifelse,所以这里是 if版本。

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

感谢@Frank 指出我应该在我的 ififelse语句周围使用 {大括号来继续链接。

下面是@JohnPaul 提供的答案的一个变体。此变体使用 `if`函数而不是复合 if ... else ...语句。

library(magrittr)


X <- 1
Y <- TRUE


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

注意,在这种情况下,在 `if`函数周围不需要花括号,在 ifelse函数周围也不需要花括号,只在 if ... else ...语句周围需要花括号。但是,如果点占位符只出现在嵌套函数调用中,那么 Magrittr默认情况下将把左边的参数导入右边的第一个参数中。通过用大括号括起表达式来覆盖此行为。请注意这两个链之间的区别:

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

点占位符嵌套在 `if`函数中两次出现的函数调用中,因为 . + 1. + 2分别被解释为 `+`(., 1)`+`(., 2)。因此,第一个表达式返回 `if`(1, TRUE, 1 + 1, 1 + 2)的结果(奇怪的是,`if`没有抱怨额外的未使用参数) ,第二个表达式返回 `if`(TRUE, 1 + 1, 1 + 2)的结果,这是本例中所需的行为。

有关 Magrittr管道操作符如何处理点占位符的详细信息,请参阅 帮助文件 For %>%,特别是“使用点作为次要用途”一节。

我认为这是 purrr::when()的案例。如果它们的和小于25,让我们总结一些数字,否则返回0。


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

when returns the value resulting from the action of the first valid condition. Put the condition to the left of ~, and the action to the right of it. Above, we only used one condition (and then an else case), but you can have many conditions.

您可以很容易地将其集成到一个较长的管道中。

我喜欢 purrr::when和这里提供的其他基本解决方案都很棒,但我想要更紧凑和灵活的东西,所以我设计了函数 pif(管道如果) ,见代码和文档在答案的末尾。

参数可以是函数的表达式(支持公式表示法) ,如果条件为 FALSE,则默认情况下输入不变地返回。

用于其他答案的例子:

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

其他例子:

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

功能

#' 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
}
}