R Boru operatörünü kullanırken koşullu değerlendirme%>%


94

Boru operatörünü kullanırken %>%gibi paketleri ile dplyr, ggvis, dycharts, vb nasıl yaparım koşullu bir adım mı? Örneğin;

step_1 %>%
step_2 %>%

if(condition)
step_3

Bu yaklaşımlar işe yaramıyor gibi görünüyor:

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

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

Uzun bir yol var:

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

Tüm fazlalık olmadan daha iyi bir yol var mı?


4
Çalışmak için bir örnek (Ben'in sağladığı gibi) tercih edilebilir, fyi.
Frank

Yanıtlar:


104

İşte .ve avantajlarından yararlanan hızlı bir örnek ifelse:

X<-1
Y<-T

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

İçinde ifelse, if Yis TRUEif 1 toplayacak, aksi takdirde sadece son değerini döndürecektir X. .Bir stand-I hem dallarda kullanabilirsiniz böylece hangi zincirin önceki adımda çıktı gider fonksiyonunu anlatır olduğunu.

Düzenleme As @BenBolker istediğiniz olmayabilir, işaret ifelseişte bir olduğunu ifsürümü.

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

@Frank'a zinciri devam ettirmek için ve ifadelerimin {etrafında parantez kullanmam gerektiğini belirttiği için teşekkürler .ififelse


4
Düzenleme sonrası versiyonu beğendim. ifelsekontrol akışı için doğal görünmüyor.
Frank

7
Unutulmaması gereken bir şey: zincirde daha sonraki bir adım varsa, kullanın {}. Örneğin, burada yoksa, kötü şeyler olur (sadece Ybir sebepten dolayı yazdırın ): X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
Frank

Magrittr takma adının addkullanılması örneği daha net hale getirecektir.
ctbrown

Kod golf terimlerinde, bu özel örnek şu şekilde yazılabilir, X %>% add(1*Y)ancak elbette bu asıl soruyu yanıtlamaz
talat

1
{}Aradaki koşullu blok içindeki önemli bir şey , dplyr borusunun (LHS olarak da adlandırılır) önceki bağımsız değişkenine nokta (.) İle başvurmanız gerektiğidir - aksi takdirde koşullu blok. tartışma!
Agile Bean

33

Sanırım bu bir durum purrr::when. Toplamları 25'in altındaysa birkaç sayıyı toplayalım, aksi takdirde 0 döndürür.


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

whenilk geçerli koşulun eyleminden kaynaklanan değeri döndürür. Koşulu soluna ~, eylemi sağına koyun. Yukarıda sadece bir koşul (ve sonra başka bir durum) kullandık, ancak birçok koşulunuz olabilir.

Bunu daha uzun bir boruya kolayca entegre edebilirsiniz.


2
Güzel! Bu aynı zamanda 'geçişe' daha sezgisel bir alternatif sağlar.
Steve G. Jones

16

İşte @JohnPaul tarafından sağlanan cevabın bir varyasyonu. Bu varyasyon, `if`bileşik bir if ... else ...ifade yerine işlevi kullanır .

library(magrittr)

X <- 1
Y <- TRUE

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

Bu durumda, küme parantezlerinin `if`işlevin etrafında veya bir ifelseişlevin çevresinde gerekli olmadığını unutmayın - yalnızca ifade çevresinde if ... else .... Bununla birlikte, nokta yer tutucusu yalnızca iç içe geçmiş bir işlev çağrısında görünürse , magrittr varsayılan olarak sol tarafı sağ tarafın ilk argümanına yönlendirir. Bu davranış, ifade küme parantezi içine alınarak geçersiz kılınır. Bu iki zincir arasındaki farka dikkat edin:

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

Nokta yer tutucusu, her iki kez de `if`işlevde göründüğü ve sırasıyla . + 1ve . + 2olarak yorumlandığı için bir işlev çağrısı içinde yuvalanmıştır . Yani, ilk ifade, sonucunu döndürüyor (garip bir şekilde, fazladan kullanılmayan argümanlardan şikayet etmiyor) ve ikinci ifade, bu durumda istenen davranış olan sonucunu döndürüyor .`+`(., 1)`+`(., 2)`if`(1, TRUE, 1 + 1, 1 + 2)`if``if`(TRUE, 1 + 1, 1 + 2)

Magrittr boru operatörünün nokta yer tutucusuna nasıl davrandığı hakkında daha fazla bilgi için , yardım dosyasına%>% , özellikle " Noktayı ikincil amaçlar için kullanma" bölümüne bakın.


kullanmak `ìf`ve yapmak arasındaki fark ifelsenedir? davranışları aynı mı?
Agile Bean

@AgileBean ifve ifelseişlevlerinin davranışı aynı değil. ifelseFonksiyon vektörlü edilir if. ifİşleve bir mantıksal vektör sağlarsanız, bir uyarı yazdırır ve bu mantıksal vektörün yalnızca ilk öğesini kullanır. Karşılaştırma `if`(c(T, F), 1:2, 3:4)için ifelse(c(T, F), 1:2, 3:4).
Cameron Bieganek

harika, açıklama için teşekkürler! Dolayısıyla, yukarıdaki problem vektörleştirilmemiş olduğundan, çözümünüzü şu şekilde de X %>% { ifelse(Y, .+1, .+2) }
Agile Bean

12

Borulardan biraz uzaklaşmak benim için en kolayı gibi görünüyor (diğer çözümleri görmekle ilgilenmeme rağmen), örneğin:

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

Bu, @ JohnPaul'un cevabının küçük bir değişikliğidir (gerçekten istemeyebilirsiniz ifelse, bu hem argümanlarını değerlendirir hem de vektörleştirilir). .Koşul yanlışsa otomatik olarak geri dönecek şekilde bunu değiştirmek güzel olurdu ... ( dikkat : Bunun işe yaradığını düşünüyorum ama gerçekten test etmedim / çok fazla düşünmedim ...)

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

Hoşuma gitti purrr::whenve burada sağlanan diğer temel çözümlerin hepsi harika ama daha kompakt ve esnek bir şey istedim, bu yüzden işlevi pif(boru eğer) tasarladım , cevabın sonunda kodu ve dokümanı görün.

Bağımsız değişkenler, işlevlerin iki ifadesi olabilir (formül gösterimi desteklenir) ve koşul ise, girdi varsayılan olarak değiştirilmeden döndürülür FALSE.

Diğer yanıtlardan örneklerde kullanılır:

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

Diğer örnekler:

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

Fonksiyon

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

"Öte yandan, işlevler veya formüller yalnızca ilgili koşul karşılanırsa verilere uygulanacaktır." Neden bunu yapmaya karar verdiğinizi açıklayabilir misiniz?
mihagazvoda

Bu yüzden sadece hesaplamam gereken şeyi hesaplıyorum ama bunu neden ifadelerle yapmadığımı merak ediyorum. Bazı nedenlerden dolayı standart dışı değerlendirme kullanmak istemedim. Özel işlevlerimde değiştirilmiş bir sürümüm olduğunu düşünüyorum, şansım olduğunda güncelleme yapacağım.
Moody_Mudskipper

Lütfen güncellediğinizde bana bildirin. Teşekkür ederim!
mihagazvoda
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.