magrittr 1.5を試してみる。

2週間ほど前にmagrittr 1.5が出ていました。

なんか今更感あるけど、そしてあんまりちゃんとmagrittr使ったことないけど、けっこう面白そうな機能が増えてるのでNEWSに書いてある内容を試してみました。

Functional sequences

A pipeline, or a "functional sequence", need not be applied to a left-hand side value instantly. Instead it can serve as a function definition. A pipeline where the left-most left-hand side is the magrittr placeholder (the dot .) will thus create a function, which applies each right-hand side in sequence to its argument (https://github.com/smbache/magrittr/blob/5d1e52654f9c94796d98f88a968aab434a665aa0/NEWS.md#functional-sequences)

パイプで処理をつないでくだけじゃなくて合成関数もつくれるようになりましたよ、と。

パイプラインの一番右側に.が来たときは関数をつくります。例えば、以下のようなirisの各Speciesを数える処理は、

> library(magrittr)
> library(dplyr)

> iris %>%
+   group_by(Species) %>%
+   summarize(n())

もっと一般的にこんな感じで書けるようになりました。

> f <- . %>%
+   group_by_(names(.)[5]) %>%
+   summarize(n())

> f(iris)
Source: local data frame [3 x 2]

     Species n()
1     setosa  50
2 versicolor  50
3  virginica  50

> f(airquality)
Source: local data frame [5 x 2]

  Month n()
1     5  31
2     6  30
3     7  31
4     8  31
5     9  30

なにこれ! めっちゃ便利!!!

Compound assignment pipe: %<>%

Pipe an object forward into a function or call expression and update the lhs object with the resulting value. (ヘルプより)

%<>%は、変数を処理してまた同じ変数に代入する、という処理です。

これまでは、-><-で代入しないといけなかったのが、

> iris %>% mutate(Species.character = as.character(Species)) -> iris

%<>%ひとつ書けばOKになります。

> iris %<>% mutate(Species.character = as.character(Species))

Tee pipe: %T>%

Pipe a value forward into a function- or call expression and return the original value instead of the result. This is useful when an expression is used for its side-effect, say plotting or printing. 〉(ヘルプより)

シェルコマンドのteeのように、結果を出力してさらに処理を続ける、ということができます。例としては次のような処理が挙げられています。

rnorm(200) %>%
  matrix(ncol = 2) %T>%
  plot %>% # plot usually does not return anything.
  colSums

で、グラフがplotされつつcolSumsの結果が出力されるわけですが、私ははじめ何やってるのか理解できませんでした。。

ゆっくり追ってみます。さくっと分かった方は読み飛ばしてください。

ふつうの%>%は、左から来たものに右の処理を適用して、その結果を次の処理に渡します。↓で言えば、rnorm(200)matrixにしたものが次の処理にされます。

rnorm(200) %>%
  matrix(ncol = 2)

で次に、%T>%です。%T>%は、左から来たものに右の処理を適用しますが、次の処理に渡すのは左の値です。処理されていない値をそのまま渡します。

↓で言えば、...plotしつつ、...をそのまま次のcolSumsに渡します。

... %T>%
  plot %>%
  colSums

これは油断すると頭がこんがらがりそうですね。。シェルスクリプトの場合teeは構文ではなくてコマンドなので、同じ感覚で理解しようとするとやけどします。

Exposition pipe: %$%

Expose the names in lhs to the rhs expression. This is useful when functions do not have a built-in data argument. (ヘルプより)

これは、イメージ的には左から来た値をattach()した状態で右の処理を評価する、というパイプです。

例として以下が挙げられています。

iris %>%
  subset(Sepal.Length > mean(Sepal.Length)) %$%
  cor(Sepal.Length, Sepal.Width)

ここで、subsetNon-standard evaluationでやってくれるのでSepal.Length, Sepal.Lengthとだけ書けば解釈してくれます。しかし、corはそんなことはありません。ただ%>%で渡したのだと↓と同じです。

> cor(Sepal.Length, Sepal.Width)
Error in is.data.frame(y) : object 'Sepal.Width' not found

%$%でパイプをすることで、以下のような感じになります。イメージ的には。

> attach(iris)
> cor(Sepal.Length, Sepal.Width)
[1] -0.1175698

これは、corの側を改善してほしいなという気もしますが、、まあ現実的な解決策だと思います。

Lambdas

Lambdas can now be made by enclosing several statements in curly braces, and is a unary function of the dot argument.

Rでラムダが必要かどうか、という議論はたまに見かけますが、ふつうにfunction(x)を使って無名関数は書けます(以下の例はvignettes参照)。

car_data %>%
(function(x) {
  if (nrow(x) > 2) 
    rbind(head(x, 1), tail(x, 1))
  else x
})

これを、function(x)を省略して以下のように書けるようになったということです。

car_data %>%
{ 
  if (nrow(.) > 0)
    rbind(head(., 1), tail(., 1))
  else .
}

これも「Functional sequences」と同じく、一変数関数ならという条件付きではあります。

まとめ

magrittr、ちゃんと使い方調べたことなかったんですけど面白いですね。まだまだ使い方分からないことも多いですが、少しずつ勉強していきたいです。