{purrr} でリストデータを操作する <2>
前の記事に続けて、{purrr}
で {rlist}
相当の処理を行う。今回はレコードの選択とソート。
サンプルデータは前回と同じものを利用する。リストの表示も同じく Hmisc::list.tree
を使う。
library(rlist) library(pipeR) library(purrr) packages <- list( list(name = 'dplyr', star = 979L, maintainer = 'hadley' , authors = c('hadley', 'romain')), list(name = 'ggplot2', star = 1546L, maintainer = 'hadley' , authors = c('hadley')), list(name = 'knitr', star = 1047L, maintainer = 'yihui' , authors = c('yihui', 'hadley', '...and all')) ) packages ## x = list 3 (2632 bytes) ## . [[1]] = list 4 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain ## . [[2]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley ## . [[3]] = list 4 ## . . name = character 1= knitr ## . . star = integer 1= 1047 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ...
レコードの選択
rlist Tutorial: Filtering 後半の内容。
ある条件にあてはまるレコードのうち最初の N 件だけを取得したいとき、{rlist}
には専用の関数 list.find
がある。{purrr}
には対応する関数はないが、keep %>% head
で同じ結果が得られる。
packages %>>% rlist::list.find(star >= 1000, 1) ## x = list 1 (840 bytes) ## . [[1]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley packages %>% purrr::keep(~ .$star >= 1000) %>% head(n = 1) # 略
また、{rlist}
には list.findi
という インデックスを返す関数がある。purrr::keep
では、引数に logical
型のベクトルを渡せば TRUE
に対応する要素のみを残すことができる。
packages %>>% rlist::list.findi(star >= 1000, 2) ## [1] 2 3 purrr::keep(seq_along(packages), flatmap(packages, ~ .$star >= 1000)) %>% head(n = 2) # 略
条件にあてはまる最初のレコードだけを取得したいときは rlist::first
もしくは purrr::detect
。
rlist::list.first(packages, star >= 1000) ## x = list 4 (792 bytes) ## . name = character 1= ggplot2 ## . star = integer 1= 1546 ## . maintainer = character 1= hadley ## . authors = character 1= hadley purrr::detect(packages, ~ .$star >= 1000) # 略
レコード数がそこまで多くない場合は keep %>% head(n = 1)
でよいと思うが、結果に 1 レベル目が入れ子として残ってしまう。上と同じ結果を得るには purrr::flatten
( unlist(recursive = FALSE)
と同じ ) を通す。
purrr::keep(packages, ~ .$star >= 1000) %>% head(n = 1) ## x = list 1 (840 bytes) ## . [[1]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley purrr::keep(packages, ~ .$star >= 1000) %>% head(n = 1) %>% purrr::flatten() ## x = list 4 (792 bytes) ## . name = character 1= ggplot2 ## . star = integer 1= 1546 ## . maintainer = character 1= hadley ## . authors = character 1= hadley
同じく、最後のレコードを取得したいときは rlist::last
もしくは purrr::detect(.right = TRUE)
。これも自分としては keep %>% tail(n = 1)
のほうが覚えやすい。
list.last(packages, star >= 1000) ## x = list 4 (920 bytes) ## . name = character 1= knitr ## . star = integer 1= 1047 ## . maintainer = character 1= yihui ## . authors = character 3= yihui hadley ... purrr::detect(packages, ~ .$star >= 1000, .right = TRUE) # 略 purrr::keep(packages, ~ .$star >= 1000) %>% tail(n = 1) %>% purrr::flatten() # 略
類似の処理として、{rlist}
には list.take
という最初の N レコードを取得する関数と list.skip
という最初の N レコードを除外する関数がある。{purrr}
には対応する関数はないが、組み込みの head
と tail
でよいのでは...。
packages %>>% rlist::list.take(2) ## x = list 2 (1696 bytes) ## . [[1]] = list 4 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain ## . [[2]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley packages %>% head(n = 2) # 略 packages %>% rlist::list.skip(1) ## x = list 2 (1768 bytes) ## . [[1]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley ## . [[2]] = list 4 ## . . name = character 1= knitr ## . . star = integer 1= 1047 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ... packages %>% tail(length(.) - 1) # 略
ある条件が初めて偽となるまでレコードを取得する場合は rlist::list.takeWhile
もしくは purrr::head_while
。
packages %>>% rlist::list.takeWhile(star <= 1500) ## x = list 1 (896 bytes) ## . [[1]] = list 4 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain packages %>% purrr::head_while(~ .$star <= 1500) # 略
ある条件が初めて偽となったレコード以降を取得したい場合は rlist::list.skipWhile
。{purrr}
には対応する処理はないが、purrr::detect_index
を使って以下のように書ける。
packages %>>% rlist::list.skipWhile(star <= 1500) ## x = list 2 (1768 bytes) ## . [[1]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley ## . [[2]] = list 4 ## . . name = character 1= knitr ## . . star = integer 1= 1047 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ... purrr::detect_index(packages, ~ .$star > 1500) ## [1] 2 packages[purrr::detect_index(packages, ~ .$star > 1500):length(packages)] # 略
一方、{purrr}
には tail_while
という末尾からみて条件にあてはまるレコードだけを取得する関数がある。
packages %>% purrr::tail_while(~ .$star <= 1500) ## x = list 1 (968 bytes) ## . [[1]] = list 4 ## . . name = character 1= knitr ## . . star = integer 1= 1047 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ...
ある条件を満たす要素がいくつあるかを調べるには rlist::list.count
。{purrr}
なら keep %>% length
でよい。
list.count(packages, star > 1000) ## [1] 2 purrr::keep(packages, ~ .$star > 1000) %>% length() # 略
{rlist}
には リストの要素の名前を正規表現で抽出する list.match
がある。{purrr}
なら keep
でできる。stringr::str_match
はマッチした結果を matrix
で返すため、complete.cases
で logical
に変換する。
data <- list(p1 = 1, p2 = 2, a1 = 3, a2 = 4) list.match(data, "p[12]") ## x = list 2 (416 bytes) ## . p1 = double 1= 1 ## . p2 = double 1= 2 complete.cases(stringr::str_match(names(data), 'p[12]')) ## [1] TRUE TRUE FALSE FALSE keep(data, complete.cases(stringr::str_match(names(data), 'p[12]'))) # 略
logical
の処理
条件に当てはまるかどうかを logical
ベクトルとして返すには rlist::list.is
。これは rlist::list.mapv
と同じなのでは...? {purrr}
なら flatmap
もしくは map_lgl
。
packages %>>% rlist::list.is(star < 1500) ## [1] TRUE FALSE TRUE packages %>>% rlist::list.mapv(star < 1500) # 略 packages %>% purrr::flatmap(~ .$star < 1500) # 略 packages %>% purrr::map_lgl(~ .$star < 1500) # 略
また、logical
のリスト全体の真偽値を以下のように取得できる
- 要素全てが
TRUE
かどうか:rlist::list.all
もしくはpurrr::every
。 - 要素いずれかが
TRUE
かどうか:rlist::list.any
もしくはpurrr::some
。
自分は purrr::flatmap %>% all
もしくは purrr::flatmap %>% any
のほうが覚えやすい。
ただし、{purrr}
v0.1.0 の every
, some
にはバグがあり正しく動作しない。下のスクリプトを実行するためには GitHub から最新の master をインストールする必要がある。
rlist::list.all(packages, star >= 500) ## [1] TRUE # install_github('hadley/purrr') # が必要 purrr::every(packages, ~ .$star >= 500) # 略 packages %>% purrr::flatmap(~ .$star >= 500) %>% all() # 略 rlist::list.any(packages, star >= 1500) ## [1] TRUE # install_github('hadley/purrr') # が必要 purrr::some(packages, ~ .$star >= 1500) packages %>% purrr::flatmap(~ .$star >= 1500) %>% any() # 略
要素の削除
{rlist}
では list.remove
で指定した名前をリストから除外できる。{purrr}
には discard
という条件にマッチする要素をリストから除外する関数がある ( purrr::keep
とは逆 )。条件の否定をとれば keep
でも書ける。
rlist::list.remove(data, c("p1", "p2")) ## x = list 2 (416 bytes) ## . a1 = double 1= 3 ## . a2 = double 1= 4 purrr::discard(data, names(data) %in% c("p1", "p2")) # 略 purrr::keep(data, !(names(data) %in% c("p1", "p2"))) # 略
{rlist}
で、名前ではなく 条件式を使ってリストから除外したい場合は list.exclude
。{purrr}
の場合は 上と同じく discard
もしくは keep
。
packages %>>% rlist::list.exclude("yihui" %in% authors) ## x = list 2 (1696 bytes) ## . [[1]] = list 4 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain ## . [[2]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley packages %>% purrr::discard(~ "yihui" %in% .$authors) # 略
また、{rlist}
には 欠損などの異常値を リストから除去する list.clean
という関数がある。下の例では リストの 1 レベル目にある欠損 "b" を除去している。{purrr}
では同じく discard
。
x <- list(a = 1, b = NULL, c = list(x = 1, y = NULL, z = logical(0L), w = c(NA, 1))) x ## x = list 3 (1040 bytes) ## . a = double 1= 1 ## . b = NULL 0 ## . c = list 4 ## . . x = double 1= 1 ## . . y = NULL 0 ## . . z = logical 0 ## . . w = double 2= NA 1 rlist::list.clean(x) ## x = list 2 (960 bytes) ## . a = double 1= 1 ## . c = list 4 ## . . x = double 1= 1 ## . . y = NULL 0 ## . . z = logical 0 ## . . w = double 2= NA 1 purrr::discard(x, ~ is.null(.)) # 略
rlist::list.clean
は recursive = TRUE
を指定することで 処理を再帰的に適用できる。{purrr}
ではこういった再帰的な処理は (自分で関数を書かない限り) できないと思う。
rlist::list.clean(x, recursive = TRUE) ## x = list 2 (912 bytes) ## . a = double 1= 1 ## . c = list 3 ## . . x = double 1= 1 ## . . z = logical 0 ## . . w = double 2= NA 1
要素の更新
rlist Tutorial: Updating に相当する内容。
rlist::list.map
や purrr::map
を用いたリストの選択では、結果に含める属性を明示的に指定する必要があった。
これは対象のリストに選択したい属性が多い場合はすこし面倒だ。
rlist::list.update
を使うと、もともとのリストの属性を残した上で 指定した要素を追加 / 更新できる。{purrr}
で同じことをするには map + update_list
。
packages %>>% rlist::list.update(lang = 'R', star = star + 1) ## x = list 3 (3160 bytes) ## . [[1]] = list 5 ## . . name = character 1= dplyr ## . . star = double 1= 980 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain ## . . lang = character 1= R ## . [[2]] = list 5 ## . . name = character 1= ggplot2 ## . . star = double 1= 1547 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley ## . . lang = character 1= R ## . [[3]] = list 5 ## . . name = character 1= knitr ## . . star = double 1= 1048 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ... ## . . lang = character 1= R packages %>% purrr::map(~ purrr::update_list(., lang = 'R', star = ~ .$star + 1)) # 略
一部の要素を取り除きたい場合は NULL
を指定する。これは {purrr}
も同じ。
packages %>>% rlist::list.update(maintainer = NULL, authors = NULL) ## x = list 3 (1464 bytes) ## . [[1]] = list 2 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . [[2]] = list 2 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . [[3]] = list 2 ## . . name = character 1= knitr ## . . star = integer 1= 1047 packages %>% purrr::map(~ purrr::update_list(., maintainer = NULL, authors = NULL)) # 略
ソート
rlist Tutorial: Sorting に相当する内容。
{rlist}
, {purrr}
とも 標準の sort
( 値のソート ) と order
( インデックスのソート ) それぞれに対応した関数を持つ。
x = c('a', 'c', 'd', 'b') sort(x) ## [1] "a" "b" "c" "d" order(x) ## [1] 1 4 2 3
sort
のようにソートした値を得るためには rlist::list.sort
もしくは purrr::sort_by
。
packages %>>% rlist::list.sort(star) ## x = list 3 (2632 bytes) ## . [[1]] = list 4 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain ## . [[2]] = list 4 ## . . name = character 1= knitr ## . . star = integer 1= 1047 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ... ## . [[3]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley packages %>% purrr::sort_by('star') # 略
{rlist}
では 列名を括弧でくくると逆順となる。{purrr}
には対応する記法がないが、結果を rev
で逆順にすればよい。
packages %>>% rlist::list.sort((star)) ## x = list 3 (2632 bytes) ## . [[1]] = list 4 ## . . name = character 1= ggplot2 ## . . star = integer 1= 1546 ## . . maintainer = character 1= hadley ## . . authors = character 1= hadley ## . [[2]] = list 4 ## . . name = character 1= knitr ## . . star = integer 1= 1047 ## . . maintainer = character 1= yihui ## . . authors = character 3= yihui hadley ... ## . [[3]] = list 4 ## . . name = character 1= dplyr ## . . star = integer 1= 979 ## . . maintainer = character 1= hadley ## . . authors = character 2= hadley romain packages %>% purrr::sort_by('star') %>% rev() # 略
order
のように結果をインデックスで得るためには rlist::list.order
もしくは purrr::order_by
。
rlist::list.order(packages, star) ## [1] 1 3 2 purrr::order_by(packages, 'star') ## [1] 1 3 2
まとめ
前回と同じく、{purrr}
では map (flatmap)
、keep (discard)
でだいたいのことはできる。処理を少し変えたい場合は 組み込み関数を適当に組み合わせればよいため、追加での学習コストは低いと思う。
一方、{rlist}
はパッケージの中だけで処理が完結するので、そちらにメリットを感じる方は {rlist}
を使ったほうがよさそう。
さらに続きます。
- 作者: Hadley Wickham,石田基広,市川太祐,高柳慎一,福島真太朗
- 出版社/メーカー: 共立出版
- 発売日: 2016/01/23
- メディア: 単行本
- この商品を含むブログ (25件) を見る