R Đánh giá điều kiện khi sử dụng người vận hành đường ống%>%


93

Khi sử dụng các nhà điều hành đường ống %>%với các gói như dplyr, ggvis, dycharts, vv, làm cách nào để làm một bước có điều kiện? Ví dụ;

step_1 %>%
step_2 %>%

if(condition)
step_3

Những cách tiếp cận này dường như không hoạt động:

step_1 %>%
step_2 
if(condition) %>% step_3

step_1 %>%
step_2 %>%
if(condition) step_3

Còn một chặng đường dài:

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

Có cách nào tốt hơn mà không có tất cả sự dư thừa?


4
Một ví dụ để làm việc với (như Ben đã cung cấp) sẽ tốt hơn, fyi.
Frank

Câu trả lời:


104

Dưới đây là một ví dụ nhanh tận dụng lợi thế của .ifelse:

X<-1
Y<-T

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

Trong ifelse, if YTRUEif sẽ thêm 1, nếu không nó sẽ chỉ trả về giá trị cuối cùng của X. Đây .là một stand-in cho chức năng biết nơi đầu ra từ bước trước của chuỗi đi, vì vậy tôi có thể sử dụng nó trên cả hai nhánh.

Chỉnh sửa Như @BenBolker đã chỉ ra, bạn có thể không muốn ifelse, vì vậy đây là một ifphiên bản.

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

Cảm ơn @Frank đã chỉ ra rằng tôi nên sử dụng {dấu ngoặc nhọn xung quanh câu lệnh ifvà của tôi ifelseđể tiếp tục chuỗi.


4
Tôi thích phiên bản sau chỉnh sửa. ifelsecó vẻ không tự nhiên đối với luồng điều khiển.
Frank

7
Một điều cần lưu ý: nếu có một bước sau trong chuỗi, hãy sử dụng {}. Ví dụ: nếu bạn không có chúng ở đây, những điều tồi tệ sẽ xảy ra (chỉ in Yvì lý do nào đó): X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
Frank

Sử dụng bí danh magrittr addsẽ làm cho ví dụ rõ ràng hơn.
ctbrown

Trong thuật ngữ chơi gôn mã, ví dụ cụ thể này có thể được viết như X %>% add(1*Y)nhưng tất nhiên điều đó không trả lời câu hỏi ban đầu
talat

1
Một điều quan trọng trong khối điều kiện giữa {}là bạn phải tham chiếu đối số trước của đường ống dplyr (còn được gọi là LHS) với dấu chấm (.) - nếu không khối điều kiện không nhận được. tranh luận!
Agile Bean,

32

Tôi nghĩ đó là một trường hợp cho purrr::when. Hãy tính tổng một vài số nếu tổng của chúng dưới 25, nếu không thì trả về 0.


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

whentrả về giá trị kết quả từ hành động của điều kiện hợp lệ đầu tiên. Đặt điều kiện ở bên trái ~và hành động ở bên phải của nó. Ở trên, chúng tôi chỉ sử dụng một điều kiện (và sau đó là một trường hợp khác), nhưng bạn có thể có nhiều điều kiện.

Bạn có thể dễ dàng tích hợp nó vào một đường ống dài hơn.


2
đẹp! Điều này cũng cung cấp một giải pháp thay thế trực quan hơn cho 'switch'.
Steve G. Jones

16

Đây là một biến thể về câu trả lời do @JohnPaul cung cấp. Biến thể này sử dụng `if`hàm thay vì một if ... else ...câu lệnh ghép .

library(magrittr)

X <- 1
Y <- TRUE

X %>% `if`(Y, . + 1, .) %>% multiply_by(2)
# [1] 4

Lưu ý rằng trong trường hợp này, các dấu ngoặc nhọn không cần thiết xung quanh `if`hàm, cũng không xung quanh một ifelsehàm — chỉ xung quanh if ... else ...câu lệnh. Tuy nhiên, nếu trình giữ chỗ dấu chấm chỉ xuất hiện trong một lệnh gọi hàm lồng nhau, thì theo mặc định , magrittr sẽ chuyển phía bên trái thành đối số đầu tiên của phía bên phải. Hành vi này bị ghi đè bằng cách đặt biểu thức trong dấu ngoặc nhọn. Lưu ý sự khác biệt giữa hai chuỗi này:

X %>% `if`(Y, . + 1, . + 2)
# [1] TRUE
X %>% {`if`(Y, . + 1, . + 2)}
# [1] 4

Trình giữ chỗ dấu chấm được lồng trong một lệnh gọi hàm cả khi nó xuất hiện trong `if` hàm, vì . + 1. + 2được hiểu là `+`(., 1)`+`(., 2), tương ứng. Vì vậy, biểu thức đầu tiên trả về kết quả của `if`(1, TRUE, 1 + 1, 1 + 2), (kỳ lạ thay, `if`không phàn nàn về các đối số không được sử dụng thêm) và biểu thức thứ hai trả về kết quả `if`(TRUE, 1 + 1, 1 + 2)là hành vi mong muốn trong trường hợp này.

Để biết thêm thông tin về cách toán tử magrittr pipe xử lý phần giữ chỗ dấu chấm, hãy xem tệp trợ giúp cho%>% , đặc biệt là phần "Sử dụng dấu chấm cho các mục đích thứ yếu".


sự khác biệt giữa việc sử dụng `ìf`ifelse? họ có giống nhau về hành vi không?
Agile Bean

@AgileBean Hành vi của các hàm ififelsechức năng không giống nhau. Các ifelsechức năng được một vector hóa if. Nếu bạn cung cấp cho ifhàm một vectơ logic, nó sẽ in một cảnh báo và nó sẽ chỉ sử dụng phần tử đầu tiên của vectơ logic đó. So sánh `if`(c(T, F), 1:2, 3:4)với ifelse(c(T, F), 1:2, 3:4).
Cameron Bieganek

tuyệt vời, cảm ơn vì đã làm rõ! Vì vậy, khi các vấn đề nêu trên là phi vector hóa, bạn có thể cũng được viết giải pháp của bạn làX %>% { ifelse(Y, .+1, .+2) }
Agile Bean

12

Đối với tôi, dường như dễ dàng nhất là rút lui khỏi các đường ống một chút (mặc dù tôi muốn xem các giải pháp khác), ví dụ:

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

Đây là một sửa đổi nhỏ cho câu trả lời của @ JohnPaul (bạn có thể không thực sự muốn ifelse, điều này đánh giá cả hai đối số của nó và được vector hóa). Sẽ rất tốt nếu bạn sửa đổi điều này để .tự động trả về nếu điều kiện là sai ... ( thận trọng : Tôi nghĩ điều này hoạt động nhưng chưa thực sự thử nghiệm / nghĩ về nó quá nhiều ...)

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

Tôi thích purrr::whenvà các giải pháp cơ bản khác được cung cấp ở đây đều tuyệt vời nhưng tôi muốn thứ gì đó nhỏ gọn và linh hoạt hơn nên tôi đã thiết kế hàm pif(pipe if), xem mã và doc ở cuối câu trả lời.

Đối số có thể là một trong hai biểu thức của hàm (hỗ trợ ký hiệu công thức) và đầu vào được trả về không thay đổi theo mặc định nếu điều kiện là FALSE.

Được sử dụng cho các ví dụ từ các câu trả lời khác:

## 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

Những ví dụ khác :

## 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

Chức năng

#' 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
  }
}

"Mặt khác, các hàm hoặc công thức sẽ chỉ được áp dụng trên dữ liệu nếu điều kiện liên quan được đáp ứng." Bạn có thể giải thích lý do tại sao bạn quyết định làm như vậy?
mihagazvoda

Vì vậy, tôi chỉ tính toán những gì tôi cần tính, nhưng tôi tự hỏi tại sao tôi không làm điều đó với các biểu thức. Vì một số lý do có vẻ như tôi không muốn sử dụng đánh giá không tiêu chuẩn. Tôi nghĩ rằng tôi có một phiên bản sửa đổi trong các chức năng tùy chỉnh của mình, tôi sẽ cập nhật khi có cơ hội.
Moody_Mudskipper

Vui lòng cho tôi biết khi bạn cập nhật nó. Cảm ơn bạn!
mihagazvoda
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.