R Условная оценка при использовании оператора трубы%>%
При использовании оператора трубы %>%
с пакетами, такими как 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
}
Есть ли лучший способ без всей избыточности?
Ответы
Ответ 1
Вот краткий пример, который использует преимущества .
и ifelse
:
X<-1
Y<-T
X %>% add(1) %>% { ifelse(Y ,add(.,1), . ) }
В ifelse
, если Y
TRUE
если добавит 1, в противном случае он просто вернет последнее значение X
.
это stand-in, который сообщает функции, в которой идет выход из предыдущего шага цепочки, поэтому я могу использовать ее на обеих ветвях.
Изменить Как отметил @BenBolker, вам может не понадобиться ifelse
, так что вот версия if
.
X %>%
add(1) %>%
{if(Y) add(.,1) else .}
Спасибо @Frank за указание, что я должен использовать {
фигурные скобки вокруг операторов if
и ifelse
чтобы продолжить цепочку.
Ответ 2
Я думаю, что случай для purrr::when
. Пусть суммируются несколько чисел, если их сумма меньше 25, в противном случае возвращаем 0.
library("magrittr")
1:3 %>%
purrr::when(sum(.) < 25 ~ sum(.),
~0
)
#> [1] 6
when
возвращает значение, полученное в результате действия первого действительного условия. Поставьте условие слева от ~
и действие справа от него. Выше мы использовали только одно условие (а затем случай else), но вы можете иметь много условий.
Вы можете легко интегрировать это в более длинный канал.
Ответ 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
, который оценивает оба его аргумента
и векторизован). Было бы неплохо изменить это, чтобы вернуться
.
автоматически, если условие ложно...
(осторожно: я думаю, что это работает, но на самом деле не проверял/думал
об этом слишком много...)
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
Ответ 4
Вот вариант ответа, предоставленного @JohnPaul. Этот вариант использует функцию 'if'
вместо составной if... else...
statement.
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
. + 1
и . + 2
. + 2
интерпретируются как '+'(., 1)
и '+'(., 2)
соответственно. Итак, первое выражение возвращает результат 'if'(1, TRUE, 1 + 1, 1 + 2)
(как это ни странно, 'if'
не жалуется на дополнительные неиспользуемые аргументы), а второе выражение возвращается результат 'if'(TRUE, 1 + 1, 1 + 2)
, который является желаемым поведением в этом случае.
Для получения дополнительной информации о том, как оператор трубки magrittr относится к точечному заполнителю, см. Файл справки для %>%
, в частности раздел "Использование точки для вторичных целей".
Ответ 5
Изменение: pif
теперь является частью mmpipe
, чем вы можете скачать с помощью devtools::install_github("moodymudskipper/mmpipe")
.
Мне нравится purrr::when
и другие базовые решения, представленные здесь, все замечательные, но я хотел что-то более компактное и гибкое, поэтому я разработал функцию pif
(pipe if), см. Код и doc в конце ответа.
Аргументами могут быть либо выражения функций (обозначение формулы поддерживается), и вход возвращается по умолчанию, если условие 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
}
}