R 파이프 연산자 %> % 사용시 조건부 평가


93

파이프 연산자를 사용하는 경우 %>%같은 패키지 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
}

모든 중복성이없는 더 나은 방법이 있습니까?


4
함께 작업 할 예 (Ben이 제공 한대로)가 바람직합니다. fyi.
Frank

답변:


104

여기에 활용합니다 빠른 예입니다 .ifelse:

X<-1
Y<-T

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

에서 ifelse, 경우가 Y있다 TRUE, 하나 추가 할 것이다, 그렇지 않으면 단지의 마지막 값을 반환하는 경우 X. 는 .독립에 나는 두 가지에 사용할 수 있도록하는 체인의 이전 단계의 출력이가는 기능을 알 수있다.

@BenBolker가 지적했듯이 편집 하면 원하지 않을 수도 ifelse있으므로 여기에 if버전이 있습니다.

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

체인을 계속하려면 {ififelse문 주위에 중괄호를 사용해야한다고 지적한 @Frank에게 감사드립니다 .


4
편집 후 버전이 마음에 듭니다. ifelse제어 흐름에 부자연스러워 보입니다.
Frank

7
한 가지 유의해야 할 점 : 체인에 나중 단계가있는 경우 {}. 예를 들어 여기에없는 경우 나쁜 일이 발생합니다 ( Y어떤 이유에서든 인쇄 ). X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
Frank

magrittr 별칭 add을 사용하면 예제가 더 명확 해집니다.
ctbrown

코드 골프 용어에서이 특정 예제는 다음과 같이 작성 될 수 X %>% add(1*Y)있지만 물론 원래 질문에 대한 답은 아닙니다
talat

1
사이의 조건부 블록 내에서 중요한 점 {}은 dplyr 파이프 (LHS라고도 함)의 이전 인수를 점 (.)으로 참조해야한다는 것입니다. 그렇지 않으면 조건부 블록이. 논의!
Agile Bean

32

나는 그것이 purrr::when. 합이 25 미만이면 몇 개의 숫자를 합산하고 그렇지 않으면 0을 반환합니다.


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

when첫 번째 유효한 조건의 작업 결과 값을 반환합니다. 조건을의 왼쪽에 ~, 작업을 오른쪽에 둡니다 . 위에서 우리는 하나의 조건 (그리고 다른 경우) 만 사용했지만 여러 조건을 가질 수 있습니다.

더 긴 파이프에 쉽게 통합 할 수 있습니다.


2
좋은! 이것은 또한 '스위치'에 대한보다 직관적 인 대안을 제공합니다.
Steve G. Jones

16

다음은 @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입니까? 행동이 동일합니까?
Agile Bean '

@AgileBean ififelse함수 의 동작이 동일하지 않습니다. ifelse기능은 벡터화된다 if. if함수에 논리 벡터 를 제공하면 경고를 출력하고 해당 논리 벡터의 첫 번째 요소 만 사용합니다. 비교 `if`(c(T, F), 1:2, 3:4)ifelse(c(T, F), 1:2, 3:4).
Cameron Bieganek 19 년

설명해 주셔서 감사합니다! 따라서 위의 문제가 벡터화되지 않았으므로 솔루션을 다음과 같이 작성할 수도 있습니다.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(파이프)를 설계 하고 답변 끝에 코드와 문서를 참조하십시오.

인수는 함수의 표현식 (공식 표기가 지원됨) 일 수 있으며 조건이이면 기본적으로 입력이 변경되지 않고 반환됩니다 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.