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


93

当使用管道操作符%>%与包,如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

有很长的路要走:

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

有没有所有冗余的更好方法吗?


4
例如,最好使用一个与Ben合作的示例。
弗兰克(Frank)

Answers:


104

这是一个利用.和的快速示例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语句两边使用花括号来继续链。


4
我喜欢编辑后的版本。ifelse对于控制流来说似乎是不自然的。
Frank Frank

7
需要注意的一件事:如果链中还有后续步骤,请使用{}。例如,如果您在这里没有它们,则会发生坏事(Y出于某种原因只是出于打印目的): X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
弗兰克

使用magrittr别名add可以使示例更清晰。
ctbrown 2015年

用打高尔夫球的方式来说,这个特定的例子可以写成,X %>% add(1*Y)但是当然不能回答原始问题
talat

1
两者之间的条件块中的一件重要事情{}是,您必须用点(。)引用dplyr管道(也称为LHS)的前一个参数-否则条件块不会收到。论据!
敏捷豆

32

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


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

when返回从第一个有效条件的操作得出的值。将条件放在的左侧~,将动作放在其右侧。上面,我们只使用了一个条件(然后是另一种情况),但是您可以使用许多条件。

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


2
真好!这也为“开关”提供了更直观的选择。
史蒂夫·琼斯

15

这是@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管道运营商对待点占位符,请参阅帮助文件%>%,特别是对“使用点用于其他目的”一节。


`ìf`和之间有什么区别ifelse?他们的行为相同吗?
敏捷豆

@AgileBeanififelse函数的行为不同。该ifelse函数是向量化的if。如果为if函数提供逻辑向量,它将打印警告,并且仅使用该逻辑向量的第一个元素。比较`if`(c(T, F), 1:2, 3:4)ifelse(c(T, F), 1:2, 3:4)
卡梅隆·比加内克

太好了,谢谢您的澄清!因此,由于上述问题尚未向量化,因此您也可以将解决方案编写为X %>% { ifelse(Y, .+1, .+2) }
Agile Bean,

12

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

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

8

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

参数可以是函数的表达式(支持公式表示法),并且如果condition为,则输入在默认情况下不变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
  }
}

“另一方面,只有在满足相关条件的情况下,函数或公式才会应用于数据。” 您能解释为什么决定这样做吗?
mihagazvoda

所以我只计算我需要计算的内容,但我想知道为什么我不使用表达式来计算。由于某些原因,我似乎不想使用非标准评估。我认为我的自定义函数中有一个修改的版本,如果有机会我会进行更新。
Moody_Mudskipper

更新时请告诉我。谢谢!
mihagazvoda
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.