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