purrr

purrr速習

purrr(読み方は、「ぷりゃー」、「ぷるるる」等さまざま)パッケージは、Rの関数型プログラミングを支援するパッケージです。関数型プログラミングでは、関数を引数として渡すことで、複数の関数を組み合わせてプログラムを構築します。

purrrパッケージの利点の一つは、関数を繰り返し実行するコードを簡潔に書き換え可能な点です。例えば、以下のコードは、map()関数を使って、mtcarsデータセットの各列の平均値を計算しています。

mtcars |> 
  map(mean)
$mpg
[1] 20.09062

$cyl
[1] 6.1875

$disp
[1] 230.7219

$hp
[1] 146.6875

$drat
[1] 3.596563

$wt
[1] 3.21725

$qsec
[1] 17.84875

$vs
[1] 0.4375

$am
[1] 0.40625

$gear
[1] 3.6875

$carb
[1] 2.8125

purrrを使わずに同様の処理を実行することももちろん可能です。しかしpurrrには、以下の表に示すようにたくさんの関数が用意されており、目的に応じて関数を使い分けることで、コードの保守性を高めることに貢献します。

与える引数の数 返り値がリスト 返り値がベクトル 返り値が入力と同じ型 返り値なし
一つ map() map_lgl(), map_chr(), map_dbl(), map_int(), map_vec() modify() walk()
二つ map2() map2_lgl(), map2_chr(), map2_dbl(), map2_int(), map2_vec() modify2() walk2()
一つ + インデックス imap() imap_lgl(), imap_chr(), imap_dbl(), imap_int() imodify() iwalk()
任意 pmap() pmap_lgl(), pmap_chr(), pmap_dbl(), pmap_int(), pmap_vec() - pwalk()

データフレーム中でmap()を使う

map(c(1, 3, 5), \(x) x + 1)
[[1]]
[1] 2

[[2]]
[1] 4

[[3]]
[1] 6
# map()関数の返り値は入力のデータにかかわらずリスト
tibble(
  x = c(1, 3, 5)) |>
  map(\(x) x + 1)
$x
[1] 2 4 6
# modify()は入力の形式を出力に反映する。
# データフレームを与えたら出力はデータフレームのまま
tibble(
    x = c(1, 3, 5)) |>
    modify(\(x) x + 1)
x
2
4
6

1つ以上の変数を引数に与える関数を適用する場合、pmap()関数を使うと便利です。

tibble(
    x = c("a", "b", "c"),
    y = c("D", "E", "F")) |> 
    map2(\(x, y) paste0(x, y))
Error in `map2()`:
! `.y` must be a vector, not a function.
tibble(
  x = c("a", "b", "c"),
  y = c("D", "E", "F")) |> 
  pmap(\(x, y) paste0(x, y))
[[1]]
[1] "aD"

[[2]]
[1] "bE"

[[3]]
[1] "cF"

dplyrパッケージのmutate()関数と組みあわせることで、ベクトル処理に対応していない関数であってもデータフレームの値に格納できます。

tibble(
  x = c("a", "b", "c"),
  y = c("D", "E", "F")) |> 
  mutate(z = paste0(x, y))
x y z
a D aD
b E bE
c F cF
tibble(
  x = c("a", "b", "c"),
  y = c("D", "E", "F")) %>%
  mutate(z = pmap_chr(., \(x, y) paste0(x, y)))
x y z
a D aD
b E bE
c F cF
tibble(
  x = c("a", "b", "c"),
  y = c("D", "E", "F")) %>%
  mutate(z = pmap(., \(x, y) paste0(x, y))) |> 
  unnest(cols = z)
x y z
a D aD
b E bE
c F cF
library(jpmesh)
tibble(
  mesh_code = c("6840", "624335", "47293525")) %>% 
  mutate(mesh_code = pmap_vec(., \(mesh_code) as_meshcode(mesh_code))) %>%
  mutate(mesh_size = pmap_vec(., \(mesh_code) mesh_size(mesh_code)))
mesh_code mesh_size
6840 80 [km]
624335 10 [km]
47293525 1 [km]

ユーティリティ

purrr 1.0.0からmap()関数の実行時に、処理状況を示すプログレスバーが表示されるようになっています。実行時間の長い処理を適用する際、全体のうちどの程度進んでいるのかがわかると安心です。これは.progress引数で制御します。

36:39 |> 
  map(jpmesh::administration_mesh, 
             to_mesh_size = 10, 
             .progress = TRUE)

処理に失敗する要素の位置を特定できるようになっています。 これにより問題の検出と対応が素早く行えるようになります。

list(1, 2, "c") |> 
  map(\(x) x + 1)
Error in `map()`:
ℹ In index: 3.
Caused by error in `x + 1`:
! non-numeric argument to binary operator

文字列、数値、論理値以外のベクトル化

seq.int(3) |> 
  map(\(x) factor(letters[x]))
[[1]]
[1] a
Levels: a

[[2]]
[1] b
Levels: b

[[3]]
[1] c
Levels: c
seq.int(3) |> 
  map_vec(\(x) factor(letters[x]))
[1] a b c
Levels: a b c
x <- list(a = 1, b = 2, c = 3, D = 4, E = 5)

x |> 
  keep_at(c("a", "b", "c")) |> 
  str()
List of 3
 $ a: num 1
 $ b: num 2
 $ c: num 3
x |> 
  keep_at(function(x) x == tolower(x)) |> 
  str()
List of 3
 $ a: num 1
 $ b: num 2
 $ c: num 3
x |> 
  discard_at(c("a", "b", "c")) |> 
  str()
List of 2
 $ D: num 4
 $ E: num 5