今回はR
の処理を高速に処理するためには避けて通れないapply系の使い方を自分自身で仕組みを理解しつつまとめていきます。R
もいわゆるループ系の処理が早ければそのほうがなじみがあるのですが、このapply
系はいままで他の言語では接したことがなかったのでシックリくるまで時間がかかりました。
すべてのapply系
をまとめて記載するとどうしても膨大になり「書式の紹介+チョットしたサンプル」程度になりがちなので、ここでは一つ一つ(自分で理解しながら)まとめていきたいと思います。
そんななか最初に取り上げるにはいきなりいまいちでマイナーですが、lapply
についてまとめてみます。
統計分析などではカラム(説明変数)ごとに処理を回して有意水準を見てみるなど、分析ををまとめて処理したいケースが多々ありますが、こういった場合に効率よく処理するときにはlapply
は重宝すると思います。
apply系の使い方についてはネットを検索するといくらでも出てきますが、それらを読んでもどうも使い方が
腹に落ちないことが多く、結局やりたいことと最も近い解説があるサイトをなんとか探して”コピペで使う・・・”ことで
なんとか使っている方も多いのではないでしょうか。
そんな方が(私もそうですが)apply系の仕組みを理解することで
もっと自由に使いこなせるようになることを目指します。
今回のテーマ: lapply
最初はよくある例をいくつか。
lapplyの基本形
1−2行目を実行すると3行目以降の結果が出力されます。
a = list(c(1:5), c(6:10), c(11:15))
lapply(a, mean)
> lapply(a, mean)
[[1]]
[1] 3
[[2]]
[1] 8
[[3]]
[1] 13
list
形式で与えた3つのベクトルが含まれる変数a
のそれぞれのリスト要素ごとに
平均値を計算して、その結果をリスト形式で返してくれています。
念のための基本の確認ですが、lapplyは入力されたデータを処理して結果をリスト形式で返す関数なので、結果のリストから要素の取り出しには二重のカギ括弧[[]]
を使う必要があるため
[1]]
[1] 3
のように、帰ってくる結果も上記のような表示となっています。実際にlapply
の結果を変数res
に入れてから
1番目の要素をres[[1]]
で取り出すことができます。
res = lapply(a, mean)
res[[1]]
[1] 3
なるほど・・・ここのmean
をstd
など他の関数に変えればOKなのか!となりますが、
では、組み込み関数以外はどうすればよいのか? すなわち、独自に作った関数を計算させるにはどうしたらよいのでしょうか。
こういったチョットした応用になると途端に基本的な使い方の解説とのギャップが大きくなるのがこのapply
系かとおもいます。
独自関数を利用する方法
すこし考えてみるとR
的に考えればmean
の部分をfunction
で定義すれば良さそうではないかと思われます。
上記の例をfunction
を使って記述した例が以下の通りとなります。
lapply(a, function(x) {
mean(x)
})
先の例では、リストa
の各要素を関数mean
に順番に渡してその結果をリスト形式で格納しましたが、
この例では、リストa
の各要素を独自に設定した関数の引数x
に順番に渡してfunction(x){...}
の
計算結果を返してリストに格納する形になります。この例を実行してみると先の結果と一致するのが確認できます。
これで独自関数を使ったlapply
の基本的な使い方がわかりましたので、少し関数部分を変化させてみます。
lapply(a, function(x) {
y = 3
z = mean(x) + y
return(z)
})
[[1]]
[1] 6
[[2]]
[1] 11
[[3]]
[1] 16
上記の例ではそれぞれの平均に3を足した結果を変数z
に保存して、そのz
を戻す(return)計算となっています。
1−5行目を計算した結果が6行目以降となります。
このようにするとfunction
の内部でどのような関数計算も処理が可能となりますので必要な計算をさせて結果をreturn
で戻してあげればOKです。
データフレームを渡して計算する方法
上記の例ではリスト形式で計算するためのデータを渡していますが、実際の計算ではdataframe
形式でデータを保管していることが多いと
思いますので、そのままデータフレームを渡してカラム(列)ごとに何らかの操作を行いたいということが多いのではないでしょうか。
こういったときには、上記の事例では変数a
に当たる部分に、処理する対象のカラム名を記載すれば、カラム名が順番に関数の中に引き渡されます。
lapply(colnames(mtcars[1:3]), function(x) {
ret = mean(mtcars[,x])
return(ret)
})
上記の例では、おなじみのR
組み込みデータのmtcars
のデータをつかっています。
colnames(mtcars[1:3])
でmtcars
の1−3行目のカラム名をベクトルとして取り出しています。
これをfunction
の引数として順番に渡してmtcars[,x]
で対象の行だけが取り出せるのでその平均を計算して返しています。
この例では引き渡す値がカラム名のベクトルでありリストではありませんでした。データセット自体はデータフレームとして別途保存してあるデータを
使っているので、lapply
の中でデータセット自体を引き渡す必要ななく、単純にカラム名だけを渡せばよいのでこのような使い方が可能となります。
こうすれば関数内に好きな計算式を入れればカラムごとに計算して結果を返すプログラムが自由に書けるようになります。
複数の引数を渡す方法
関数の計算では2つ以上の引数を使う場合もありますが、その場合はlapply
の関数の後に追加の引数を記入することで使うことができます。
では実際の事例を見てみます。以下の例では関数部分をmyfunc
で外出しにしてあり、二つの引数x, y
が使われています。 lapply
で順番に引き渡す変数は一つだけなので、
それ以外の引数は関数(この場合はmyfunc)の後に追加でlapply(a, myfunc, y = 3)
のように記入していけばOKです。この例では、y
は関数myfunc
の外部から与える
形になっているので、プログラムの他の部分で作ったy
の値を引き渡すなどの計算が可能となります。
myfunc = function(x, y) {
a = list(c(1:5), c(6:10), c(11:15))
z = mean(x) + y
return(z)
}
lapply(a, myfunc, y = 3)
結果の取り出し
lapply
で順番に処理した結果はリスト形式で格納されています。これを取り出す方法はいくつかありますが
最も簡単な方法は先にも説明した[[]]
ダブルのカッコで囲う方法です。
上記の例でres = lapply(a, myfunc, y = 3)
のようにして結果をres
という変数に保存してみます。
このときres
はリスト形式となっています。
一つ目の要素を取り出すにはres[[1]]
とすればOKです。
特定の結果を取り出すだけならこれでOKですがまとめて取り出したいときは面倒ですので、その際は
リストをベクトルに変更するunlist
関数を使います。
unlist(res)
とすれば結果がベクトルで一度に得られます。
> res = lapply(a, myfunc, y = 3)
> unlist(res)
[1] 6 11 16
もう少し複雑な計算の場合
上記の例では平均値の結果”だけ”を計算しているので結果を簡単にベクトルで取得することができますが、複数の計算結果が含まれるような関数やパッケージを使う
場合について検討します。
以下の例では正規性の検定をシャピロウイルク検定で行った例です。データセットmtcars
の1−3列のデータを順番に正規検定を行って結果をres
にリスト形式で格納しています。
res = lapply(names(mtcars[1:3]), function(x) {
shapiro.test(mtcars[,x])
})
この結果の1番目を出力すると以下の通りの計算結果が得られています。
> res[[1]]
Shapiro-Wilk normality test
data: mtcars[, x]
W = 0.94756, p-value = 0.1229
計算結果には統計量やp値などいろいろ含まれているようです。以下に内容を展開してみました。
これをunlist
で展開してもデータがベクトル形式で羅列されるので
非常に加工しにくいものになってしまいます。
> res = lapply(names(mtcars[1:3]), function(x) {
+ shapiro.test(mtcars[,x])
+ })
>
> unlist(res)
statistic.W p.value
"0.947564726479274" "0.122881358539443"
method data.name
"Shapiro-Wilk normality test" "mtcars[, x]"
statistic.W p.value
"0.753310022842721" "6.05833813310341e-06"
method data.name
"Shapiro-Wilk normality test" "mtcars[, x]"
statistic.W p.value
"0.920012680133146" "0.0208065696108598"
method data.name
"Shapiro-Wilk normality test" "mtcars[, x]"
ではこのリストの中からp値だけを取り出してみたいと思います。
このような場合には、以下のような形で$
を使って要素をとりだすことが基本になります。
res[[1]]$p.value
> res[[1]]$p.value
[1] 0.1228814
目的のリストの一番目のp値だけ取り出すことができました。
では、このp値をすべてのリスト項目からまとめて取り出したいときにはどうしたら良いでしょうか。
具体的な方法は以下の通りです。
res = lapply(names(mtcars[1:3]), function(x) {
shapiro.test(mtcars[,x])
})
res.pval <- unlist(lapply(res, `[[`, "p.value"))
この形で処理すれば、変数res.pval
にp値がベクトル形式で取得できます。
> unlist(lapply(res, `[[`, "p.value"))
[1] 1.228814e-01 6.058338e-06 2.080657e-02
※ ちなみにres.pval <- sapply(res,
[[, "p.value")
の形で実行しても同じ結果が取得できますが、こちらはまたsapply
の時に紹介します。
※※ さらに、res.pval <- do.call(rbind, lapply(res,
[[, "p.value"))
とすると、結果がデータフレーム形式で取得できます。
上記の例でポイントになるのはlapply(res,
[[, "p.value")
が何をやっているのか?といったあたりかと思います。が、実はこのあたりまだよくわかってません。
この形式で書けば結果が取り出せるのはわかるのですが、このカギ括弧が何をやっているのか?その理由が明快に書かれている場所(マニュアルなど)がまだみつけられていないので、現段階ではおまじない的に利用しています。
なお、このコードでp.value
をstatistic
にすれば統計量が得られます。
今回は、lapply
の使い方を自分自身で再確認しながらまとめてみました。
まだまだ奥が深そうで自由に使いこなせるようになるにはしばらく時間がかかりそうですが、今後も地道に理解を深めていきたいとおもいます。