rpart

Published

October 1, 2026

パッケージの概要

rpart (Recursive Partitioning And Regression Trees)は、再帰的分割(データをグループに分けることを繰り返して木構造を作る手法)によって回帰木・分類木を構築するためのパッケージです。また、rpart.plot パッケージを用いることで、決定木をより見やすい形で可視化することができます。

使用例:Bostonデータ

MASSパッケージに含まれるBostonデータを用いて、住宅価格の中央値を推定する回帰木の構築を試してみます。

データの確認

Bostonデータを読み込みます。12列目(black)は使用しないこととします。

library(MASS)
df <- Boston[, -12]
str(df)
'data.frame':   506 obs. of  13 variables:
 $ crim   : num  0.00632 0.02731 0.02729 0.03237 0.06905 ...
 $ zn     : num  18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
 $ indus  : num  2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
 $ chas   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ nox    : num  0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
 $ rm     : num  6.58 6.42 7.18 7 7.15 ...
 $ age    : num  65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
 $ dis    : num  4.09 4.97 4.97 6.06 6.06 ...
 $ rad    : int  1 2 2 3 3 3 5 5 5 5 ...
 $ tax    : num  296 242 242 222 222 222 311 311 311 311 ...
 $ ptratio: num  15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
 $ lstat  : num  4.98 9.14 4.03 2.94 5.33 ...
 $ medv   : num  24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...

各変数の意味は以下の通りです。

変数名 概要
crim 町別の「犯罪率」 
zn 25,000平方フィートを超える区画に分類される住宅地の割合
indus    町別の「非小売業の割合」    
chas    チャールズ川のダミー変数(区画が川に接している場合は1、そうでない場合は0)
nox    「NOx濃度(0.1ppm単位)」=一酸化窒素濃度    
rm    1戸当たりの「平均部屋数」    
age    1940年より前に建てられた持ち家の割合   
dis    5つあるボストン雇用センターまでの加重距離     
rad    「主要高速道路へのアクセス性」の指数   
tax    10,000ドル当たりの「固定資産税率」    
ptratio   町別の「生徒と先生の比率」     
lstat   「低所得者人口の割合」    
medv    「住宅価格」(1000ドル単位)の中央値(目的変数)

数値変数について、medvとの散布図を確認します。

library(ggplot2)
library(dplyr)

Attaching package: 'dplyr'
The following object is masked from 'package:MASS':

    select
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(tidyr)
# データ読み込み
data("Boston")
df <- Boston

# 目的変数と説明変数(数値)
target <- "medv"
num_vars <- c("crim", "zn", "indus", "nox", "rm", "age", "dis", "tax", "ptratio", "lstat")

# long形式に変換
df_long_num <- df %>%
  select(all_of(c(target, num_vars))) %>%
  pivot_longer(cols = all_of(num_vars),
               names_to = "variable", values_to = "value")

# 散布図
ggplot(df_long_num, aes(x = value, y = .data[[target]])) +
  geom_point(alpha = 0.6) +
  facet_wrap(~variable, scales = "free_x") +
  theme_minimal()

カテゴリ変数について、medvのヒストグラムを確認します。以下は、変数chas(チャールズ川に接しているかどうか)についてのヒストグラムです。

# chas
ggplot(df, aes(x = medv)) +
  geom_histogram(bins = 30) +
  facet_wrap(~chas) +
  theme_minimal()

以下は、変数rad(「主要高速道路へのアクセス性」の指数)についてのヒストグラムです。

# rad
ggplot(df, aes(x = medv)) +
  geom_histogram(bins = 30) +
  facet_wrap(~rad) +
  theme_minimal()

モデル構築1(全体データ)

まずは全てのデータを使って分類木モデルを構築してみます。

library(rpart)
library(rpart.plot)
# シードを設定
set.seed(123) 
(model <- rpart(medv ~ ., data = df))
n= 506 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 506 42716.3000 22.53281  
   2) rm< 6.941 430 17317.3200 19.93372  
     4) lstat>=14.4 175  3373.2510 14.95600  
       8) crim>=6.99237 74  1085.9050 11.97838 *
       9) crim< 6.99237 101  1150.5370 17.13762 *
     5) lstat< 14.4 255  6632.2170 23.34980  
      10) dis>=1.5511 248  3658.3930 22.93629  
        20) rm< 6.543 193  1589.8140 21.65648 *
        21) rm>=6.543 55   643.1691 27.42727 *
      11) dis< 1.5511 7  1429.0200 38.00000 *
   3) rm>=6.941 76  6059.4190 37.23816  
     6) rm< 7.437 46  1899.6120 32.11304  
      12) lstat>=9.65 7   432.9971 23.05714 *
      13) lstat< 9.65 39   789.5123 33.73846 *
     7) rm>=7.437 30  1098.8500 45.09667 *

モデルの可視化

モデルを可視化します。plotで木の構造(分岐)を表示し、textで各ノードの分岐の基準や分類ラベルを表示します。

plot(model)
text(model)

rpart.plotを用いると、分類木をより分かりやすく表示させることができます。

rpart.plot(model)

モデル構築2(訓練データとテストデータに分割)

Bostonデータをモデル生成のための訓練データと、モデル評価のためのテストデータに分割します。データ割合は訓練データを7割、テストデータを3割とします。確認のため、データサイズを出力します。

# シードを設定
set.seed(123) 

# データの分割
sample_indices <- sample(1:nrow(df), 0.7 * nrow(df))  
df.train <- df[sample_indices, ]
df.test <- df[-sample_indices, ]

# データサイズの確認
c(nrow(df), nrow(df.train), nrow(df.test))
[1] 506 354 152

モデル生成

訓練データを用いて分類木モデルを生成します。

# シードを設定
set.seed(123)
model2 <- rpart(medv ~ ., data = df.train)

rpart.plotでモデルを可視化します。

rpart.plot(model2)

モデル評価

テストデータを使ってモデル評価を行います。まずはテストデータを元に生成したモデルを用いて予測結果を算出します。

res_act <- df.test$medv
res_pred <- predict(model2, df.test, type = "vector")

モデル性能の評価のために、二乗平均平方根誤差(RMSE: Root Mean Squared Error)と決定係数(R²: Coefficient of Determination)を算出します。

library(MLmetrics)

Attaching package: 'MLmetrics'
The following object is masked from 'package:base':

    Recall
sprintf("RMSE : %.3f", RMSE(res_act, res_pred))
[1] "RMSE : 4.873"
sprintf("決定係数 : %.3f", R2_Score(res_act, res_pred))
[1] "決定係数 : 0.737"

ハイパーパラメーターのチューニング

rpartの主なハイパーパラメーターは以下の通りです。

  • 木の複雑度に関するパラメータ(cp)
  • ノード分割の最小サンプル数(minsplit)
  • 木の最大の深さ(maxdepth)

これらのハイパーパラメータの最適な設定を探す作業がハイパーパラメーターのチューニングとなります。

各ハイパーパラメータは相互に影響を及ぼすため、ハイパーパラメータのチューニングは個別のパラメータごとに行うのではなく、対象のパラメータを同時に行う必要があります。ここではcpとminsplitのグリッドサーチにより、最適なパラメータを探す作業を行います。

data.frameの出力整形用の関数を定義します。

library(knitr)
library(kableExtra)

Attaching package: 'kableExtra'
The following object is masked from 'package:dplyr':

    group_rows
show_df <- function(df, cap=""){
  if(is_html_output()){
    kable(df, format = "html", caption = cap) %>%
      kable_styling(bootstrap_options = "striped", full_width = FALSE, position = "left")
  }else{
    kable(df, format = "pipe", caption = cap) 
  }
}

チューニング候補値です。expand.gridを作って全ての組み合わせを列挙します。

param_grid <- expand.grid(
  cp = c(0.01, 0.03, 0.05),
  minsplit = c(10, 20, 30, 40, 50)
)

各パラメータでモデル精度を検証してゆきます。

#結果保存用
results <- data.frame(
  cp = numeric(),
  minsplit = integer(),
  RMSE = numeric(),
  R2_Score = numeric()
)

set.seed(123)
for (i in 1:nrow(param_grid)) {
  params <- param_grid[i, ]
  
  model3 <- rpart(
    medv ~ .,
    data = df.train,
    control = rpart.control(cp = params$cp, minsplit = params$minsplit),
  )
  
  res_pred <- predict(model3, df.test, type = "vector")
  
  results <- rbind(results, data.frame(
    cp = params$cp,
    minsplit = params$minsplit,
    RMSE = round(RMSE(res_act, res_pred), 3),
    R2_Score = round(R2_Score(res_act, res_pred), 3)
  ))
}

show_df(results)
cp minsplit RMSE R2_Score
0.01 10 4.617 0.771
0.03 10 4.852 0.734
0.05 10 4.852 0.734
0.01 20 4.873 0.737
0.03 20 4.934 0.724
0.05 20 5.202 0.691
0.01 30 5.134 0.713
0.03 30 5.202 0.691
0.05 30 5.202 0.691
0.01 40 4.961 0.726
0.03 40 5.202 0.691
0.05 40 5.202 0.691
0.01 50 5.147 0.709
0.03 50 5.202 0.691
0.05 50 5.202 0.691

RMSEが小さく決定係数が大きな組み合わせであるcp = 0.01、minsplit = 10の設定が良さそうであることが分かりました。より汎化性能を高めるためには、クロスバリデーションを行う必要があります。

このモデルの分類木を可視化してみましょう。木の形も少し変わっています。

# シードを設定
set.seed(123)

model4 <- rpart(
  medv ~ ., 
  data = df.train,
  control = rpart.control(cp = 0.01, minsplit = 10)
)

rpart.plot(model4)