5 map 関数ってすごい
5.2 データの準備
Rの iris
7 データで解析を紹介します。
iris
は data.frame
として定義されているので、as_tibble()
を使って tibble
のクラスを追加します。
iris = iris |> as_tibble()
iris
#> # A tibble: 150 × 5
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> <dbl> <dbl> <dbl> <dbl> <fct>
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3 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 3.6 1.4 0.2 setosa
#> 6 5.4 3.9 1.7 0.4 setosa
#> 7 4.6 3.4 1.4 0.3 setosa
#> 8 5 3.4 1.5 0.2 setosa
#> 9 4.4 2.9 1.4 0.2 setosa
#> 10 4.9 3.1 1.5 0.1 setosa
#> # … with 140 more rows
5.3 説明
tidyverse
の開発によって、Rでのデータ処理はすこぶる楽になりました。
個人的には、Rでデータ処理するのはとても楽しいです。
そこで、もっともデータ処理を楽にしてくれたのは map()
関数です。
実は、数種類のmap()
があります。
そのほかにもありますが、研究室のコードでは上のものが多いです。
他によく使う関数は pmap()
と map2()
です。
pmap()
は3変数以上を関数に渡したいときに使います。
map2()
は2変数のバージョンです。
どの map()
には .x
と .f
の引数を渡す必要があります。
-
.x
は list または vector のオブジェクトです。 -
.f
は list/vector のそれぞれの要素に適応したい関数です。
例えば、つぎの list を定義します。
z = list(a = rnorm(10),
b = rnorm(10),
c = rnorm(5))
z
#> $a
#> [1] 1.4791785 -0.1438554 0.2482697 0.3668389 0.6041956
#> [6] 0.3306062 -0.2593741 -0.1650458 2.0903996 -1.4555092
#>
#> $b
#> [1] 0.26024647 -1.69152339 0.27190315 -1.10126927
#> [5] 0.84767878 2.11992986 0.64611243 0.17848958
#> [9] 0.30964498 0.05438722
#>
#> $c
#> [1] -1.2820465 -0.5203833 0.5708300 -0.7918932 1.3111160
それぞれの要素の平均値を出したいなら、次のように map()
を使います。
map(z, mean)
#> $a
#> [1] 0.3095704
#>
#> $b
#> [1] 0.18956
#>
#> $c
#> [1] -0.1424754
map()
は必ず list として結果を返します。
ベース (base) Rlapply()
と同じですね。
lapply(z, mean)
#> $a
#> [1] 0.3095704
#>
#> $b
#> [1] 0.18956
#>
#> $c
#> [1] -0.1424754
ベースRの sapply()
のようにベクトルとして返してほしいなら、map_dbl()
を使います。
map_dbl(z, mean)
#> a b c
#> 0.3095704 0.1895600 -0.1424754
ベースRの sapply()
の結果と同じです。
sapply(z, mean)
#> a b c
#> 0.3095704 0.1895600 -0.1424754
ちなみに、for-loop でもできますが、研究室では使用を禁じます。
5.4 map()
の魅力
map()
の魅力は tidyverse
のパイプラインに使えること、map()
に複雑な関数を渡せること。
結果は tibble
として返せることかな。
他にあるとおもいますが、使えるようになるとデータ処理は楽しいです。
たとえば、次のようことができます。
iris
のデータを tibble
に変換し、pivot_longer()
に渡して縦長に変えます。
pivot_longer()
には Sepal
と Petal
を含む列を cols
引数に渡すようにしています。
変換したあと、pivot_longer()
が作った name
の列は separate()
によって part
と measurement
に分けます。
iris_long = iris |>
as_tibble() |>
pivot_longer(cols = matches("Sepal|Petal")) |>
separate(name, c("part", "measurement"))
ここでは関数を定義していますが、この関数は複数の t 検定を実施し、その結果を一つの tibble
にまとめています。
t.test()
に渡すデータは filter()
関数に通しています。
filter()
は str_detect()
を使って、 Species
列から解析したいデータを抽出しています。
str_detect()
で処理する列は Species
、検索する文字列は pattern
に渡しています。
たとえば、pattern = "set|ver"
は set
または ver
を意味しています。
t 検定の結果を t12
、t13
、t23
の入れます。
こんど、それらを broom
パッケージの tidy()
に渡し、tibble
かします。
bind_rows()
を使って、縦に結合し、結合した要素の名前を comparison
にします。
runmultttest = function(df) {
#t12: setosa - versicolor
#t13: setosa - virginica
#t23: versicolor - virginica
t12 = t.test(value ~ part, data = filter(df, str_detect(string = Species, pattern = "set|ver")))
t13 = t.test(value ~ part, data = filter(df, str_detect(string = Species, pattern = "set|vir")))
t23 = t.test(value ~ part, data = filter(df, str_detect(string = Species, pattern = "ver|vir")))
bind_rows("setosa vs. versicolor" = tidy(t12),
"setosa vs. virginica" = tidy(t13),
"versicolor vs. virginica" = tidy(t23), .id = "comparison")
}
上のコードチャンクで定義した関数は iris_long
に適応しますが、
measurement
ごとに data
の要素ごとに実施されます。
iris_long = iris_long |> group_nest(measurement)
iris_long
#> # A tibble: 2 × 2
#> measurement data
#> <chr> <list<tibble[,3]>>
#> 1 Length [300 × 3]
#> 2 Width [300 × 3]
つまり、data
は map()
を通して、 runmultttest()
が適応されます。
iris_long |>
mutate(tout = map(data, runmultttest)) |>
unnest(tout)
#> # A tibble: 6 × 13
#> measurement data comparison estimate estimate1
#> <chr> <list<tibble[,3> <chr> <dbl> <dbl>
#> 1 Length [300 × 3] setosa vs… -2.61 2.86
#> 2 Length [300 × 3] setosa vs… -2.29 3.51
#> 3 Length [300 × 3] versicolo… -1.36 4.91
#> 4 Width [300 × 3] setosa vs… -2.31 0.786
#> 5 Width [300 × 3] setosa vs… -2.07 1.14
#> 6 Width [300 × 3] versicolo… -1.20 1.68
#> # … with 8 more variables: estimate2 <dbl>,
#> # statistic <dbl>, p.value <dbl>, parameter <dbl>,
#> # conf.low <dbl>, conf.high <dbl>, method <chr>,
#> # alternative <chr>
5.5 map_dbl()
の使い方
map_lgl()
, map_int()
, map_dbl()
, map_chr()
シリーズの関数が返すものは N = 1 のベクトルです。
よって、適応する関数はベクトルを返すようにくみましょう。
\(df) {...}
は無名関数と呼びます。
\(df) {...}
は function(df) {...}
の諸略です。
このとき、関数は summarise()
を通して、tibble()
を返すので、エラーが発生します。
iris_long |>
mutate(out = map_dbl(data, \(df) {
df |>
group_by(Species, part) |>
summarise(value = mean(value))
}))
#> Error in `mutate()`:
#> ! Problem while computing `out = map_dbl(...)`.
#> Caused by error in `stop_bad_type()`:
#> ! Result 1 must be a single double, not a vector of class `grouped_df/tbl_df/tbl/data.frame` and of length 3
次のコードは pull()
を使って、mean
だけ返すようにしたが、
N > 1 のベクトルなので、エラーが発生した。
iris_long |>
mutate(out = map_dbl(data, \(df) {
df |>
group_by(Species, part) |>
summarise(value = mean(value)) |> pull(mean)
}))
#> Error in `mutate()`:
#> ! Problem while computing `out = map_dbl(...)`.
#> Caused by error:
#> ! Must extract column with a single valid subscript.
#> ✖ Subscript `var` has the wrong type `function`.
#> ℹ It must be numeric or character.
Species
, measurement
, part
ごとに map_dbl()
で平均を求めたいので、
一旦 iris_long
の data
のネスティングを作り直します。
iris_long |>
unnest(data) |>
group_nest(Species, measurement, part) |>
mutate(out = map_dbl(data, \(df) {
# mean(df$value) # でもOK
df |> summarise(value = mean(value)) |> pull(value)
}))
#> # A tibble: 12 × 5
#> Species measurement part data out
#> <fct> <chr> <chr> <list<tibble[,1]>> <dbl>
#> 1 setosa Length Petal [50 × 1] 1.46
#> 2 setosa Length Sepal [50 × 1] 5.01
#> 3 setosa Width Petal [50 × 1] 0.246
#> 4 setosa Width Sepal [50 × 1] 3.43
#> 5 versicolor Length Petal [50 × 1] 4.26
#> 6 versicolor Length Sepal [50 × 1] 5.94
#> 7 versicolor Width Petal [50 × 1] 1.33
#> 8 versicolor Width Sepal [50 × 1] 2.77
#> 9 virginica Length Petal [50 × 1] 5.55
#> 10 virginica Length Sepal [50 × 1] 6.59
#> 11 virginica Width Petal [50 × 1] 2.03
#> 12 virginica Width Sepal [50 × 1] 2.97
エラーがなくなりましたが、グループごとの平均値をもとめたいなら、summarise()
のほうがいいですね。
iris_long |>
unnest(data) |>
group_by(Species, measurement, part) |>
summarise(value = mean(value))
#> # A tibble: 12 × 4
#> # Groups: Species, measurement [6]
#> Species measurement part value
#> <fct> <chr> <chr> <dbl>
#> 1 setosa Length Petal 1.46
#> 2 setosa Length Sepal 5.01
#> 3 setosa Width Petal 0.246
#> 4 setosa Width Sepal 3.43
#> 5 versicolor Length Petal 4.26
#> 6 versicolor Length Sepal 5.94
#> 7 versicolor Width Petal 1.33
#> 8 versicolor Width Sepal 2.77
#> 9 virginica Length Petal 5.55
#> 10 virginica Length Sepal 6.59
#> 11 virginica Width Petal 2.03
#> 12 virginica Width Sepal 2.97
map2()
を使えば、 2変数渡せます。
ここでは map2_dbl()
を使っています。
iris |>
mutate(LW = map2_dbl(Petal.Length, Petal.Width, \(l,w) {
(l * w)
}))
#> # A tibble: 150 × 6
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> <dbl> <dbl> <dbl> <dbl> <fct>
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3 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 3.6 1.4 0.2 setosa
#> 6 5.4 3.9 1.7 0.4 setosa
#> 7 4.6 3.4 1.4 0.3 setosa
#> 8 5 3.4 1.5 0.2 setosa
#> 9 4.4 2.9 1.4 0.2 setosa
#> 10 4.9 3.1 1.5 0.1 setosa
#> # … with 140 more rows, and 1 more variable: LW <dbl>
pmap()
の場合、渡す変数は list にまとめてから渡しましょう。
iris |>
mutate(out = pmap_dbl(list(Petal.Length, Petal.Width,
Sepal.Length, Sepal.Width), \(pl,pw, sl, sw) {
(pl * pw) / (sl * sw)
}))
#> # A tibble: 150 × 6
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> <dbl> <dbl> <dbl> <dbl> <fct>
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3 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 3.6 1.4 0.2 setosa
#> 6 5.4 3.9 1.7 0.4 setosa
#> 7 4.6 3.4 1.4 0.3 setosa
#> 8 5 3.4 1.5 0.2 setosa
#> 9 4.4 2.9 1.4 0.2 setosa
#> 10 4.9 3.1 1.5 0.1 setosa
#> # … with 140 more rows, and 1 more variable: out <dbl>
iris: アヤメ↩︎