purrr

はじめに

環境設定

例のごとくtidyverseパッケージを読み込む

library(tidyverse)
library(repurrrsive)
library(listviewer)

purrrパッケージとは

  • リストの扱いを楽にするパッケージ
  • 特にリストの各要素に同じ処理を適用する関数map()が有用
  • Rのforループ遅いし、apply系も色々バージョンがあってめんどい
  • というわけでpurrrを使おう!!

リストってなんだっけ?

リストとは、Rにおけるベクトルの一般形
いわゆるベクトルとの違いは、

  • 要素に任意のオブジェクトを格納することができる
  • つまり、任意のモノを要素として扱うことのできるベクトル

リストの例を示す

tmp <- list(pi, LETTERS, 1:10, head(iris))
tmp
[[1]]
[1] 3.141593

[[2]]
 [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q"
[18] "R" "S" "T" "U" "V" "W" "X" "Y" "Z"

[[3]]
 [1]  1  2  3  4  5  6  7  8  9 10

[[4]]
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
  • 1つめの要素に円周率
  • 2つめの要素に文字ベクトル
  • 3つめの要素に整数ベクトル
  • 4つめの要素にデータフレーム

が入っている。最も柔軟なベクトル形式になっていることが分かる。

データフレーム

データフレームも実はリスト(の特殊な形)である。

head(mtcars) %>% kable()
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

このようなデータフレームも、中身は次のような名前付きリストになっている

head(mtcars) %>% as.list()
$mpg
[1] 21.0 21.0 22.8 21.4 18.7 18.1

$cyl
[1] 6 6 4 6 8 6

$disp
[1] 160 160 108 258 360 225

$hp
[1] 110 110  93 110 175 105

$drat
[1] 3.90 3.90 3.85 3.08 3.15 2.76

$wt
[1] 2.620 2.875 2.320 3.215 3.440 3.460

$qsec
[1] 16.46 17.02 18.61 19.44 17.02 20.22

$vs
[1] 0 0 1 1 0 1

$am
[1] 1 1 1 0 0 0

$gear
[1] 4 4 4 3 3 3

$carb
[1] 4 4 1 1 2 1

Atomic Vector

リストと対比して、普通のベクトルはAtomic Vectorと呼ばれる
Atomic Vectorとは

  • 全ての要素が同じ型になっているベクトル

のことである。以下にその例を示す

文字ベクトル

LETTERS
 [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q"
[18] "R" "S" "T" "U" "V" "W" "X" "Y" "Z"

整数ベクトル

1:10
 [1]  1  2  3  4  5  6  7  8  9 10

実数ベクトル

rnorm(10)
 [1] -2.50549752  0.28395235 -0.04268645 -0.80947962  0.09587863
 [6] -2.11074308  0.05693193 -0.88088375 -0.40102850  1.52158895

ファクターベクトル

as.factor(c("X", "Y", "X", "Y", "Y"))
[1] X Y X Y Y
Levels: X Y

論理ベクトル

c(T, T, F, T, F, F, F)
[1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE

Atomic Vectorへの処理

Rの多くの関数は、ベクトルの各要素に対して同じ処理を適用することが出来る

x <- c(1, 4, 9, 16)
x
[1]  1  4  9 16
sqrt(x)
[1] 1 2 3 4
x^2
[1]   1  16  81 256
sqrt((x - mean(x))^2)
[1] 6.5 3.5 1.5 8.5

このような処理は、通常Atomic Vectorに対してのみ有効である

map()

purrr::map()はAtomic Vectorだけでなく、リストの各要素に同じ処理を適用する
lapply()のようなものだと思えばよい

使い方

map()

map(リスト, 各要素に適用する関数)

のように書く。
ここではベクトル\((1,2,3,4)\)の平方根を求める

x <- c(1, 2, 3, 4)
map(x, sqrt)
[[1]]
[1] 1

[[2]]
[1] 1.414214

[[3]]
[1] 1.732051

[[4]]
[1] 2

帰ってくる値がリスト形式であることに注目しよう。

また、関数にはラムダ式の記述が可能である。
~ではじめて、 .x で受ける

map(x, ~sqrt(.x))
[[1]]
[1] 1

[[2]]
[1] 1.414214

[[3]]
[1] 1.732051

[[4]]
[1] 2

型の指定

帰ってくる値に、リストよりAtomic Vectorが好まれるときがあるでしょう。
そのようなときは次のように型を指定することが出来る

map_dbl(x, sqrt)
[1] 1.000000 1.414214 1.732051 2.000000
map_lgl(x, ~.x > 2.5)
[1] FALSE FALSE  TRUE  TRUE

つまりmap_型()というようにする。

複数変数

複数の変数を取りたい時があるでしょう。
もちろん型の指定もできる

map2()

2変数ならばpurrr::map2()を使う
mapの中に関数を直接書いてしまう場合はラムダ式が分かり易い

  • mtcarsデータをシリンダー数ごとに分ける
  • 分けてリストに入れる
  • それぞれに単回帰モデルを作成(map使う)
  • モデルを当てはめる(モデルとデータの2変数必要. map2使う)
by_cyl <- mtcars %>% split(.$cyl)
mods <- by_cyl %>% map(~lm(mpg ~ wt, data = .x))
map2(mods, by_cyl, predict)
$`4`
    Datsun 710      Merc 240D       Merc 230       Fiat 128    Honda Civic 
      26.47010       21.55719       21.78307       27.14774       30.45125 
Toyota Corolla  Toyota Corona      Fiat X1-9  Porsche 914-2   Lotus Europa 
      29.20890       25.65128       28.64420       27.48656       31.02725 
    Volvo 142E 
      23.87247 

$`6`
     Mazda RX4  Mazda RX4 Wag Hornet 4 Drive        Valiant       Merc 280 
      21.12497       20.41604       19.47080       18.78968       18.84528 
     Merc 280C   Ferrari Dino 
      18.84528       20.70795 

$`8`
  Hornet Sportabout          Duster 360          Merc 450SE 
           16.32604            16.04103            14.94481 
         Merc 450SL         Merc 450SLC  Cadillac Fleetwood 
           15.69024            15.58061            12.35773 
Lincoln Continental   Chrysler Imperial    Dodge Challenger 
           11.97625            12.14945            16.15065 
        AMC Javelin          Camaro Z28    Pontiac Firebird 
           16.33700            15.44907            15.43811 
     Ford Pantera L       Maserati Bora 
           16.91800            16.04103 

pmap

3変数以上になるときはpurrr::pmap()を使う
pmap(リスト, 関数)のようにする
3変数以上をまとめて一つのリストにする必要がある

x <- list(1, 10, 100)
y <- list(1, 2, 3)
z <- list(0.1, 0.4, 0.9)

l <- list(x, y, z)
f <- function(a, b, c) a + b - c

pmap(l, f)
[[1]]
[1] 1.9

[[2]]
[1] 11.6

[[3]]
[1] 102.1

実践的な使い方

なんとなくmapを使ってきて、こんな感じで使うのがイイ!というものをまとめてみました

nest_iris <- iris %>% group_by(Species) %>% nest()

nest_iris
# A tibble: 3 x 2
  Species    data             
  <fct>      <list>           
1 setosa     <tibble [50 x 4]>
2 versicolor <tibble [50 x 4]>
3 virginica  <tibble [50 x 4]>
m_iris <- nest_iris %>% mutate(model = map(data, ~lm(Sepal.Length ~ ., data = .x)))

m_iris
# A tibble: 3 x 3
  Species    data              model   
  <fct>      <list>            <list>  
1 setosa     <tibble [50 x 4]> <S3: lm>
2 versicolor <tibble [50 x 4]> <S3: lm>
3 virginica  <tibble [50 x 4]> <S3: lm>
m_iris$model[[1]]$coefficients[2]
Sepal.Width 
   0.654835 
m_iris %>% transmute(Species, intercept = map_dbl(model, ~.x$coefficients[1]), 
    beta_Sepal.Width = map_dbl(model, ~.x$coefficients[2]))
# A tibble: 3 x 3
  Species    intercept beta_Sepal.Width
  <fct>          <dbl>            <dbl>
1 setosa         2.35             0.655
2 versicolor     1.90             0.387
3 virginica      0.700            0.330

Yutaka Kuroki

2018年3月5日