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

purrr 0.1.0を使ってみる(3) zip

R purrr

追記(2015/11/15): zip_*()transpose()になるらしいです。やっぱりまだコロコロ変わりますね...。

github.com


zipは、複数のベクトルを、各要素のペアのリストにします。PythonとかHaskellにあるのと同じやつです。

ただ、すべてがベクトルなRだと、他の言語とちょっと感覚が違います。purrrにはunzipがありません(正確には、昔あったけどなくなりました)。zip_n()がzipもunzipも兼ねています。後述しますが、matrixをt()するたびに行と列が入れ替わるように、zip_n()するたび行と列(?)が入れ替わるイメージです。

ちなみに、タイトルに「zip」と書いといてあれなんですが、RではutilsにZIPファイルをつくるzip()という関数があるので、purrrにはzip()という名前の関数はありません。ややこしい…

zip2, zip3

zip2は、2つのベクトルを、zip3は3つのベクトルをzipします。

こんな感じです。

zip2(1:3, letters[1:3]) %>% str
#> List of 3
#> $ :List of 2
#> ..$ : int 1
#> ..$ : chr "a"
#> $ :List of 2
#> ..$ : int 2
#> ..$ : chr "b"
#> $ :List of 2
#> ..$ : int 3
#> ..$ : chr "c"

1,2,3"a", "b", "c"という2つのベクトルが、それぞれの1番目の要素のペア(1, "a")、2番目の要素のペア(2"b")、3番目の要素のペア(3"c")のリストに変換されています。

型が同じものをzipするときは、.simplify = TRUEをつけるとベクトルにしてくれます。

zip2(1:3, 11:13, .simplify = TRUE) %>% str
List of 3
 $ : int [1:2] 1 11
 $ : int [1:2] 2 12
 $ : int [1:2] 3 13

zip_n

zip_nは、ベクトルのリストをzipします。

zip2zip3は実はzip_nのラッパーです。たとえばzip2(x, y)zip_n(list(x, y))のショートカットです。

これは、冒頭に書いたように行と列を入れ変える感覚で使えます。たとえば、ヘルプに書いてある例はこんな感じです(スペース節約のため、ちょっと短めバージョンでやります。)

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 %>% zip_n() %>% 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 %>% zip_n() %>% zip_n() %>% 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

なんということでしょう。zip_n()を2回適用すると、元の形になっています。

これをうまく使うと、データの方向を入れ変えながら処理を進めていくことができます。たぶん(具体的なユースケースがまだピンときていない...)。

zipの注意点

行と列を入れ変える感覚で使える、と書きましたが、元の情報が失われてしまうパターンもあるので油断は禁物です。今のところ、使ってる中で気づいたのは以下の2つですが、もっと留意事項は多いと思います。

長さが違う場合

ベクトルの長さが違う場合は短い方に合わせられます。特にエラーとかでないので、この挙動にはちょっと注意が必要な気がします。(足りない分はNAで埋めたりするオプションほしい…)

zip2(1:2, 11:13, .simplify = TRUE) %>% str
#> List of 2
#>  $ : int [1:2] 1 11
#>  $ : int [1:2] 2 12

factorの扱い。

今のところfactorをうまく扱えません。数値変換されてしまいます。

list(x = factor(letters[1:3]), y = factor(LETTERS[1:3])) %>% zip_n
#> [[1]]
#> [[1]]$x
#> [1] 1
#> 
#> [[1]]$y
#> [1] 1
#> 
#> 
#> [[2]]
#> [[2]]$x
#> [1] 2
#> 
#> [[2]]$y
#> [1] 2
#> 
#> 
#> [[3]]
#> [[3]]$x
#> [1] 3
#> 
#> [[3]]$y
#> [1] 3

Issueは上がっているのでそのうち改善されるような気はしますが、けっこう悩ましい問題だと思います。

github.com

感想

zipはけっこうR独特の世界ですね。手ごわい。。