R 使用管道运算符时的条件评估 %>%

问题描述 投票:0回答:7

当将管道运算符

%>%
dplyr
ggvis
dycharts
等包一起使用时,如何有条件地执行步骤?例如;

step_1 %>%
step_2 %>%

if(condition)
step_3

这些方法似乎不起作用:

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

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

还有很长的路要走:

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

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

r dplyr ggvis magrittr
7个回答
166
投票

这是一个利用

.
ifelse
的简单示例:

X<-1
Y<-T

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

ifelse
中,如果
Y
TRUE
的话就会加1,否则就只返回
X
的最后一个值。
.
是一个替代函数,它告诉函数链上一步的输出去了哪里,所以我可以在两个分支上使用它。

编辑 正如 @BenBolker 指出的,你可能不想要

ifelse
,所以这里有一个
if
版本。

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

感谢@Frank 指出我应该在

{
if
语句周围使用
ifelse
大括号来继续链。


48
投票

编辑:

purrr::when()
自 {purrr} 版本 1.0.0

起已弃用

我认为这就是

purrr::when()
的情况。让我们对几个数字进行求和,如果它们的总和低于 25,则返回 0。


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

when
返回第一个有效条件的操作所产生的值。将条件放在
~
的左侧,将操作放在其右侧。上面,我们只使用了一个条件(然后是 else 情况),但您可以有很多条件。

您可以轻松地将其集成到更长的管道中。


23
投票

这是@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 管道运算符如何处理点占位符的更多信息,请参阅 %>%

帮助文件
,特别是“将点用于次要目的”部分。


16
投票

对我来说,稍微退出管道似乎是最简单的(尽管我有兴趣看到其他解决方案),例如:

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
,它评估它的两个参数 并进行矢量化)。最好修改一下它以返回 如果条件为假,
.
自动... (警告:我认为这有效,但尚未真正测试/思考 关于它太多了...)

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

13
投票

我喜欢

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

参数可以是函数表达式(支持公式表示法),如果条件为

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

2
投票

一个可能的解决方案是使用匿名函数

library(magrittr)
1 %>% 
  (\(.) if (T) . + 1 else .) %>% 
  multiply_by(2)

0
投票

我知道这是一个旧线程,但是滚动浏览建议的解决方案,我自己对其中任何一个都不满意,因此想出了一个仅仅充当辅助函数是本机语言构造的解决方案。

data %>% ifelse_(T, select(voter), select(sex)) %>% head() # voter column
data %>% ifelse_(F, select(voter), select(sex)) %>% head() # sex column
data %>% ifelse_(T, select(voter)) %>% head() # voter column
data %>% ifelse_(F, select(voter)) %>% head() # complete data frame
data %>% if_(T, select(voter)) %>% head() # voter column
data %>% if_(F, select(voter)) %>% head() # complete data frame

用途:

pipe_left %>% ifelse_(condition, if_true, if_false) %>% pipe_right

简称和别名:

pipe_left %>% ifelse_(condition, if_true) %>% pipe_right
pipe_left %>% if_(condition, if_true) %>% pipe_right

if_true
if_false
可以是自然出现在当前管道位置的任何表达式。如果条件为 false 并且未提供
if_false
,则会传递 pipeline_left。

来源:(需要

rlang
dplyr
magrittr

ifelse_ = function(data,c,a,b){
  if(c){
    e = enexpr(a)
  } else {
    if(missing(b)){
      return(data)
    } else {
      e = enexpr(b)
    }
  }
  u = expr(`%>%`((.),!!e))
  data %>% {eval_tidy(u)}
}
if_ = function(...){
  ifelse_(...)
}

要做的事情:也在数据上下文中评估条件。

© www.soinside.com 2019 - 2024. All rights reserved.