При использовании оператора трубы %>%
с пакетами , такими как 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
}
Есть ли лучший способ без избыточности?
Ответы:
Вот краткий пример, в котором используются преимущества
.
иifelse
:X<-1 Y<-T X %>% add(1) %>% { ifelse(Y ,add(.,1), . ) }
В
ifelse
ifY
isTRUE
if добавляется 1, в противном случае возвращается только последнее значениеX
. Это.
подстановка, которая сообщает функции, куда идет вывод из предыдущего шага цепочки, поэтому я могу использовать его в обеих ветвях.Изменить Как отметил @BenBolker, возможно, вы не захотите
ifelse
, так что вотif
версия.X %>% add(1) %>% {if(Y) add(.,1) else .}
Благодаря @Frank за указание на то , что я должен использовать
{
фигурные скобки вокруг моихif
иifelse
заявлений продолжать цепочку.источник
ifelse
кажется неестественным для потока управления.{}
. Например, если у вас их здесь нет, случаются неприятности (просто печатьY
по какой-то причине):X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
add
сделало бы пример более ясным.X %>% add(1*Y)
но он, конечно, не отвечает на исходный вопрос{}
заключается в том, что вы должны ссылаться на предыдущий аргумент канала dplyr (также называемый LHS) с точкой (.) - в противном случае условный блок не получит. аргумент!Я думаю, это случай
purrr::when
. Суммируем несколько чисел, если их сумма меньше 25, иначе вернем 0.library("magrittr") 1:3 %>% purrr::when(sum(.) < 25 ~ sum(.), ~0 ) #> [1] 6
when
возвращает значение, полученное в результате действия первого допустимого условия. Поместите условие слева от него~
, а действие - справа от него. Выше мы использовали только одно условие (а затем еще случай), но у вас может быть много условий.Вы можете легко встроить это в более длинную трубу.
источник
Вот вариант ответа, предоставленного @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
? они идентичны по поведению?if
иifelse
функций не идентичны.ifelse
Функция является векторизацияif
. Если вы предоставитеif
функции с логическим вектором, она напечатает предупреждение и будет использовать только первый элемент этого логического вектора. Сравните`if`(c(T, F), 1:2, 3:4)
сifelse(c(T, F), 1:2, 3:4)
.X %>% { ifelse(Y, .+1, .+2) }
Мне показалось бы, что проще всего немного отступить от труб (хотя мне было бы интересно увидеть другие решения), например:
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
источник
Мне нравятся
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 } }
источник