purrr 0.2.0がCRANにきてました。けっこう色々変更があります。
dplyrとの兼ね合いでどこを変えるかまだ試行錯誤している雰囲気があって、まだまだbreaking changeが入りそうな雰囲気です。がっつり使うのはまだ控えたほうがいいかも...。
新しい関数
%||%
x %||% y
is shorthand forif (is.null(x)) y else x
(#109).
x
がNULL
だったらy
の値を返す中置演算子です。
1 %||% 2 #> [1] 1 NULL %||% 2 #> [1] 2
%@%
x %@% "a"
is shorthand forattr(x, "a", exact = TRUE)
(#69).
attributeを取り出す中置演算子です。
matrix(1:6, nrow = 2) %@% "dim" #> [1] 2 3
accumulate()
accumulate()
has been added to handle recursive folding.
baseの関数Reduce()
の方はaccumerate = TRUE
という引数があって、
Reduce(c, 1:5, 0, accumulate = TRUE)
みたいに結果を貯めていくことができたんですが、これをpurrrでは↓のように書けるようになります。
1:5 %>% accumulate(c)
Reduce()
のinit
引数にあたる.init
引数を指定することもできます。
1:10 %>% accumulate(max, .init = 5) #> [1] 5 5 5 5 5 5 6 7 8 9 10
右から折りたたみたいときにはaccumulate_right()
、reduce_right()
があるらしいです。
1:10 %>% accumulate_right(min, .init = 5) #> [1] 1 2 3 4 5 5 5 5 5 5 5
map_df()
map_df()
row-binds output together. It's the equivalent ofplyr::ldply()
(#127)
これ便利です。
これまで、map()
した結果を一つのdata.frameにするのはdplyr::bind_rows()
を呼び出してましたが、
1:10 %>% map(~ list(waru10 = ./10, kakeru5 = .*5)) %>% dplyr::bind_rows()
map_df()
できるようになります。
1:10 %>% map_df(~ list(waru10 = ./10, kakeru5 = .*5)) #> Source: local data frame [10 x 2] #> #> waru10 kakeru5 #> (dbl) (dbl) #> 1 0.1 5 #> 2 0.2 10 #> 3 0.3 15 #> 4 0.4 20 #> 5 0.5 25
あれ? listなのに_df
だと型が違うってエラーにならないの?と思ったそこのアナタ(たぶんいない…)
そうそう、purrrはちょっと型に対して甘くなりました。型が変換できるときは変換する、というポリシーになったのです。これNEWSに載せないのかな...
ということで、Japan.Rでの私のつぶやきは今や嘘です。忘れてください。
flatten()
の挙動変更(flatmap()
のは廃止)、flatten()
の型指定版
flatten()
is now type-stable and always returns a list. To return a simpler vector, useflatten_lgl()
,flatten_int()
,flatten_dbl()
,flatten_chr()
, orflatten_df()
.
flatmap()
-> usemap()
followed by the appropriateflatten()
.
flatmap()
がdeprecatedになりました。また、flatten()
にも型を指定するバージョンの関数ができました。
flatten()
はunlist()
的なやつです。あんまり前の挙動を覚えてないので変化がピンとこないんですが、、こういう動きです。
x <- map(2:3, sample) x #> [[1]] #> [1] 1 2 #> #> [[2]] #> [1] 3 1 2 x %>% flatten() #> [[1]] #> [1] 4 #> #> [[2]] #> [1] 1 #> #> [[3]] #> [1] 3 #> #> [[4]] #> [1] 2 #> #> [[5]] #> [1] 1 #> #> [[6]] #> [1] 3 #> #> [[7]] #> [1] 2 #> #> [[8]] #> [1] 4
平らなlist()
になってますね。これをvectorにまとめるには、flatten_XXX
を使います。int
はInteger、dbl
はdouble、chr
はcharacter、lgl
はlogical、df
はdata.frameです。
x %>% flatten_int() #> [1] 4 1 3 2 1 3 2 4
invoke()
の挙動変更(map_call()
は廃止)、invoke_map()
invoke()
has been overhauled to be more useful: it now works similarly tomap_call()
when.x
is NULL, and hencemap_call()
has been deprecated.
invoke()
はdo.call()
の単純なラッパーになりました。関数ひとつを適用するだけです。これに伴って、map_call()
が非推奨になりました。
invoke(runif, list(n = 10)) #> [1] 0.56532877 0.02724676 0.60148070 0.07418137 0.16318623 0.04143166 #> [7] 0.72120653 0.39732728 0.95660030 0.11342016 do.call(runif, list(n = 10)) #> [1] 0.2695608 0.3741370 0.3705948 0.7065786 0.3497762 0.7854298 0.6258898 #> [8] 0.3753446 0.3891058 0.7384476
関数の引数をlistじゃなくて名前付き引数でも渡せるのがdo.call()
と微妙に違います。
do.call(runif, n = 10) #> Error in do.call(runif, n = 10) : unused argument (n = 10) invoke(runif, list(n = 10)) #> [1] 0.07460899 0.41545157 0.35268372 0.02313332 0.60232309 0.52617241 #> [7] 0.84474122 0.73989323 0.67794054 0.96395660
複数の関数を適用にするには、新しいinvoke_map()
を使います。
list(m1 = mean, m2 = median) %>% invoke_map(x = rcauchy(100)) #> $m1 #> [1] 1.669889 #> #> $m2 #> [1] 0.09461299
flatten_XXX()
やmap_XXX()
のように、型指定の関数もあります。
list(m1 = mean, m2 = median) %>% invoke_map_dbl(x = rcauchy(100)) #> m1 m2 #> 1.57018475 -0.04951891
transpose()
(zipX()
は廃止)、simplify_all()
transpose()
replaceszip2()
,zip3()
, andzip_n()
(#128).
It no longer has fields argument or the
.simplify
argument; instead use the newsimplify_all()
function.
他の言語とちょっと違って分かりづらかったzipX()
は廃止されました。transpose()
は、名前の通り、listを転置する関数です。こういう動きをします。2回転置すると元に戻ります。
set.seed(1) x <- rerun(3, x = runif(1), y = runif(3)) x %>% str() #> List of 3 #> $ :List of 2 #> ..$ x: num 0.266 #> ..$ y: num [1:3] 0.372 0.573 0.908 #> $ :List of 2 #> ..$ x: num 0.202 #> ..$ y: num [1:3] 0.898 0.945 0.661 #> $ :List of 2 #> ..$ x: num 0.629 #> ..$ y: num [1:3] 0.0618 0.206 0.1766 x %>% transpose() %>% str() #> List of 2 #> $ x:List of 3 #> ..$ : num 0.266 #> ..$ : num 0.202 #> ..$ : num 0.629 #> $ y:List of 3 #> ..$ : num [1:3] 0.372 0.573 0.908 #> ..$ : num [1:3] 0.898 0.945 0.661 #> ..$ : num [1:3] 0.0618 0.206 0.1766 x %>% transpose() %>% transpose() %>% str() #> List of 3 #> $ :List of 2 #> ..$ x: num 0.266 #> ..$ y: num [1:3] 0.372 0.573 0.908 #> $ :List of 2 #> ..$ x: num 0.202 #> ..$ y: num [1:3] 0.898 0.945 0.661 #> $ :List of 2 #> ..$ x: num 0.629 #> ..$ y: num [1:3] 0.0618 0.206 0.1766
simplify_all()
を使うと、listの各要素を可能な限りvectorにまとめてくれます。
x %>% transpose() %>% simplify_all() #> $x #> [1] 0.2655087 0.2016819 0.6291140 #> #> $y #> [1] 0.37212390 0.57285336 0.90820779 0.89838968 0.94467527 0.66079779 #> [7] 0.06178627 0.20597457 0.17655675
safely()
、quietly()
、possibly()
safely()
,quietly()
, andpossibly()
are experimental functions for working with functions with side-effects (e.g. printed output, messages, warnings, and errors) (#120).
これは、はじめはHaskellとかでいうMaybeをつくろうとしてたみたいなんですが、結果とエラーとメッセージをリストとして返す、というものになりました。
こんな感じです。
safe_log <- safely(log) safe_log(10) #> $result #> [1] 2.302585 #> #> $error #> NULL safe_log("a") #> $result #> NULL #> #> $error #> <simpleError in .f(...): non-numeric argument to mathematical function>
otherwise
引数を指定すれば、エラーになった場合でもresult
に値が入ります。これでMaybeっぽい使い方ができるかもしれません。
list_along()
, rep_along()
list_along()
andrep_along()
generalise the idea ofseq_along()
.
list_along()
は、引数に指定したオブジェクトと同じ長さのlistを返します。rep_along()
は、第一引数に指定したオブジェクトの長さだけ、第二引数のオブジェクトを繰り返します。
x <- 1:5 rep_along(x, 1:2) #> [1] 1 2 1 2 1 list_along(x) #> [[1]] #> NULL #> #> [[2]] #> NULL #> #> [[3]] #> NULL #> #> [[4]] #> NULL #> #> [[5]] #> NULL
is_null()
is_null()
is the snake-case version ofis.null()
.
スネークケースになっただけみたいです。もはや意地ですね…。
この辺のpredicate functionは別のパッケージに移そうという計画もあるみたいです。
pmap()
(map_n()
は廃止)
pmap()
(parallel map) replacesmap_n()
(#132)
map_n()
は、pmap()
にリネームされました。またpmap()
はmap()
と同じくlgl
とかint
とかいう型指定版があります。
つまり、まとめるとこんな感じです。
返り値の型 | 引数が1つ | 引数が2つ | 引数が3つ以上 |
---|---|---|---|
logical | map_lgl() |
map2_lgl() |
pmap_lgl() |
integer | map_int() |
map2_int() |
pmap_int() |
double | map_dbl() |
map2_dbl() |
pmap_dbl() |
character | map_chr() |
map2_chr() |
pmap_chr() |
data.frame | map_df() |
map2_df() |
pmap_df() |
set_names()
set_names()
is a snake-case alternative tosetNames()
with stricter equality checking
setNames()
よりちょっと引数チェックが厳しい関数らしいです。よく分からない。(知らない方も多いかもしれませんが、setNames()
は、名前つきベクトルを一発でつくれる便利な関数です)
set_names(1:4, c("a", "b", "c", "d")) #> a b c d #> 1 2 3 4
名前を省略すると、それぞれの要素がそのまま名前になります。
set_names(letters[1:5]) #> a b c d e #> "a" "b" "c" "d" "e"
行指向の関数
行指向とはどういうことかというと、dplyrみたいな感じのことができるようになるということです。
dmap()
, dmap_at()
, dmap_if()
map()
now always returns a list. Data frame support has been moved tomap_df()
anddmap()
. The latter supports sliced data frames as a shortcut for the combination ofby_slice()
anddmap()
:x %>% by_slice(dmap, fun, .collate = "rows")
. The conditional variantsdmap_at()
anddmap_if()
also support sliced data frames and will recycle scalar results to the slice size.
dmap()
は、dplyr::mutate_each()
みたいなものです。slice_rows()
はgroup_by()
のラッパーみたいなものです(grouped_df
が返ってくる)。
# slice_rows("cyl")の代わりにgroup_by(cyl)にしても同じ sliced_df <- mtcars[1:5] %>% slice_rows("cyl") sliced_df %>% dmap(mean) #> Source: local data frame [3 x 5] #> #> cyl mpg disp hp drat #> (dbl) (dbl) (dbl) (dbl) (dbl) #> 1 4 26.66364 105.1364 82.63636 4.070909 #> 2 6 19.74286 183.3143 122.28571 3.585714 #> 3 8 15.10000 353.1000 209.21429 3.229286
これと同じことをdmap()
を使わずにやろうと思うと、たぶん↓こんな感じですがよく分かりませんでした。
mtcars[1:5] %>% split(.$cyl) %>% map(map_dbl, mean)
めちゃくちゃ便利そうですが、dplyrとの使い分けが気になるところです。
invoke_rows()
(map_rows()
の代わり)
map_rows()
has been renamed toinvoke_rows()
.
これむずくてよく分からなかったのでパス。。
.to
引数、.collate
引数
The rows-based functionals gain a
.to
option to name the output column as well as a.collate
argument. The latter allows to collate the output in lists (by default), on columns or on rows. This makes these functions more flexible and more predictable.
これはどういうことかというと、行指向の関数は、新しい列をつくって結果をそこに格納します。.to
引数はそのときの名前を選びます。デフォルトだと.out
という名前になります。
mtcars[1:2] %>% by_row(function(x) 1:5) #> Source: local data frame [32 x 3] #> #> mpg cyl .out #> (dbl) (dbl) (list) #> 1 21.0 6 <int[5]> #> 2 21.0 6 <int[5]> #> 3 22.8 4 <int[5]> #> 4 21.4 6 <int[5]> #> 5 18.7 8 <int[5]> #> .. ... ... ...
.collate
引数は、その結果をどう展開するかを指定します。"list"
だと上のようにネストした状態で格納されます。"rows"
だと行方向に展開されます。元のdata.frameと.out
をjoinするようなイメージです。
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows") #> Source: local data frame [160 x 4] #> #> mpg cyl .row .out #> (dbl) (dbl) (int) (int) #> 1 21 6 1 1 #> 2 21 6 1 2 #> 3 21 6 1 3 #> 4 21 6 1 4 #> 5 21 6 1 5 #> .. ... ... ... ...
"cols"
だと列方向に展開されます。その分、列が増えます。
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols") #> Source: local data frame [32 x 7] #> #> mpg cyl .out1 .out2 .out3 .out4 .out5 #> (dbl) (dbl) (int) (int) (int) (int) (int) #> 1 21.0 6 1 2 3 4 5 #> 2 21.0 6 1 2 3 4 5 #> 3 22.8 4 1 2 3 4 5 #> 4 21.4 6 1 2 3 4 5 #> 5 18.7 8 1 2 3 4 5 #> .. ... ... ... ... ... ... ...
その他
as_function()
as_function()
, which converts formulas etc to functions, is now exported (#123).
これは、map()
などなどpurrrの関数の中でラムダ関数をつくるのに使われている関数です。具体的には、以下のような挙動をします。
If a function, it is used as is.
If a formula, e.g.~ .x + 2
, it is converted to a function with two arguments,.x
or.
and.y
. This allows you to create very compact anonymous functions with up to two inputs.
If character or integer vector, e.g."y"
, it is converted to an extractor function,function(x) x[["y"]]
. To index deeply into a nested list, use multiple values;c("x", "y")
is equivalent toz[["x"]][["y"]]
.
引数が関数なら、そのまま関数が返ってきます。
引数がformulaなら、.
と.x
を第一引数、.y
を第二引数とする関数に変換します。
引数がcharacterかnumericなら、ベクトルからそのインデックスの要素を取り出す関数に変換します。例えば、as_function(1)
は、1つ目の要素を取り出す関数です。
Cで実装
map*()
now use custom C code, rather than relying onlapply()
,mapply()
etc. The performance characteristcs are very similar, but it allows us greater control over the output (#118).
これまでは単純にlapply()
とmapply()
の単なるラッパーでしたが、独自にCで実装したものを使うようになりました。これによってパフォーマンス面での向上が見込まれ、るかと思いきやそこはあんまり変わらなくて、出力をもっとうまくコントロールできるようになるとかなんとか。よくわかりません。
感想
予想通り?、dplyrの領分にも浸食し出したpurrr。これからも目が離せません。心配という意味で。下手をするとdplyrをぶっ壊しかねないのでは…と危惧しています。