Rによるデータ分析

Rによる決定木

決定木 は、データマイニングの強力な手法です。

なお、決定木には、 機械学習 の手法のひとつとして 予測 に使う使い方もありますが、 過学習 になったり、粗過ぎたりで、ちょうど良い感じにしにくいので、この使い方をしないです。

Rのライブラリーの種類

Rで決定木を使う場合、ライブラリーがいろいろあり、一長一短です。
Decision Tree

実務の中で使うため、 短所の部分については、このサイトのサンプルコードでは、改善させるようにしています。
Decision Tree

rpartの完成度の高さについて

rpartはよくできていて、Yの種類を自動的に判断して、質的データなら分類木を実行し、量的データなら回帰木を実行します。 また、Xは質的データと量的データが混ざっていても使えます。

CHAIDやC5.0で、 特に何もケアせずに、rpartのようにして実行すると、「データが論理型や文字列型」の場合にエラーが出てしまいます。 CHAIDやC5.0では、このエラーを回避するための前処理があるため、rpartに比べるとコードが長くなります。

入門的な決定木

分類木と回帰木 と入門的な使い方の例です。

分類木の場合

Rの使用例は下記になります。 (下記は、コピーペーストで、そのまま使えます。 この例では、Cドライブの「Rtest」というフォルダに、 「Data.csv」という名前でデータが入っている事を想定しています。 このコードの前に、ライブラリ「partykit」のインストールが必要です。

データは、Yと20個のXがあるとします。90行分あります。
Decision Tree

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(partykit)
# ライブラリを読み込み
library(rpart)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T)
# データを読み込み
treeModel <- rpart(Y ~ ., data = Data)
# rpartを実行
plot(as.party(treeModel))
# グラフにする。
Decision Tree

下のようなデータについて、「量的データの列は、質的データにまとめて変換」という処理は、下記でできます。
Decision Tree
setwd("C:/Rtest") # 作業用ディレクトリを変更
Data <- read.csv("Data.csv", header=T)
# データを読み込み
for (i in 1:ncol(Data)) {
# ループの始まり。データの列数を数えて同じ回数繰り返す
if (class(Data[,i]) == "numeric") {
# 条件分岐の始まり
Data[,i] <- droplevels(cut(Data[,i], breaks = 5,include.lowest = TRUE))
# 5分割する場合。量的データは、質的データに変換する。
}
# if文の処理の終わり
}
# ループの終わり
Decision Tree

回帰木の場合

データは、Yと4つのXがあるとします。106行分あります。
Decision Tree

コードは、上記とまったく同じで良いです。 Yが量的データなら、回帰木として処理されます。
Decision Tree

N進木

N進木 の例です。

質的データだけの場合(CHAID)

このコードの前に、ライブラリ「CHAID」のインストールが必要です。

データは、Yと3つのXがあるとします。100行分あります。
Decision Tree

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(CHAID)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T, stringsAsFactors=TRUE)
# データを読み込み
treeModel <- chaid(Y ~ ., data = Data)
# CHAIDを実行
plot(treeModel)
# グラフにする。
Decision Tree

XやYに量的データが混ざっている場合(CHAID)

データは、Yと3つのXがあるとします。100行分あります。X1は量的データになっています。
Decision Tree

なお、このコードは、Yが量的データでも使えます。

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(CHAID)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T, stringsAsFactors=TRUE)
# データを読み込み
for (i in 1:ncol(Data)) {
# ループの始まり。データの列数を数えて同じ回数繰り返す
if (class(Data[,i]) == "numeric") {
# 条件分岐の始まり
Data[,i] <- droplevels(cut(Data[,i], breaks = 5,include.lowest = TRUE))
# 5分割する場合。量的データは、質的データに変換する。
}
# if文の処理の終わり
}
# ループの終わり
treeModel <- chaid(Y ~ ., data = Data)
# CHAIDを実行
plot(treeModel)
# グラフにする。
Decision Tree

Wekaにある「wether.numeric」というサンプルデータをcsv形式にして、Yの列名を変えたものでは枝分かれしませんでした。 原因は、このデータが14行しかないスモールデータのためと思います。 パラメータを変えれば様子が変わるかもしれませんが、そこまでは試していません。
Decision Tree

XやYに量的データが混ざっている場合(C5.0)

C5.0は、量的・質的データが混ざっていても使える手法です。 量的データが二進木、質的データがN進木になります。

RのC50というライブラリーで使えます。インストールは簡単です。 下記のコードですが、まず、csvファイルの読み込み時に、文字列型(strings)と自動認識された列は、因子型(factor)に変換しています。 forから始まる5行は、論理値型(logical)と自動認識された列を因子型に変換しています。 これらの前処理をしないと、C50のパッケージではエラーになります。

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(C50)
# ライブラリを読み込み
library(partykit)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T, stringsAsFactors=TRUE)
# データを読み込み
if (class(Data$Y) == "numeric") {
# 条件分岐の始まり
Data$Y <- droplevels(cut(Data$Y, breaks = 5,include.lowest = TRUE))
# 5分割する場合。量的データは、質的データに変換する。
}
# if文の処理の終わり
for (i in 1:ncol(Data)) {
# ループの始まり。データの列数を数えて同じ回数繰り返す
if (class(Data[,i]) == "logical") {
# 条件分岐の始まり
Data[,i] <- as.factor(Data[,i])
# logical型の列は、factor型に変換する。
}
# if文の処理の終わり
}
# ループの終わり
treeModel <- C5.0(Y ~ ., data = Data)
# C5.0を実行
plot(as.party(treeModel))
# グラフにする。繰り返した回数分のグラフファイルができる。

左が、CHAIDの「XやYに量的データが混ざっている場合」で作ったデータの結果です。 右が Wekaにある「wether.numeric」というサンプルデータをcsv形式にして、Yの列名を変えたものの結果です。 WekaのJ48とほぼ同じ結果になりました。
Decision Tree Decision Tree

ランダムフォレストで重要な変数を求める

ランダムフォレスト の実施例です。

このコードの前に、ライブラリ「randomForst」のインストールが必要です。

データは、Yと20個のXがあるとします。90行分あります。
Decision Tree

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(randomForest)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T, stringsAsFactors=TRUE)
# データを読み込み
treeModel <- randomForest(Y ~ ., data = Data, ntree = 10)
# 木を10個作る。省略すると500個
varImpPlot(treeModel)
# 変数の重要度のグラフを作る

Decision Tree

予測値の求め方は、他の手法と同じで 予測のためのソフトの使い方 にあります。

ランダムフォレストで、データマイニング

randomForestのライブラリは、 ランダムフォレスト を使って、重要な変数を探したり、予測をするのに便利に作られています。 しかし、それらの計算の途中で作られる個々の木(フォレスト:森)が、どのように作られたかというのは、 それらしい情報は見れるのですが、出力する機能はないようです。

そこで、筆者は、木の構造を出力するためのコードを作ってみました。

randomForestのコードではなく、rpart、CAHID、C5.0のそれぞれをベースにしています。 一般的なランダムフォレストは2進木なので、一般的なランダムフォレストの計算途中の木に近い結果が出るのは、rpartをベースにしたものになります。 N進木 のものが欲しかったので、CHAIDベースとC5.0ベースのランダムフォレストも作ってみました。

ポイントになるのは10回分のデータセットの作り方です。 このコードでは、自分で調整できるのが便利です。

rpartベースのランダムフォレスト

このコードの前に、ライブラリ「partykit」のインストールが必要です。

データは、Yと20個のXがあるとします。90行分あります。
Decision Tree

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(rpart)
# ライブラリを読み込み
library(partykit)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T)
# データを読み込み
ncolMax <- ncol(Data)
# データの列数を求める
nrowMax <- nrow(Data)
# データの行数を求める
DataY <- Data$Y
# Yの列を別名で保管する
Data$Y <- NULL
# データからYの列を消して、Xの列だけにする
for (i in 1:9) {
# ループの始まり。「9」でランダムサンプリングを9回実行することを表す
DataX <- Data[,runif (floor(sqrt(ncolMax)), max=ncolMax)]
# ランダムに列数の平方根の数の列を選択する
Data1 <- transform(DataX, Y = DataY) 
# Yと選択したXで新しいデータセットを作る
Data2 <- Data1[runif (floor(sqrt(nrowMax)), ,max=nrowMax), ]
# ランダムに行数の平方根の数の行を選択する
treeModel <- rpart(Y ~ ., data = Data2, minsplit = 3)
# rpartを実行
jpeg(paste("plot",i,".jpg"), width = 300, height = 300)
# 描画デバイスを開く
plot(as.party(treeModel))
# グラフにする。繰り返した回数分のグラフファイルができる。
dev.off()
# 描画デバイスを閉じる
}
# ループの終わり

このコードを実行すると、作業用ディレクトリに結果の画像ファイルが木の数だけ作成されます。
Decision Tree
狙い通りに、枝が3つ以上になる木もできました。

C5.0ベースのランダムフォレスト

このコードの前に、ライブラリ「partykit」と「C50」のインストールが必要です。

データは、上記と同じです。

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(C50)
# ライブラリを読み込み
library(partykit)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T, stringsAsFactors=TRUE)
# データを読み込み
ncolMax <- ncol(Data)
# データの列数を求める
nrowMax <- nrow(Data)
# データの行数を求める
if (class(Data$Y) == "numeric") {
# 条件分岐の始まり
Data$Y <- droplevels(cut(Data$Y, breaks = 5,include.lowest = TRUE))
# 5分割する場合。量的データは、質的データに変換する。
}
# if文の処理の終わり
DataY <- Data$Y
# Yの列を別名で保管する
for (i in 1:ncolMax) {
# ループの始まり。データの列数を数えて同じ回数繰り返す
if (class(Data[,i]) == "logical") {
# 条件分岐の始まり
Data[,i] <- as.factor(Data[,i])
# logical型の列は、factor型に変換する。
}
# if文の処理の終わり
}
# ループの終わり
Data$Y <- NULL
# データからYの列を消して、Xの列だけにする
for (i in 1:9) {
# ループの始まり。「9」でランダムサンプリングを9回実行することを表す
DataX <- Data[,runif (floor(sqrt(ncolMax)), max=ncolMax)]
# ランダムに列数の平方根の数の列を選択する
Data1 <- transform(DataX, Y = DataY) 
# Yと選択したXで新しいデータセットを作る
Data2 <- Data1[runif (floor(sqrt(nrowMax)), ,max=nrowMax), ]
# ランダムに行数の平方根の数の行を選択する
treeModel <- C5.0(Y ~ ., data = Data2)
# C5.0を実行
jpeg(paste("plot",i,".jpg"), width = 300, height = 300)
# 描画デバイスを開く
plot(as.party(treeModel))
# グラフにする。繰り返した回数分のグラフファイルができる。
dev.off()
# 描画デバイスを閉じる
}
# ループの終わり

このコードを実行すると、作業用ディレクトリに結果の画像ファイルが木の数だけ作成されます。
Decision Tree
狙い通りに、枝が3つ以上になる木もできました。

CHAIDベースのランダムフォレスト

量的データも3分岐以上になる N進木 にしたい場合は、CHAIDを使っても良いと思います。

下のコードの特徴ですが、CHAIDであることの他に、 アンサンブル学習 のページに書いている「列のバギング」になっています。 つまり、サンプリングは列だけで、行はしていません。

C5.0ベースのものと比べると、計算時間が長いです。

このコードの前に、ライブラリ「CHAID」のインストールが必要です。

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(CHAID)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T, stringsAsFactors=TRUE)
# データを読み込み
ncolMax <- ncol(Data)
# データの列数を求める
nrowMax <- nrow(Data)
# データの行数を求める
for (i in 1:ncolMax) {
# ループの始まり。データの列数を数えて同じ回数繰り返す
if (class(Data[,i]) == "numeric") {
# 条件分岐の始まり
Data[,i] <- droplevels(cut(Data[,i], breaks = 5,include.lowest = TRUE))
# 5分割する場合。量的データは、質的データに変換する。
}
# if文の処理の終わり
}
# ループの終わり
DataY <- Data$Y
# Yの列を別名で保管する
Data$Y <- NULL
# データからYの列を消して、Xの列だけにする
for (i in 1:9) {
# ループの始まり。「9」でランダムサンプリングを9回実行することを表す
DataX <- Data[,runif (floor(sqrt(ncolMax)), max=ncolMax)]
# ランダムに列数の平方根の数の列を選択する
Data1 <- transform(DataX, Y = DataY) 
# Yと選択したXで新しいデータセットを作る
#Data2 <- Data1[runif (floor(sqrt(nrowMax)), ,max=nrowMax), ]
# ランダムに行数の平方根の数の行を選択する
Data2 <- Data1
# 行はサンプリングしない
treeModel <- chaid(Y ~ ., data = Data2)
# C5.0を実行
jpeg(paste("plot",i,".jpg"), width = 600, height = 300)
# 描画デバイスを開く
plot(treeModel)
# グラフにする。繰り返した回数分のグラフファイルができる。
dev.off()
# 描画デバイスを閉じる
}
# ループの終わり

Decision Tree

モデル木

モデル木 の例です。

setwd("C:/Rtest") # 作業用ディレクトリを変更
library(Cubist)
# ライブラリを読み込み
library(ggplot2)
# ライブラリを読み込み
Data <- read.csv("Data.csv", header=T)
# データを読み込み
Ydata <- Data$Y
# Yのデータを抜き出す
Data$Y<-NULL
# Yの列を消す
Cu <- cubist(y = Ydata, x=Data, data = Data)
# モデルを作る
summary(Cu)
# 結果(モデル)の出力
Decision Tree
Output <- predict(Cu,Data)
# 予測値を計算
Data2 <- cbind(Data, Output)

ggplot(Data2, aes(x=Y, y=Output)) + geom_point()+xlab("Data Y")+ylab("Predicted Y")+ggtitle("Model Tree (Cubist)")
# 予測値と元データのYの関係

Decision Tree

上記のcubistはデフォルトです。 例えば、作られるルールを3つまでで止めたい場合は、下記になります。
Cu <- cubist(y = Ydata, x=Data, data = Data, control=cubistControl(rules = 3))



参考文献

CHAID

RのCHAIDのパラメータ
https://rdrr.io/rforge/CHAID/man/chaid.html


CHAIDのインストール
CHAIDは、一般的なRのパッケージとは違って、CRANにはありません。このページのファイルをダウンロードして手動で読み込むか、 R-forgeから読み込むように設定を変える必要があります。
https://r-forge.r-project.org/R/?group_id=343


ランダムフォレスト

同志社大学 金明哲先生のページ
Rと集団学習 https://www1.doshisha.ac.jp/~mjin/R/Chap_32/32.html


Rでバギングして、個々の木を見る
https://toukeier.hatenablog.com/entry/2018/09/10/214253#%E3%83%90%E3%82%AE%E3%83%B3%E3%82%B0%E3%81%AE%E4%BE%8B


このページの例とは違うコードが載っていますが、 ランダムフォレスト用のデータセットを作るためのヒントは、これを参考にさせていただきました。
http://tips-r.blogspot.com/2014/06/r1.html


Pythonで、木をたくさん作る過程を動画にする話があります。
https://watlab-blog.com/2020/01/06/random-forest-animation/





Tweet データサイエンス教室