« 覚え書き:BPTO(Brand Price Trade-off)とはどんな手法か (途中で投げ出しましたけど) | メイン | 読了:Wright & Ziegler (2017) ランダム・フォレストのRパッケージranger »
2018年5月 1日 (火)
たまにSNSとかを見るたびに、なんだか知らんがすごそうな若い人たちが、なんだか知らんがすごそうなことを語っていて(データサイエンティストっていうんですか?すごいですねえ)、彼我の違いにクラクラしてしまう。
私の仕事のかなりの部分をデータ解析が占めるんだけど、データ解析というものは、私の知る限り実に地味なものでして、9割がたは前処理でございます。あんまし憧れるようなものではないと思うんだけど、きっと世の中にはそうでない世界もあるのだろう。
Rには前処理用の便利機能を提供するパッケージが結構あるが、私はそういうのをあんまり使っていない。自力で書いちゃったほうが早いし、なんとなく安心できるからだ。
その一方で、そういう機能を活用した方が効率的なのかな、と後ろめたい気分になることもある。でもああいうの、ほんとに忙しいときには、いちいち調べるのも面倒なんですよね。
という事情により、ちょっと調べておくことにした。
caretパッケージは機械学習系の予測モデル構築で定番のパッケージだが、前処理用の機能もいろいろ用意している。パッケージ解説の3章 Pre-Processing で紹介されている前処理用の関数についてメモする。
(あとで気づいたのだが、逐語訳に近いものを作っておられる方がいた。ありがたいことであります)
- preProcess(): いろんな変数変換や変数削除に使う。後述。
- dummyVars(): カテゴリカル変数をダミー変数にする。文字通り各レベルをダミー変数にしてくれるだけで、lm()用に切片項をつけてくれたりはしないことに注意。
- findLinearCombos(): 行列をQR分解して、線形従属性がある変数群を特定する。
- classDist(): テストデータの各ケースについて、「学習データの各クラスの重心からの距離」という新しい変数をつくることができる。真の決定境界が実は線形である時に便利だ、と書いてある(「お、おう...」という感じだ)。これもpreProcess()と同様にオブジェクトを返し、predict()メソッドではじめて変数がつくられる。
ほかにnearZeroVar(), findCorrelation(), spatialSign()が紹介されているが、preProcess()でも実行できるようなので省略する。
。。。というわけなのだが、preProcess()の機能がとても多くて面食らう。
ここからはpreProcess()について、マニュアルを参照してメモしておく。
preProcess()は変数変換や削除そのものを行う関数ではなく。変換のためのパラメータを推定したり、どの変数を削除べきかを探してくれたりするだけ。実際の処理はこの関数が戻すオブジェクトとデータを引数にとったpredict()で行う。
基本的な使用方法は:
preProcess(x, method, ...)
xは予測子がはいっている行列かデータフレーム。numericでない変数は単に無視される。
methodは以下の文字列のうちひとつ、ないし複数個のベクトルをとる。
...でとれる引数(以下では便宜的に「追加引数」と呼ぶ)には3種類ある。
- methodを問わない引数。na.remove, verboseがある。
- methodによって異なる引数。以下で全部メモする。
- どちらでもない引数。methodとして"ica"を指定した場合、fastICA::fastICA()にパスされる。試してみたところ、methodに"ica"が入っていない場合は無言で無視されるようだ。怖い。
さて、methodがとる文字列は以下の通り。複数個指定した場合の処理順序も決まっているので、その順にメモする。
- "zv": 分散がゼロの変数を削る。
- "nzv": 分散がゼロに近い変数を削る。caret::nearZeroVar()をコールするのであろう。追加引数としてfreqCut, uniqueCutをとる。
- "corr": caret::findCorrelation()をコールし、相関の高い変数を削る。追加引数としてcutoffをとる。
- 以下の変換。うーん、目的変数じゃなくて予測子をBox-Cox変換するというのもなんだか変な気分だが、そういうこともあるんでしょうね。
- "BoxCox": ご存じBox-Cox変換 $(x^\lambda-1)/\lambda$。xに含まれている各変数について$\lambda$を決めてくれる。追加引数としてfudge(tolerance値), outcome(numericないしfactorのベクトル。どう使われるんだろう、よくわからない), numUnique(xのユニークな値の数がこの値より小さかったら変換しない)がある。内部でcaret::BoxCoxTrans()を呼ぶようだが、結局それはMASS::boxcox()のラッパーらしい。
- "YeoJohnson": Yeo-Johnson変換という、Box-Cox変換みたいなものがあって、負の値もゼロもとれるらしい。試してみたら$\lambda$なるものを決めてくれた。webで拾ったPDFによれば、$x$が0以上のときは$((x+1)^\lambda-1)/\lambda$つまり$x+1$のBox-Cox変換で、$x$が負の時は$-[(-x+1)^{2-\lambda}-1]/(2-\lambda)$となる由。いったいなにがなんだか。上記PDFにも「解釈は困難」と書いてあって、笑ってしまった。
- "expoTrans": exponential transformation. caretのマニュアルでreferされているManly(1976)の最初のページによれば$(\exp(\gamma x)-1)/\gamma$だそうである。これもBox-Cox変換みたいなもので、負の値がとれる。濱崎ら(2000)は「指数型べき変換」と呼んでいる。$\gamma$を決めてくれるのかなあ? 試してみたけどよく分からなかった。内部でcaret::expoTrans()を呼ぶらしいのだが、マニュアルに説明がない。
- "center": 平均を引く。
- "scale": SDで割る。
- "range": 指定した範囲に収める。範囲は追加引数rangeBounds(長さ2のnumericベクトル)で指定する。
- 以下の欠損値補完。
- "knnImpute": K最近隣法による欠損値補完。追加引数としてk, knnSummary(近隣の値を平均するための関数。デフォルトではmean)がある。
- "bag-Impute": 他の変数を全部使ったbagged treeモデルによる欠損値補完。
- "medianImpute": 単に中央値で欠損を埋める模様。
- "pca": PCAで新しい変数をつくる。追加引数としてthresh(分散の累積割合のカットオフ),pcaComp(主成分の数。threshをoverrideする)がある。
- "ica": ICAで新しい変数をつくる。fastICA::fastICA()をコールする。追加変数はfastICA()にパスされる。
- "spatialSign": caret::spatialSign()をコールする。spatial sign変換というのをやるらしいのだが、意味がよくわからん、今度調べよう。
このほかに、マニュアルでは実行順序がはっきりしないが、次のmethodがある(きっと最初に実行されるんだろうな)。
- "conditionalX": 追加引数としてoutcomeをとる。ここでoutcomeはfactorベクトルでないといけない(そうでないときは無視される)。「outcomeのどこかのクラスにおいて、値が1種類しかない予測子」を削る。caret::checkConditionalX()をコールする模様。
。。。ううむ。
自分の仕事にあてはめて考えると、dummyVars()には代替がいっぱいある。その場になってみないとわからないけど、私はdummiesパッケージを使うか、model.matrix()で計画行列を作ることを考えると思う。classDist()はちょっと使い道が思いつかない。preProcess()の機能のうち、center, scale, rangeは自前で書くだろうし、変数の単変量分布の観察にかなり時間をかけることが多いので、zv, nzvは手作業でやる。Box-Cox系の変換は、目的変数ならともかく、予測子のほうに掛けたくなったという記憶が無い。欠損値補完や変数直交化をやるとしたらかなり気合いを入れるので、caretではやらないと思う。
こうしてみると、使うことがありそうなのは、ひとつはfindLinearCombos()。こういうの、以前SASで苦労して自作したことがある。もうひとつは、意外にもpreProcess()のconditionalXだ。確かにね、こういうことが問題になるケース、ありそうですね。naive bayesのCPTにゼロを出したくない、とか。
... いやいや、そういう問題じゃないか。あれもこれも全部統一的な枠組みでできます、ってところがポイントなんでしょうね。業務改革への道は遠いぞ。
雑記:データ解析 - 覚え書き:caretパッケージが提供する前処理用の関数たち