17章 purrrでイテレーション

20191110:{purrr}の`map()`についての表記を無名関数を用いた表記に変更しました。

17.0 ライブラリの読み込み

library("tidyverse")
library("stringr")
library("microbenchmark")

17.1 はじめに

練習問題はありません

17.2 for-loop

練習問題1 for-loopを書く

  • mtcarsの各列の平均を計算。

output <- vector("double", ncol(mtcars))
names(output) <- names(mtcars)
for (i in names(mtcars)) {
  output[[i]] <- mean(mtcars[[i]])
}

output
       mpg        cyl       disp         hp       drat         wt       qsec         vs 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750   0.437500 
        am       gear       carb 
  0.406250   3.687500   2.812500 
  
# apply(mtcars, 2, mean)
# map_dbl(mtcars, mean)
# mtcars %>%
#   map_dbl(.x = ., .f = function(data){mean(data)}) 
  • nycflights13::flightsの各列の型を調べる。

  • irisの各列のユニークな値の数を計算。

  • μ = -10、0、10、100のそれぞれに対して10個の乱数を生成。

matrix()をうまく付けばfor-loopを書かなくても同じようなことはできます。

練習問題2 ベクトルを扱う既存の関数を利用して、以下の各例のforループを排除しなさい。

str_c()でもpaste0()のいずれの関数もベクトルを扱います。

仮にこれがデータフレームで各行に文字が保持されているような場合でも、summarise()を使うことで、文字列の結合が可能です。

書籍ではsdですがここではわかりやすくするためにsd2とします。sd2は、for-loop内で偏差平方和を計算しているので、これをベクトルの長さ-1で割ったものが分散。平方根をとれば標準偏差です。なので、sd()を使えば済みます。

これは累積和を計算しています。cumsum()で計算可能です。

練習問題3 関数の作成とforループのスキルを組み合わせなさい。

  • "Alice the camel"に歌詞を付けたforループを書き、prints()しなさい。

これが"Alice the camel"の歌詞です。

five, four, three, two, one, no, Now Alice is a horseの部分が変動している部分なので、ここを変化させます。

  • “ten in the bed” を関数に変換し、何人でも対応できるように一般化しなさい。

これが“ten in the bed” の歌詞です。

変動する部分は、ベッドに入る人間の数なので、そこをforループで回します。

  • “99 bottles of beer on the wall”を歌を関数にしなさい。表面に液体が入っている容器を一般化しなさい。

“99 bottles of beer on the wall”の歌詞はこのとおりです。

99本のビールから始まり、0本になったら買い出しに行く歌です。

変動するのはビールの本数です。

練習問題4 アウトプットを事前に割り当てず、代わりに各ステップでベクトルの長さを増やしながらループさせるのが一般的ですが、これはパフォーマンスにどのように影響するのか?

{microbenchmark}パッケージを使って、最初から入れ物を用意(事前に割り当てる)したf2()と毎回、入れ物を拡大してf1()で100回繰り返し、時間を計測してみます。だいたい258倍くらいはやくなりました。

17.3 重要なアトミックベクトル

練習問題1 files <- dir("data/", pattern = "\\.csv$", full.names = TRUE)のパスを使い、read_csv()で連続的にデータを読み込みなさい。また、単一のデータフレームにしなさい。

ここでは/Users/**/Desktop/sample/のディレクトリにdata*.csvというデータがあるとします。

練習問題2 for (nm in names(x))xに名前がない場合、どうなるのか。

ベクトルの名前がない場合は、イタレーション回数がNULLなので、ループ内でコードが実行されません。

NULLの長さは0です。

練習問題3 データフレーム内の各数値列の平均とその名前を表示する関数を作成しなさい。たとえば、show_mean(iris)は次のように出力される。変数名の長さが異なっていても、数字がうまく並ぶようにするためにはどうするか。

str_c()str_pad()を上手く組み合わせてレイアウトを作ります。

練習問題4 このコードはどのように機能するのか。

このコードはdispam列を変更します。

  • disp を0.0163871倍

  • am をファクタ変数に置き換え

コードは関数の名前付きリストをループすることによって機能します。transのリストにある名前付き関数をmtcarsの同じ名前で呼び出し、その列の値を置き換えます。

17.4 for-loopと汎関数

練習問題1 apply()のドキュメントを読んで、2つのfor-loopを一般化しなさい。

apply()関数はapply(X, MARGIN, FUN, ...)のように使用されます。ここXは行列または配列、FUNは適用する関数、および...追加の引数として渡されます。MARGINは関数を適用する方向を示します。

これは下記とやっていることは同じです。これがapply()を使えば1行でかけます。またapply()を進化させたものが、map()です。

練習問題2 col_summary()が数値列にのみ適用されるようにしなさい。

現状のcol_summary()はこの状態です。数値列にのみ使用できるようにするためには、データ型を判定し、数値だけ抽出し、それを計算する、という方向性で進める必要があります。

さきほどの方向性に乗っ取り、関数を作成します。各列をis.numeric()で判定し、TRUEの列のみwhich()で列番号を取得します。その列番号を指定し、データフレームの該当列を抽出し、計算します。

ちょっと改良してみます。{dplyr}{purrr}の関数たちを使うことで、少し短くできます。

さらに改良します。実のところもっと改良できますが、最後の練習問題がこの関数を改良することなので、そちらに改良版のcol_summary4()を記載します。

{tidyverse}の関数群を使って関数を作るときにはtidyevalという評価の枠ぐみのもとで関数を作成することが望ましいです。

17.5 Map関数

練習問題1 map()で下記を書きなさい。

map()には様々な書き方が用意されています。結果はどれも同じです。上から通常関数、ラムダ記法、無名関数を使った記法です。

  • mtcarsの各列の平均を計算。

  • nycflights13::flightsの各列の型を調べる。

  • irisの各列のユニークな値の数を計算。

  • μ = -10、0、10、100のそれぞれに対して10個の乱数を生成。

練習問題2 データフレームの各列に対して、それがfactorであるかどうかを示す単一のベクトルを作成しなさい。

map_lgl()を使えば1行で済みます。

練習問題3 リストではないベクトルに対してmap()を使うとどうなるのか?map(1:5, runif)はどうなるのか。

map()は、リストだけでなくベクトルでも機能します。リストと同様に、map()は関数をベクトルの各要素に適用します。

map()を使えば、大数の法則も簡単にシミュレーションできます。

map()という関数は、繰り返し回数と引数の値を同時に実行できる優れた関数です。反対に、回数と引数は別の値を使いたい場合は下記のように[[.]]で囲んで上げれば、実現できます。

練習問題4 map(-2:2, rnorm, n = 5)map_dbl(-2:2, rnorm, n = 5)はどう違うのか。

前者は実行可能で、後者はエラーが表示されてしまいます。これは、出力形式がリストなのか、ベクトルなのかの違いによるものです。map_dbl()で想定されるの出力は、ループ回数に対して単一のスカラです。したがって、1回のループで5個の乱数が生成されてしまったために、このような結果となります。

この厳密さは、map_dbl()が入力ベクトルと同じ長さの数値ベクトルを返すことを保証するためです。そのため、下記のような使い方であれば、入力ベクトルと同じ長さの数値ベクトルを返せるため、実行可能です。

エラーが表示されたコードで実現したいことが、-2、-1、0、1、2の平均を持つ5つの正規分布からサイズ5のサンプルを発生させたいのであれば、下記のように実現可能です。

練習問題5 map(x, function(df) lm(mpg ~ wt, data = df))の無名関数を削除するように書き直しなさい。

そもそも質問文のコードはxが定義されていないので実行できません。そこで下記のように場面を考えます。

ここから無名関数を取り除きます。ラムダ式で書いてみます。

もしくは名前付きの関数do_regressを定義して利用することも可能です。

17.6 失敗への対処

練習問題はありません

17.7 複数の引数へのマッピン

練習問題はありません

17.7 Walk

練習問題はありません

17.8 for-loopの他のパタ​​ーン

練習問題1 for-loopを使用して独自のevery()を実装します。purrr::every()と比較しなさい。

purrr::every()は、すべての要素が判定をパス、つまりTRUEとなるかを調べる関数です。

無名関数を使えば、より柔軟な判定が可能です。

よりシンプルな実装はこちらです。

練習問題2 col_summary()をデータフレーム内のすべての数値列に集計関数を適用する機能を作成しなさい。

keep()という関数は論理判定がTRUEの要素のみを残します。したがって、数値列のみをkeepすることで、集計関数を数値のみに適用できます。

練習問題3 col_summary()baseで考えるとどうなるのか。

sapply()を使うことで同じことは実装可能です。

最終更新

役に立ちましたか?