Rパイプ演算子を使用する場合の条件付き評価%>%


93

パイプ演算子を使用するとき%>%のようなパッケージでdplyrggvisdycharts、など、どのように行うの私は条件付きでステップを実行しますか?例えば;

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
(ベンが提供したように)一緒に作業する例が望ましいでしょう、fyi。
フランク

回答:


104

.andを利用した簡単な例を次に示しますifelse

X<-1
Y<-T

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

ifelse、ifYTRUE1を追加する場合、それ以外の場合は、の最後の値を返すだけですX。ザ・.は、チェーンの前のステップからの出力がどこに行くかを関数に伝える代用であるため、両方のブランチで使用できます。

編集 @BenBolkerが指摘したように、あなたは望まないかもしれないのでifelse、ここにifバージョンがあります。

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

チェーンを継続するには、andステートメントを{中括弧で囲む必要があることを指摘してくれた@Frankに感謝します。ififelse


4
私は編集後のバージョンが好きです。ifelse制御フローにとって不自然に思えます。
フランク

7
注意すべきことの1つは、チェーンに後のステップがある場合は、を使用すること{}です。たとえば、ここにそれらがない場合、悪いことが起こります(Y何らかの理由で印刷するだけです): X %>% "+"(1) %>% {if(Y) "+"(1) else .} %>% "*"(5)
Frank

magrittrエイリアスaddを使用すると、例がより明確になります。
ctbrown 2015

コードゴルフの用語では、この特定の例は次のように書くことができますX %>% add(1*Y)が、もちろん元の質問には答えません
talat 2016

1
間の条件付きブロック内の重要なことの1つ{}は、dplyrパイプ(LHSとも呼ばれる)の前の引数をドット(。)で参照する必要があることです。そうしないと、条件付きブロックは。を受け取りません。引数!
アジャイルビーン

32

の場合だと思いますpurrr::when。合計が25未満の場合はいくつかの数値を合計し、それ以外の場合は0を返します。


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

when最初の有効な条件のアクションの結果の値を返します。の左側に条件を置き、~その右側にアクションを置きます。上記では、1つの条件(次に他のケース)のみを使用しましたが、多くの条件を使用できます。

それをより長いパイプに簡単に統合できます。


2
いいね!これは、「スイッチ」のより直感的な代替手段も提供します。
スティーブ・G・ジョーンズ

16

これは@JohnPaulによって提供された回答のバリエーションです。このバリエーションでは、`if`複合if ... else ...ステートメントの代わりに関数を使用します。

library(magrittr)

X <- 1
Y <- TRUE

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

この場合、中括弧は`if`関数の周囲にも関数の周囲にも必要ではなくifelseif ... else ...ステートメントの周囲にのみ必要であることに注意してください。ただし、ドットプレースホルダーがネストされた関数呼び出しにのみ表示される場合、magrittrはデフォルトで、左側を右側の最初の引数にパイプします。この動作は、式を中括弧で囲むことによってオーバーライドされます。これら2つのチェーンの違いに注意してください。

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`余分な未使用の引数について文句を言わない)、2番目の式はの結果を返し`if`(TRUE, 1 + 1, 1 + 2)ます。これはこの場合の望ましい動作です。

方法の詳細についてはmagrittrを参照パイプオペレータ扱いドットプレースホルダ、ヘルプファイルのために%>%、「二次的目的のためにドットを使用した」上の特定のセクション。


使用しての違いは何である`ìf`とはifelse?それらは動作が同じですか?
アジャイルビーン

@AgileBeanififelse関数の動作は同じではありません。ifelse関数がベクトル化されますif。あなたが提供する場合はif論理ベクトルを持つ関数を、それは警告を出力しますし、それだけでその論理ベクトルの最初の要素を使用します。と比較`if`(c(T, F), 1:2, 3:4)してくださいifelse(c(T, F), 1:2, 3:4)
キャメロンビーガネック

わかりやすくしてくれてありがとう!したがって、上記の問題はベクトル化されていないため、ソリューションを次のように記述することもできます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両方の引数を評価してベクトル化する、は本当に必要ないかもしれません)。.条件がfalseの場合に自動的に戻るようにこれを変更すると便利です...(注意:これは機能すると思いますが、実際にはテスト/検討 しすぎていません...)

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.