読者です 読者をやめる 読者になる 読者になる

purrr 0.1.0を使ってみる(6) cross

crossは、複数ベクトルの直積集合を返す、すなわち、いい感じのexpand.grid()です。

完。

...というくらいに特筆すべきことはないと思うんですけど、↓こんな記事もあったので、いちおう書いときます。

cross_d()

これがexpand.grid()と同じ動作をするものです。

とヘルプに書いてあったのでさっそく↑の記事と同じコードを試してみると、エラーになります。

library(purrr)

age <- seq(from = 20, to = 40, by = 5)
cross_d(性別 = c("男性", "女性"), 年齢 = age)
#> Error in cross_d(性別 = c("男性", "女性"), 年齢 = age) : 
#>   unused arguments (性別 = c("男性", "女性"), 年齢 = age)

これはなぜかと言うと、cross_d()は引数にデータを並べるタイプ(...)ではなく、データをまとめてlistにしたものを渡すタイプだからです。

引数の型が違うときにすることは何か。一つしかありませんね。そう、liftです。

...(d)をlist(l)にしたいので、lift_ld()を使います。これでgrid.expand()と同じ動作ができます。

lift_ld(cross_d)(性別 = c("男性", "女性"), 年齢 = age)
#> Source: local data frame [10 x 2]
#> 
#>     性別  年齢
#>    (chr) (dbl)
#> 1   男性    20
#> 2   女性    20
#> 3   男性    25
#> 4   女性    25
#> 5   男性    30
#> 6   女性    30
#> 7   男性    35
#> 8   女性    35
#> 9   男性    40
#> 10  女性    40

いや、素直にlistを渡してもいいんですけど。

cross_d(list(性別 = c("男性", "女性"), 年齢 = age))

cross_n()cross2()cross3()

cross_d()はdata.frameを返してましたが、cross_n()はそのlist版です。ヘルプに載ってる例はこんな感じ。

data <- list(
  id = c("John", "Jane"),
  greeting = c("Hello.", "Bonjour."),
  sep = c("! ", "... ")
)

# 組み合わせのlistのlistをつくる
data_crossed <- cross_n(data)

str(data_crossed, list.len = 4)
#> List of 8
#>  $ :List of 3
#>   ..$ id      : chr "John"
#>   ..$ greeting: chr "Hello."
#>   ..$ sep     : chr "! "
#>  $ :List of 3
#>   ..$ id      : chr "Jane"
#>   ..$ greeting: chr "Hello."
#>   ..$ sep     : chr "! "
#>  $ :List of 3
#>   ..$ id      : chr "John"
#>   ..$ greeting: chr "Bonjour."
#>   ..$ sep     : chr "! "
#>  $ :List of 3
#>   ..$ id      : chr "Jane"
#>   ..$ greeting: chr "Bonjour."
#>   ..$ sep     : chr "! "
#>   [list output truncated]

map_chr(data_crossed, lift_dl(paste))
#> [1] "John! Hello."     "Jane! Hello."     "John! Bonjour."   "Jane! Bonjour."   "John... Hello."   "Jane... Hello."   "John... Bonjour."
#> [8] "Jane... Bonjour."

cross2()とかcross3()はlistじゃなくて、引数を2つ、3つ並べる版です。

↑の例はこんな感じにも書けます。ただし、列名にあたるものを指定することはできないので、あとでidgreetingのような列名でデータを参照したい場合は向いていません。今回は特に列名を必要しないので問題ないですけど。

cross3(
  c("John", "Jane"),
  c("Hello.", "Bonjour."),
  c("! ", "... ")
) %>%
  map_chr(lift_dl(paste))

.filter引数

expand.grid()とひとつ違う点として、.filterに叙述関数(predicate function)を指定できるということがあります。これを指定すると、その関数がTRUEを返す行は除外されます。

たとえば「1~3の数字で、別々の数字の組み合わせ」というのは以下のように書けます。ヘルプに載ってる例です。

seq_len(3) %>%
  list(x = ., y = .) %>%
  cross_n(.filter = `==`)
#> Source: local data frame [6 x 2]
#> 
#>       x     y
#>   (int) (int)
#> 1     2     1
#> 2     3     1
#> 3     1     2
#> 4     3     2
#> 5     1     3
#> 6     2     3

これはちょっと便利かも。でも、ふつうにdiscard()を使えばいい気もするけど。。

感想

expand.grid()でそんなに困らない気もしますが、たしかに関数にmap()するときとかはdata.frameよりlistの方が便利なこともあるので、これはこれで有用かも。でも、わざわざ覚えるほどでもないかなあ...というのが個人的な感想です。