R 使用管道运算符时的条件评估 %>%
R Conditional evaluation when using the pipe operator %>%
将管道运算符 %>%
与 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
}
有没有更好的方法而不需要所有冗余?
对我来说,稍微退出管道似乎是最简单的(尽管我有兴趣看到其他解决方案),例如:
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
,它计算它的两个参数
并被矢量化)。最好将其修改为 return
.
如果条件为假则自动...
(注意:我认为这行得通,但实际上行不通tested/thought
关于它太多了......)
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
中,如果Y
是TRUE
则加1,否则只是returnX
的最后一个值。 .
是一个替代函数,它告诉函数链的上一步输出的去向,因此我可以在两个分支上使用它。
编辑
正如@BenBolker 指出的那样,您可能不想要 ifelse
,所以这里有一个 if
版本。
X %>%
add(1) %>%
{if(Y) add(.,1) else .}
感谢@Frank 指出我应该在 if
和 ifelse
语句周围使用 {
大括号来继续链。
这是@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 管道运算符如何处理点占位符的更多信息,请参阅 help file for %>%
,特别是关于 [= 的部分39=].
我认为 purrr::when()
就是这种情况。如果总和小于 25 则让我们求和,否则 return 0.
library("magrittr")
1:3 %>%
purrr::when(sum(.) < 25 ~ sum(.), ~0)
#> [1] 6
when
returns 第一个有效条件的操作产生的值。将条件放在 ~
的左边,将动作放在它的右边。上面,我们只用了一个条件(然后是else case),但是你可以有很多条件。
您可以轻松地将其集成到更长的管道中。
我喜欢 purrr::when
,这里提供的其他基本解决方案都很棒,但我想要更紧凑和灵活的东西,所以我设计了函数 pif
(pipe 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
}
}
将管道运算符 %>%
与 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
}
有没有更好的方法而不需要所有冗余?
对我来说,稍微退出管道似乎是最简单的(尽管我有兴趣看到其他解决方案),例如:
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
,它计算它的两个参数
并被矢量化)。最好将其修改为 return
.
如果条件为假则自动...
(注意:我认为这行得通,但实际上行不通tested/thought
关于它太多了......)
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
中,如果Y
是TRUE
则加1,否则只是returnX
的最后一个值。 .
是一个替代函数,它告诉函数链的上一步输出的去向,因此我可以在两个分支上使用它。
编辑
正如@BenBolker 指出的那样,您可能不想要 ifelse
,所以这里有一个 if
版本。
X %>%
add(1) %>%
{if(Y) add(.,1) else .}
感谢@Frank 指出我应该在 if
和 ifelse
语句周围使用 {
大括号来继续链。
这是@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 管道运算符如何处理点占位符的更多信息,请参阅 help file for %>%
,特别是关于 [= 的部分39=].
我认为 purrr::when()
就是这种情况。如果总和小于 25 则让我们求和,否则 return 0.
library("magrittr")
1:3 %>%
purrr::when(sum(.) < 25 ~ sum(.), ~0)
#> [1] 6
when
returns 第一个有效条件的操作产生的值。将条件放在 ~
的左边,将动作放在它的右边。上面,我们只用了一个条件(然后是else case),但是你可以有很多条件。
您可以轻松地将其集成到更长的管道中。
我喜欢 purrr::when
,这里提供的其他基本解决方案都很棒,但我想要更紧凑和灵活的东西,所以我设计了函数 pif
(pipe 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
}
}