// PART 03 · ARTICLE 09 / 15
ALGO TREE L1

ランダムフォレスト

— Bagging の決定版、リハ予後予測の中堅選手

9 min read· L1· 2026.05.20 update· by Editor

ランダムフォレストは、多数の決定木を作り、分類なら多数決、回帰なら平均で予測するモデルです[1]。1 本の木はわかりやすい一方で、データの少しの違いで構造が大きく変わってしまいます。ランダムフォレストは、その不安定さを複数の木でならす考え方。リハビリテーション研究では、FIM・SIAS・NIHSS・BBS・TUG・歩行指標・認知機能などの表形式データを扱う場面で使いやすいモデルです。

// CONTEXT

本記事では、ランダムフォレストを「高性能な黒箱」としてではなく、決定木 の不安定さを Bagging で抑える方法として整理します(アンサンブル学習 の系譜の中で位置づけます)。比較の基準は ロジスティック回帰正則化線形モデル。 過学習の考え方は 09·01 過学習と正則化14·01 過学習デモ を、評価設計は 14·02 交差検証デモ を合わせて読むと理解しやすくなります。

// 01 · LEARN OUTCOMESこの記事で学ぶこと

  • ランダムフォレストが、ブートストラップと特徴量サブセットで多数の木を作る仕組みを説明できる。
  • Gini importance、Permutation importance、SHAP の違いと限界を区別できる。
  • リハ研究で、ロジスティック回帰・正則化線形・勾配ブースティングと比較する視点を持てる。
  • Pipeline、GroupKFold、チューニング、リーケージ防止を含む実装の流れを理解できる。

// 02 · CONCLUSIONまず結論

なお、RF の結果を臨床導入の可否に直結させるのではなく、まず研究仮説とモデル比較の材料として慎重に扱います。特に単施設研究では、外部施設で同じ傾向が再現するかを丁寧に確認します。

ランダムフォレストの強みは、非線形性と交互作用を比較的自然に扱える点です。「入院時 FIM が低い症例では年齢の影響が強いが、FIM が高い症例では認知機能の影響が目立つ」といった関係を、明示的に交互作用項を作らなくても拾うことがあります。

一方、係数やオッズ比のような直接的な説明はできません。重要度や SHAP で説明を補う必要があり、ランダムフォレストは「解釈しやすいモデル」ではなく、説明を追加できる予測モデルとして扱う方が安全です。

// 03 · FIGURE図で理解するランダムフォレスト

// RANDOM FOREST · 行と列の 2 つのランダム化 元データ D N 例 × p 特徴量 ① Bootstrap 行のランダム化 D_b^{boot 1} 重複あり抽出 D_b^{boot 2} 重複あり抽出 D_b^{boot B} ... B 個 (例: 500) ② max_features 列のランダム化 各分岐で √p や log₂(p) 個 Tree₁ 候補: {x₁,x₃,x₇} 深い木 Tree₂ 候補: {x₂,x₄,x₆} 深い木 Tree_B 候補: {x₃,x₅,x₈} 深い木 ③ Aggregate 投票 / 平均 予測 f̂_RF(x) 分類: 多数決 / 回帰: 平均 行と列の二重ランダム化 → 木同士の相関を下げ、分散を抑える (Bagging + Random Subspace)
図1: ランダムフォレストの基本構造。元データからブートストラップサンプルを複数作り、各木では一部の特徴量だけを候補にして分岐します。最後に多数決または平均で予測します。
[ A ] GINI IMPORTANCE model.feature_importances_ — 分岐の不純度減少の合計 入院時FIM運動 0.28 施設ID 0.22 ← 高カード変数で水増し 年齢 0.18 認知FIM 0.14 BBS 0.10 NIHSS 0.08 0.00 0.15 0.30 + 速い (学習時に副産物として取得) + モデル内部の値: 追加計算不要 − 高カード変数を過大評価 − 連続変数 > カテゴリ変数 になりがち [ B ] PERMUTATION IMPORTANCE 検証データでシャッフル → 性能低下を測定 入院時FIM運動 0.32 認知FIM 0.20 年齢 0.15 BBS 0.12 NIHSS 0.10 施設ID 0.03 ← 真の寄与が露出 0.00 0.16 0.32 + モデル非依存・公平な比較が可能 + 検証データ上の "実際の寄与" を測る − 計算が重い (n_repeats × p 回の再評価) − 相関変数では値が分散して低く出る RULE OF THUMB 学習時のスクリーニングは Gini で速く回し、論文・報告書での "主要特徴量" は Permutation で再確認する。
図2: Gini importance と Permutation importance の違い。Gini importance は分岐での不純度減少を合計します。Permutation importance は評価データで特徴量をシャッフルし、性能低下から寄与を見ます。

図1で重要なのは、木がただ増えるだけではない点です。各木は、症例の一部を重複ありで抽出したデータから学びます。さらに、各分岐では全特徴量を見ず、一部の特徴量だけを候補にします[2]。この 2 つのランダム化で、似た木ばかりになることを避けます。

図2は、解釈の注意点を示します。Gini importance は計算が速く、モデルの中から簡単に取り出せます。しかし、カテゴリ数の多い変数や連続変数を高く見積もる傾向があり、医療データで施設 ID や病棟 ID を入れる場合、この偏りは特に問題になります。

// 04 · CLINICALリハ研究での使いどころ

// CASE 1 · 退院時 FIM 予測

回復期病棟の脳卒中データで、退院時 FIM 合計点や歩行自立を予測する場面です。入院時 FIM・年齢・発症から入院までの日数・SIAS・認知機能・栄養指標などを入力します。まずロジスティック回帰や正則化線形を作り、その比較対象としてランダムフォレストを置くと、非線形性を加えた効果が見えやすくなります。

// CASE 2 · 転倒予測 100 変数

BBS・TUG・10m 歩行・服薬数・既往歴・認知機能・夜間トイレ回数など、候補変数が多い転倒予測では、単純なモデルだけでは関係を拾いきれないことがあります。ランダムフォレストは、上位 20 変数の重要度を可視化できます。ただし、重要度の順位は「転倒の原因」の順位ではなく、予測に効いた情報として読みます。

// CASE 3 · 多施設データと施設差

多施設のリハデータでは、施設ごとに評価時期・リハ量・退院調整・記録方法が異なることがあります。施設 ID を入れると性能が上がる場合もありますが、施設固有の運用を学習しているだけかもしれません。GroupKFold で施設単位に分けると、未知施設での汎化性能をより厳しく確認できます。

// CASE 4 · 歩行特徴量の異常分類

ウェアラブルセンサーや動画解析から、歩幅・歩行速度・左右対称性・立脚時間・加速度特徴量を抽出する場面です。特徴量同士の関係が直線的でない場合、ランダムフォレストは安定した比較モデルになります。時系列そのものを扱うモデルではないため、時相情報を使うには特徴量設計が必要です。

// 05 · THEORY仕組みを少し詳しく見る

1. 決定木をたくさん作る

ランダムフォレストは、決定木を多数作る方法です。1 本の木は、データを「FIM が 60 以上か」「年齢が 75 歳以上か」のような閾値で分けます。ただし、1 本だけでは分割が偶然の症例配置に左右されてしまうため、複数の木を作って予測を平均します。

Classification:
  final prediction = majority vote of trees

Regression:
  final prediction = average of tree predictions

2. 行のランダム化: ブートストラップ

各木は、元データから重複を許して症例を抽出したデータで学習します。これをブートストラップサンプルと呼びます。ある症例は同じ木に複数回入ることがあり、別の症例はその木には入らないこともあります。

D_b^{boot} = bootstrap sample from D
T_b(x)     = tree fitted on D_b^{boot}

この「木ごとに違う症例を見る」仕組みが、Bagging の中心です。単一木の分散を減らす方向に働きます[3]。深い木は個々には揺れますが、多数の木で平均すると揺れが小さくなります。

3. 列のランダム化: max_features

ランダムフォレストは、各分岐で使う候補特徴量もランダムに制限します。40 個の特徴量があっても、各分岐ではその一部だけを見ます。これにより、すべての木が「入院時 FIM」ばかりで分岐する状況を避けられます。

At each split:
  choose a random subset of features
  search the best split only within that subset

max_features を小さくすると、木同士の違いは増えます。ただし個々の木は弱くなる場合があります。大きくすると強い木になりやすい一方、木同士が似てきます。ここに、強さと多様性のバランスがあります。

4. 予測式

ランダムフォレストの予測は、次のように書けます。B は木の数、T_b は b 番目の木です。

Regression:
  f_RF(x) = (1 / B) * Σ T_b(x; theta_b, D_b^{boot})

Classification:
  p_RF(y = 1 | x) = (1 / B) * Σ I(T_b(x) = 1)

分類で得られる確率は、木の投票割合です。便利ですが、較正された確率とは限りません。予後予測モデルとして使うなら、AUC だけでなく Brier score や calibration plot も確認します。

5. OOB 推定

ブートストラップで各木を作ると、ある症例はその木の学習に使われません。その症例にとって、使われなかった木は簡易的な検証モデルになります。これを Out-of-Bag (OOB) 推定と呼びます。

Probability that a sample is not selected in one bootstrap sample:
  (1 - 1/n)^n ≈ 1/e ≈ 0.368

OOB は便利ですが、万能な外部検証ではありません。同じ施設・同じ時期・同じ収集ルールの中での内部的な見積もりです。多施設研究や時期間差がある研究では、GroupKFold や外部検証と役割を分けます。

6. 重要度の読み方

Gini importance は、各特徴量が分岐でどれだけ不純度を下げたかを合計します[4]。計算が速い反面、連続変数やカテゴリ数の多い変数を高く評価しやすい性質があります。施設 ID のような高カード変数を扱うときは特に注意が必要です。

Gini importance:
  sum of impurity decrease over splits using the feature

Permutation importance:
  performance drop after shuffling the feature in validation data

Permutation importance は、評価データで特徴量をシャッフルし、性能がどれだけ下がるかを見ます。モデル内部の分岐だけでなく、検証データ上の影響を見られるのが利点。一方、相関の強い特徴量が複数あると、重要度が分散して低く見えることがあります。

7. リハ研究で特に効く制約

リハビリテーション研究では、症例数が 100〜1,000 程度に収まることが多くあります。一方で、FIM の小項目・麻痺評価・歩行指標・認知機能・併存疾患・栄養指標を入れれば、特徴量はすぐに増えます。ランダムフォレストは、このような中小規模の表形式データで、線形モデルより柔軟な比較対象になります。

ただし、柔軟さは利点だけではありません。入院期間・退院前評価・退院調整の結果など、予測時点より未来の情報を入れると、モデルは簡単に高性能に見えてしまいます。これはアルゴリズムの力ではなく データリーケージ。研究目的が「入院時に退院時 ADL を予測する」なら、入力変数は入院時点で利用できる情報に限定します。

// 06 · IMPLEMENTATIONPython 実装例

以下は scikit-learn を用いた、教育用の仮想データ example_rehab_dataset.csv の例です[5]。実データを扱う場合は、個人情報保護・倫理審査・施設規程・データ利用契約を確認します。解析前に全データで欠測補完やエンコードを fit するとリーケージになるため、前処理は Pipeline の中に入れます。

# Random Forest for an educational rehab dataset
# File: example_rehab_dataset.csv
# Do not include direct identifiers in the analysis file.

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.inspection import permutation_importance
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score, brier_score_loss
from sklearn.model_selection import GridSearchCV, GroupKFold, StratifiedKFold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

df = pd.read_csv("example_rehab_dataset.csv")
y = df["home_discharge"]
groups = df["facility_id"] if "facility_id" in df.columns else None

numeric_features = [
    "age", "days_from_onset", "admission_fim_motor",
    "admission_fim_cognition", "sias_total", "nihss_total",
    "bbs", "tug_sec", "grip_strength", "moca",
]
categorical_features = ["sex", "diagnosis_group", "stroke_type"]
feature_cols = numeric_features + categorical_features
X = df[feature_cols]

# StandardScaler is not used for trees.
rf_preprocess = ColumnTransformer([
    ("num", SimpleImputer(strategy="median"), numeric_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore")),
    ]), categorical_features),
])

rf_pipe = Pipeline([
    ("preprocess", rf_preprocess),
    ("model", RandomForestClassifier(
        n_estimators=500,
        random_state=42,
        n_jobs=-1,
        class_weight="balanced",
        oob_score=True,
    )),
])

# Numeric variables are scaled only for the linear model.
logit_preprocess = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]), numeric_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore")),
    ]), categorical_features),
])

logit_pipe = Pipeline([
    ("preprocess", logit_preprocess),
    ("model", LogisticRegression(
        penalty="l2", solver="lbfgs",
        max_iter=2000, class_weight="balanced",
    )),
])

if groups is not None:
    cv = GroupKFold(n_splits=5)
else:
    cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

param_grid = {
    "model__max_depth": [3, 5, 8, None],
    "model__min_samples_leaf": [5, 10, 20, 40],
    "model__max_features": ["sqrt", "log2", 0.5],
}

search = GridSearchCV(
    rf_pipe,
    param_grid=param_grid,
    scoring="roc_auc",
    cv=cv,
    n_jobs=-1,
    refit=True,
)

fit_kwargs = {"groups": groups} if groups is not None else {}
search.fit(X, y, **fit_kwargs)

print("Best RF params:", search.best_params_)
print("Best RF CV AUC:", round(search.best_score_, 3))
print("RF OOB score:", round(search.best_estimator_.named_steps["model"].oob_score_, 3))

logit_scores = []
for train_idx, test_idx in cv.split(X, y, groups=groups):
    logit_pipe.fit(X.iloc[train_idx], y.iloc[train_idx])
    pred = logit_pipe.predict_proba(X.iloc[test_idx])[:, 1]
    logit_scores.append(roc_auc_score(y.iloc[test_idx], pred))
print("Logistic CV AUC:", round(np.mean(logit_scores), 3))

# Use held-out or external data in a real study.
perm = permutation_importance(
    search.best_estimator_, X, y,
    scoring="roc_auc", n_repeats=20,
    random_state=42, n_jobs=-1,
)
importance = pd.DataFrame({
    "feature": feature_cols,
    "mean_importance": perm.importances_mean,
    "sd_importance": perm.importances_std,
}).sort_values("mean_importance", ascending=False)
print(importance.head(20))

prob = search.best_estimator_.predict_proba(X)[:, 1]
print("Apparent Brier:", round(brier_score_loss(y, prob), 3))

このコードでは、ランダムフォレストとロジスティック回帰を同じ特徴量で比較しています。ランダムフォレスト側には StandardScaler を入れていません。木系モデルは特徴量の大小そのものではなく、閾値で分割するためです。一方、ロジスティック回帰側には StandardScaler を入れています。

facility_id がある場合は GroupKFold を使っています。同じ施設の症例が学習と評価の両方に入ることを避ける設計です。多施設研究では、単純な StratifiedKFold より厳しい評価になり、施設差が大きい研究では、この差が結果の解釈を変えます。

// 07 · MYTHSよくある誤解

誤解: ランダムフォレストは過学習しない
単一木より過学習しにくい傾向はあります。とはいえ、深すぎる木・少なすぎる min_samples_leaf・施設 ID のような強い識別子があると、内部データに寄りすぎることがあります。
誤解: Gini importance で上位なら予後因子といえる
Gini importance は予測寄与の指標で、因果効果ではありません。さらに、カテゴリ数の多い変数や連続変数に偏ることがあります。重要度を見るなら、Permutation importance や SHAP も併用します。
誤解: n_estimators=100 で固定すれば十分
木の数を増やすと予測は安定しやすくなりますが、計算量も増えます。まず 300〜500 程度で安定性を見て、性能が頭打ちかを確認します。
誤解: 標準化不要なので前処理も不要
標準化は不要でも、欠測補完・カテゴリ変数の処理・外れ値の確認・時点の整合性は必要です。とくに退院後情報や入院期間など、未来の情報を入れない設計が重要です。

// 08 · WRITING論文 Methods に書くべきこと

ランダムフォレストを使った研究では、モデル名だけでは Methods として不足します。査読では、前処理・交差検証・ハイパーパラメータ・比較モデル・評価指標・解釈手法が問われます。TRIPOD+AI では、予測モデル研究の報告において、データソース・アウトカム・予測因子・モデル開発・評価・使用目的を明確にすることが重視されます[6]。PROBAST+AI の観点では、対象者・予測因子・アウトカム・解析方法のバイアスを確認します[7]

Methods に入れたい項目

  • 01アウトカム定義: 自宅退院、歩行自立、退院時 FIM など。
  • 02入力変数の時点: 入院時、発症後日数、初回評価など。
  • 03前処理: 欠測補完、カテゴリ変数処理、標準化不要の理由。
  • 04モデル設定: n_estimators、max_depth、min_samples_leaf、max_features。
  • 05検証設計: StratifiedKFold、GroupKFold、hold-out、外部検証。
  • 06比較モデル: ロジスティック回帰、正則化線形、勾配ブースティング。
  • 07評価指標: ROC AUC、感度、特異度、Brier score、calibration plot。
  • 08解釈手法: Gini importance、Permutation importance、SHAP の使い分け。

実装では、性能表だけでなく、どの分割で評価したかを残すことも重要です。同じ AUC でも、ランダム分割で得た値と、施設単位で分けた値では意味が違います。Methods には、分割単位・乱数シード・探索範囲・最終モデルの選択基準を記載します。

査読で指摘されやすい点

1 つ目は、比較対象がないこと。ランダムフォレストだけを提示すると、その性能が本当に非線形モデルによるものか判断できません。少なくともロジスティック回帰や正則化線形と比較します。

2 つ目は、交差検証の単位。多施設データで症例単位にランダム分割すると、施設固有の癖が学習と評価の両方に入ることがあり、未知施設での性能を過大評価する可能性があります。

3 つ目は、重要度の解釈です。「重要度が高いので予後因子といえる」と書くと、因果推論と混同されます。「予測に寄与した変数」と表現し、臨床的解釈は仮説として示します。

もう一つの注意点は、最終モデルの保存方法です。交差検証で最も良かった fold のモデルだけを採用すると、偶然に良かった分割へ寄ります。実務では、探索で選んだハイパーパラメータを固定し、開発データ全体で再学習したモデルを最終候補として扱います。

4 つ目は、較正の不足です。ランダムフォレストは分類性能が良く見えても、確率の較正が十分とは限りません。退院支援やリスク層別化に使う研究では、AUC だけでなく calibration も示します。

// 09 · CHECKLIST実装前の確認リスト

  • 01目的変数は、入院時点で予測可能な臨床課題になっている。
  • 02退院後にしかわからない情報を説明変数に入れていない。
  • 03欠測補完とカテゴリ変数処理を Pipeline 内で行っている。
  • 04RF では StandardScaler を使わない理由を説明できる。
  • 05max_depth と min_samples_leaf を調整している。
  • 06ロジスティック回帰や正則化線形と比較している。
  • 07Gini importance だけで結論を書いていない。
  • 08多施設データでは GroupKFold や外部検証を検討している。

// 10 · QUIZ理解度チェック

  1. Q1ランダムフォレストの「列のランダム化」を主に制御するハイパーパラメータはどれですか。
    • n_estimators
    • max_features
    • class_weight
    • random_state
    SHOW ANSWER
    B. max_features は各分岐で候補にする特徴量数を制御します。
  2. Q2RF の前処理として正しいものはどれですか。
    • 数値変数は必ず標準化する
    • 欠測補完は全データで先に fit する
    • 欠測補完と OneHotEncoder は Pipeline 内に入れる
    • カテゴリ変数はそのまま文字列で入れてよい
    SHOW ANSWER
    C. RF は標準化不要ですが、欠測補完とカテゴリ変数処理は必要です。
  3. Q3Gini importance の注意点として適切なものはどれですか。
    • 因果効果を直接示す
    • カテゴリ数の多い変数を高く評価することがある
    • 検証データを必ず使って計算される
    • 相関特徴量の影響を完全に補正できる
    SHOW ANSWER
    B. 高カード変数や連続変数に偏ることがあります。
  4. Q4多施設データで施設差を考慮したい場合、検討しやすい交差検証はどれですか。
    • GroupKFold
    • Leave-one-feature-out
    • RandomizedSearchCV のみ
    • 標準化
    SHOW ANSWER
    A. 施設 ID で group を作り、施設単位で分割できます。

// 11 · FAQよくある質問

ランダムフォレストで標準化は必要ですか?
不要です。木系モデルは閾値で分岐するため、変数のスケールの違いに影響されにくい性質があります。一方、欠測補完とカテゴリ変数処理は Pipeline 内で行います。
Gini importance と Permutation importance は使い分けるべきですか?
はい。Gini importance は計算が速い反面、高カード変数や連続変数を過大評価する傾向があります。論文の主要特徴量としては Permutation importance や SHAP で再確認するのが安全です。
多施設データで RF を評価するときの注意点は?
症例単位で StratifiedKFold すると、同じ施設の症例が学習と評価の両方に入り、未知施設の汎化性能を過大評価することがあります。GroupKFold で施設単位に分割するか、外部施設データで検証します。

// REF参考文献

  1. Breiman L. Random forests. Machine Learning 2001;45(1):5-32.
  2. Ho TK. The random subspace method for constructing decision forests. IEEE Transactions on Pattern Analysis and Machine Intelligence 1998;20(8):832-844.
  3. Breiman L. Bagging predictors. Machine Learning 1996;24(2):123-140.
  4. Strobl C, Boulesteix AL, Zeileis A, Hothorn T. Bias in random forest variable importance measures: illustrations, sources and a solution. BMC Bioinformatics 2007;8:25.
  5. Pedregosa F, Varoquaux G, Gramfort A, et al. Scikit-learn: machine learning in Python. Journal of Machine Learning Research 2011;12:2825-2830.
  6. Collins GS, Moons KGM, Dhiman P, et al. TRIPOD+AI statement: updated guidance for reporting clinical prediction models that use regression or machine learning methods. BMJ 2024;385:e078378.
  7. Moons KGM, Damen JAA, Kaul T, et al. PROBAST+AI: an updated quality, risk of bias, and applicability assessment tool for prediction models using regression or artificial intelligence methods. BMJ 2025;388:e082505.