logo

RでROC曲線を描く方法 📂機械学習

RでROC曲線を描く方法

定義

誤差行列のFalse Positive RateとTrue Positive Rateをそれぞれの軸にして描かれたグラフをROC曲線Receiver Operating Characteristic Curveという。

説明

ROC曲線はモデルのパフォーマンスを一目で見せるだけでなく、最適なカットオフを見つけたり、モデル間の比較にも使われるなど、とても便利だ。例を通して、RでROC曲線を描いて、その意味を理解しよう。主なパッケージとしてROCRが使われる。

実践

Carパッケージの内蔵データChileデータをロードしてみよう。

20190128\_132747.png

Chileは、1988年にチリの独裁者アウグスト・ピノチェットが自身の政権延長のための投票に関する調査データで、region(地域)、population(応答者コミュニティの人口)、sex(性別)、age(年齢)、education(教育レベル)、income(収入)、statusquo(現状に対する支持率)によるvote(投票傾向)がわかる。

20190128\_133740.png 投票傾向はA(棄権)、U(保留)、N(反対)、Y(賛成)の4つのクラスがあり、ロジスティック回帰分析を使うため、Y以外はすべてNに変更された。この投稿では、Yを「賛成」、Nを「反対」とする。

20190128\_152957.png モデルのパフォーマンスを確認するため、トレーニングデータとテストデータに分けられた。

20190128\_153114.png トレーニングデータから得られたロジスティック回帰モデルは上記の通り。今度はこのモデルにテストデータを入れて、有権者が「賛成」する確率を計算してみよう。predict()関数にnewdata=testオプションを与えると、モデルにテストデータを入れて自動的に計算してくれる。この時、type=“response"を入れないと、求める「確率」の形で返してくれないことに注意する必要がある。

out0<-glm(vote~.,family=binomial(),data=train); summary(out0)
p <- predict(out0, newdata=test, type="response"); p

出力結果は次の通り。

20190128\_152826.png 問題は、この確率をどのように解釈し、使用するかだ。実際に求めたかったのは、確率ではなく、ある人が「賛成」したかどうかを当てることだ。だから、ある確率以上の場合は「賛成」と予測し、それ以下の場合は「反対」と予測したい。この「ある確率」をカットオフcutoff あるいは閾値thresholdと言い、通常はカットオフという表現をよく使う。

最適カットオフ

このカットオフをどうするかによって、正分類性は変わってくる。極端に例えば、0.99以下を全部「反対」と予測しても、実際に「反対」した人々が当たるので、ある程度の正分類性は出る。0.01以上を全部「賛成」と予測しても同様に当たる人はいる。例えば、1年365日のうち120日間雨が降る国があるとすれば、1年中次の日の天気を「晴れ」と予測しても、正答率はおおよそ$67\%$になる。

私たちはこれより良い分析と予測が必要なので、良いカットオフを見つけたい。とりあえず単純に考えて、0.5を基準にして高ければ「賛成」、低ければ「反対」と予測すればいいように思えるが、実際にはそうではない場合が多い。(また、常に正分類率だけが基準となるわけではない。状況によってはもっと重要な尺度がある。)そのため、良いカットオフを見つけるために行うことは、計算した確率pの全ての値をカットオフとして使ってみることだ。0.89572040で誤差行列を作り、False Positive RateとTrue Positive Rateを計算してみて、0.81810785でまた計算してみて、0.70215561でまた計算してみて、…これを繰り返しながら、横軸にFalse Positive Rate、縦軸にTrue Positive Rateを表示したグラフがまさにROCカーブだ。

言葉では難しいが、コード上では簡単なので心配しなくていい。誤差行列を作って必要な数値を計算するには、以下のようなコードを実行しなければならない。確率とは違い、実際に出力結果を見る必要はないので、各関数がどのような役割を果たすかだけをざっくりと説明する。prediction()関数は、計算した確率pと実際のテストデータのtest$vote를 비교해서 분류율을 계산해준다. performance() 함수는 위에서 계산한 오차행렬의 수치 pr에서 필요한 데이터를 뽑아 plot() 함수에 넣으면 ROC 곡선을 그릴 수 있도록 하는 데이터를 반환해준다.

pr <- prediction(p, test$vote)
prf <- performance(pr, measure = "tpr", x.measure = "fpr")
win.graph(); plot(prf, main='ROC of Test Data')

위의 코드를 실행하면 다음과 같이 ROC 커브를 그려준다.

ROC.png

위 곡선은 모든 컷오프에 대해서 FPR과 TPR을 계산하고, 그것을 각각 $x$ 축의 좌표, $y$ 축의 좌표로 갖는 곡선이다. 곡선은 가능한 한 그 아래의 면적이 넓은 것이 좋다. 궤적으로 보자면 곡선과 왼쪽 위의 점 $(0,1)$ 이 가깝게 붙는 식으로 나오는 게 좋다.막상 그 컷오프는 그림에서 표현되지 않는데, 자료구조를 뜯어보면 $alpha.values 를 통해 참조할 수 있음을 알 수 있다.

20190129\_101311.png str() 함수로 뜯어보면 컷오프가 0.939 일 때 TPR이 0, FPR이 0.00556 임을 알 수 있다. FPR이 0.00556 이라는 것은 ‘반대’를 잘못 예측한 비율이 $0.5%$ 밖에 안 된다는 뜻이다. 여기까진 좋아보이지만, TPR이 0이므로 ‘찬성’을 제대로 예측한 케이스가 단 한 건도 없다는 말이 된다. 직관적으로 생각해보면 ‘찬성’할 확률이 0.939 이하인 사람을 모두 ‘반대’로 예측했기 때문에 ‘찬성’을 ‘찬성’으로 예측하는 허들이 너무 높았던 것이다. 이러니 ‘반대’는 다 맞춰도 ‘찬성’은 맞출 수가 없는 것이다.

그림이 각져서 나와요

자기가 그린 ROC 곡선은 너무 네모나서 이상하다는 사람이 있다. 그건 단순히 테스트 데이터가 적기 때문에 일어나는 일이다. 데이터가 적은 것 자체는 걱정할 수 있지만 각진 모양으로 나오는건 전혀 걱정하지 않아도 좋다. 예를 들어 이 예제에서도 테스트 데이터의 크기를 20개로 줄이면 다음과 같은 모양이 나온다.

smallroc.png

코드

아래는 예제 코드 전체다.

install.packages("car")
install.packages("ROCR")
 
library(car)
library(ROCR)
 
set.seed(150421)
 
?Chile
str(Chile)
nrow(Chile)
head(Chile); tail(Chile)
 
DATA<-na.omit(Chile)
DATA$vote[DATA
          $vote!='Y']<-'N'
DATA$vote<-factor(DATA$vote)
head(DATA); tail(DATA)
 
DATANUM<-nrow(DATA)
train<-sample(1:DATANUM)<(DATANUM*0.8)
test<-DATA[!train,]; head(test)
train<-DATA[train,]; head(train)
 
out0<-glm(vote~.,family=binomial(),data=train); summary(out0)
p <- predict(out0, newdata=test, type="response"); p
 
pr <- prediction(p, test$vote)
prf <- performance(pr, measure = "tpr", x.measure = "fpr")
win.graph(); plot(prf, main='ROC of Test Data')
str(prf)
 
smalltest<-test[sample(nrow(test),20),]
p <- predict(out0, newdata=smalltest, type="response"); p
pr <- prediction(p, smalltest$vote)
prf <- performance(pr, measure = "tpr", x.measure = "fpr")
win.graph(); plot(prf, main='ROC of Small Test Data')