表形式データの前処理は、派手ではありません。 しかし、研究の信頼性を大きく左右します。 欠測、外れ値、表記揺れ、時点の混入を見逃すと、 モデル性能は見かけ上よく見えることがあります。 本記事では、第2部の総まとめとして、 リハ研究でまず確認したい実践的な手順を整理します。
ここで扱うのは、FIM、SIAS、NIHSS、握力、歩行速度、 施設 ID、退院先などを含む表形式データです。 画像や動画より小さく見えるデータでも、 時点・定義・欠測理由をそろえないと、 予測モデルの解釈は不安定になります。
// 01 · LEARN OUTCOMESこの記事で学ぶこと
- データ受領後 30 分で確認する項目を説明できます。
- データ辞書に入れるべき列名、単位、時点、欠測理由を整理できます。
- 施設別・時期別の分布差を、前処理段階で確認できます。
- 第2部の内容を 1 つの Pipeline にまとめる考え方を理解できます。
// 02 · CONCLUSION結論
// 03 · FIGURE図で理解する表形式データ前処理
前処理は、個別のテクニックの集まりではありません。 欠測補完だけを丁寧に行っても、 時点が混ざればリーケージが起こります。 標準化だけを Pipeline に入れても、 特徴量選択を全データで行えば評価は楽観的になります。
実務では、まずデータを理解する段階があります。 その次に、モデルへ渡すための整形があります。 この 2 段階を分けて考えると、 ミスを見つけやすくなります。
// 04 · CLINICALリハ研究でよく起きる場面
回復期病棟のデータを受け取りました。 行数は N=287、列数は 50 です。 SIAS 下肢項目の欠測率が 22% でした。 患者 ID の重複はありませんでした。
ここで重要なのは、欠測率だけではありません。 欠測が重症例に偏るかを確認します。 欠測理由が「評価不能」なのか、 「入力漏れ」なのかも分けて記録します。
施設 A だけ追跡期間が長いデータがあります。 施設 B と C では、6 か月後 FIM が少ない状況です。 施設 ID は単なる管理番号ではありません。 収集体制や患者背景を反映することがあります。
施設別に欠測率、年齢、入院時 FIM、 アウトカム分布を確認します。 モデル評価では、施設単位の分割も検討します。 これは データリーケージの予防にも関係します。
診断名に「脳梗塞」「脳こうそく」 「Cerebral infarction」が混在しています。 これを別カテゴリとして扱うと、 同じ病態が複数の変数に分かれます。
まず正規化辞書を作ります。 その後に One-Hot Encoding や Frequency Encoding を行います。 表記揺れの処理は、 カテゴリ変数の前処理の前段階です。
入院時に退院時歩行自立を予測する研究です。 しかし、退院後 6 か月の外来 FIM が表に入っています。 この列を説明変数に入れると、 予測時点では得られない情報を使うことになります。
入院時、退院時、追跡時を列名だけで判断しないことが重要です。 データ辞書に収集時点を入れることで、 未来情報の混入を避けやすくなります。
// 05 · THEORY実務で使う考え方
1. Tidy data の原則
表形式データを扱う基本に、Tidy data があります。 1 行は 1 観測、1 列は 1 変数、1 セルは 1 値です。 この形に近いほど、集計や Pipeline 化が容易になります[1]。
1 row = 1 observation
1 col = 1 variable
1 cell = 1 value
例:
patient_id | timepoint | fim_motor | fim_cognition | facility_id
0001 | admission | 48 | 23 | A
0001 | discharge | 72 | 28 | A
ただし、リハ研究では縦持ちと横持ちの両方を使います。 予測モデルでは、入院時変数だけを横持ちにすることもあります。 大切なのは、観測単位を明確にすることです。
2. データ辞書は研究計画の一部
データ辞書は、列名の説明表ではありません。 変数の定義、単位、収集時点、欠測理由、 予測時点で利用可能かを整理する表です。 これは 説明変数と目的変数を決める作業と直結します。
column_name : fim_motor_admission
meaning : 入院時 FIM 運動項目合計
unit : points
range : 13-91
collection_time : admission
missing_reason : not assessed / entry error / unknown
available_at_model : yes
source : electronic chart
notes : PT/OT assessment record
「FIM_motor」という列名だけでは不十分です。 入院時なのか、退院時なのかで意味が変わります。 退院時 FIM を入院時予測に使うと、 モデルは未来の情報を見ています。
3. データの型は 3 種に分ける
前処理では、データ型だけを見ると不十分です。 数値型やカテゴリ型に加えて、 観測単位、時点、ソースを確認します。
type 1: observation unit
patient-level / admission-level / assessment-level
type 2: timepoint
baseline / discharge / follow-up / after outcome
type 3: source
chart / sensor / questionnaire / claims / manual entry
同じ患者の複数評価がある場合、 行単位でランダム分割すると情報が漏れます。 この場合は患者 ID を group として扱います。 前処理のリーケージ防止でも重要な視点です。
4. 第2部の総整理
第2部では、欠測、外れ値、カテゴリ変数、標準化、 特徴量生成、特徴量選択、リーケージ防止を扱いました。 これらは順番に行う作業に見えますが、 実際には相互に関係します。 たとえば、欠測フラグは特徴量になります。 外れ値処理は標準化の選択に影響します。 特徴量選択は CV 内で行わないと、 test の情報を使った選び直しになります。
そのため、前処理はノートブック上の手作業で終わらせず、 Pipeline として保存できる形にします。 scikit-learn の Pipeline は、この目的に向いた実装です[6]。 臨床的な変数選択やサンプルサイズ設計では、 Harrell や Steyerberg の考え方も参考になります[3][4]。 特徴量設計の実践的な整理には、Kuhn と Johnson の書籍が有用です[2]。
// 06 · IMPLEMENTATIONPython 実装例
以下は教育用の仮想データを想定した例です。 実データを扱う場合は、個人情報保護、倫理審査、施設ルール、 データ利用規約を確認したうえで解析環境を整えます。 個人を特定できる情報は、解析用ファイルに含めない設計にします。
# 02·07 表形式データの実践Tips
# 教育用の仮想データ: example_rehab_dataset.csv
# 実データでは個人情報保護・倫理審査・施設ルールを確認します。
import json, random, sys
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GroupKFold, StratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
df = pd.read_csv("example_rehab_dataset.csv")
# 1. データ受領後 30 分チェック
print("shape:", df.shape)
print("dtypes:")
print(df.dtypes)
print("missing rate:")
print(df.isna().mean().sort_values(ascending=False).head(20))
print("summary:")
print(df.describe(include="all").T)
# 2. 患者 ID とユニーク値
if "patient_id" in df.columns:
n_dup = df.loc[df.duplicated("patient_id", keep=False), "patient_id"].nunique()
print("duplicated patient_id:", n_dup)
for col in df.columns:
n_unique = df[col].nunique(dropna=False)
if n_unique <= 20:
print(f"\n{col}: {n_unique} unique")
print(df[col].value_counts(dropna=False).head(20))
# 3. 施設別分布
if "facility_id" in df.columns:
facility_summary = df.groupby("facility_id").agg({
"age": ["count", "mean", "std"],
"fim_motor_admission": ["mean", "std"],
"walking_independent_discharge": ["mean", "count"],
})
print(facility_summary)
# 4. 予測時点で使えない列を除外
outcome_col = "walking_independent_discharge"
time_leak_cols = [
"fim_motor_discharge",
"fim_total_discharge",
"fim_6month_followup",
"discharge_destination",
]
leak_cols = [c for c in time_leak_cols if c in df.columns]
X = df.drop(columns=[outcome_col, "patient_id"] + leak_cols, errors="ignore")
y = df[outcome_col].astype(int)
numeric_cols = X.select_dtypes(include=["int64", "float64"]).columns.tolist()
categorical_cols = X.select_dtypes(include=["object", "category", "bool"]).columns.tolist()
numeric_pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
])
categorical_pipe = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
])
preprocess = ColumnTransformer([
("num", numeric_pipe, numeric_cols),
("cat", categorical_pipe, categorical_cols),
])
model = LogisticRegression(
penalty="l1", solver="liblinear", C=0.5,
max_iter=2000, random_state=SEED,
)
pipe = Pipeline([
("preprocess", preprocess),
("model", model),
])
# 5. CV 内で前処理も fit される
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=SEED)
scores = cross_val_score(pipe, X, y, cv=cv, scoring="roc_auc")
print("AUC mean:", scores.mean())
print("AUC sd:", scores.std())
# 6. 同一患者の複数評価がある場合
if "patient_id" in df.columns and df["patient_id"].duplicated().any():
groups = df["patient_id"]
group_cv = GroupKFold(n_splits=5)
group_scores = cross_val_score(pipe, X, y, cv=group_cv, groups=groups, scoring="roc_auc")
print("Group AUC mean:", group_scores.mean())
# 7. データ辞書ひな形
schema = []
for col in df.columns:
schema.append({
"column_name": col,
"dtype": str(df[col].dtype),
"missing_rate": float(df[col].isna().mean()),
"collection_time": "TODO: admission/discharge/follow-up",
"unit": "TODO",
"definition": "TODO",
"available_at_model": "TODO: yes/no",
})
Path("data_dictionary_template.json").write_text(
json.dumps(schema, ensure_ascii=False, indent=2), encoding="utf-8"
)
Path("python_version.txt").write_text(sys.version, encoding="utf-8")
# pip freeze > requirements.txt で環境も記録します。
この例では、欠測補完、標準化、One-Hot、L1 正則化を 1 つの Pipeline に入れています。 CV の各 fold では、前処理も学習側だけで fit されます。 これは リーケージを防ぐ前処理の基本です。
pandas で全データに対して補完や標準化をしてから分割すると、 validation や test の情報が前処理に混ざります。 そのため、探索的確認とモデル用前処理は分けて扱います。
// 07 · MYTHSよくある誤解
- 誤解: Excel で目視確認すれば十分
- 数千行を超えると、目視では欠測や表記揺れを見落とします。 pandas で型、欠測率、ユニーク値、範囲を確認します。
- 誤解: 列名さえあれば意味は明らか
- FIM_motor だけでは、入院時か退院時か分かりません。 単位、定義、収集時点をデータ辞書で管理します。
- 誤解: データが整っていれば前処理は短くできる
- 医療データは整って見えても、時点や施設差が混ざることがあります。 形式の整備と意味の確認は別の作業です。
- 誤解: Tidy data を意識しなくても結果は同じ
- 解析結果だけなら動く場合があります。 しかし、再利用性、監査性、Pipeline 化で差が出ます。
// 08 · WRITINGMethods に書くこと
表形式データの前処理は、Methods で省略されがちです。 しかし、予測モデル研究では再現性に関わります。 TRIPOD+AI でも、データ、予測因子、欠測、 モデル開発過程の透明性が重視されます[5]。
Methods に含めたい項目
- 01解析対象の観測単位を明記します。
- 02予測時点を明記します。
- 03説明変数に入れた時点を明記します。
- 04欠測率と欠測処理の方法を書きます。
- 05カテゴリ変数のエンコード方法を書きます。
- 06標準化や変換を行った列を示します。
- 07特徴量選択を CV 内で行ったかを示します。
- 08ソフトウェアとバージョンを記録します。
書き方の例
All preprocessing steps were implemented within a scikit-learn Pipeline.
Continuous variables were imputed using the median and standardized.
Categorical variables were imputed using the most frequent category
and encoded using one-hot encoding.
The preprocessing parameters were fitted only on the training folds
within cross-validation.
論文では「前処理した」とだけ書くと、 どのデータで fit したかが分かりません。 train fold 内で fit したか、 外部検証データには transform のみ行ったかを記載します。
査読では、未来情報の混入、同一患者の重複、 施設差、欠測処理の妥当性が問われやすいです。 特に、退院時・追跡時の情報が入院時予測に入っていないかは、 研究計画段階で確認します。
// 09 · CHECKLIST30 分チェックリスト
- 01行数、列数、患者数を確認しましたか。
- 02各列の型とユニーク値を確認しましたか。
- 03欠測率を列ごとに一覧化しましたか。
- 04基本統計と臨床的範囲を確認しましたか。
- 05患者 ID の重複と複数評価を確認しましたか。
- 06入院時、退院時、追跡時の列を分けましたか。
- 07施設別・時期別の分布差を確認しましたか。
- 08データ辞書を作り、単位と定義を記録しましたか。
このチェックリストは、モデル作成前の点検です。 点検で見つかった問題は、 欠測値処理、 外れ値・カテゴリ変数・標準化、 特徴量エンジニアリング、 特徴量選択で個別に扱います。
// 10 · QUIZ確認クイズ
-
Q1入院時予測モデルで、退院後 6 か月 FIM を説明変数に入れると何が問題ですか。
- 欠測がなくなる
- 未来情報が混入する
- 標準化が不要になる
- 施設差が消える
SHOW ANSWER
B. 予測時点で取得できない情報を使うため、リーケージになります。 -
Q2データ辞書に含める項目として、特に重要なものはどれですか。
- 列名、単位、定義、収集時点
- ファイル名だけ
- モデル名だけ
- 論文タイトルだけ
SHOW ANSWER
A. 列名だけでは、時点や単位が分からないことがあります。 -
Q3同一患者の複数評価がある場合、評価で注意する分割はどれですか。
- 行単位の完全ランダム分割
- 患者 ID を考慮した GroupKFold
- 欠測列だけを削除する分割
- 目的変数で並べ替える分割
SHOW ANSWER
B. 同一患者の情報が train と test に分かれると、評価が甘くなります。 -
Q4Pipeline に入れる目的として正しいものはどれですか。
- 前処理の fit を fold 内に閉じ込める
- 外れ値を自動で医学的に判定する
- 欠測の理由を自動で確定する
- 研究倫理の確認を省略する
SHOW ANSWER
A. Pipeline は前処理とモデルを一体化し、CV 内 fit を保ちやすくします。
// REF参考文献
- Wickham H. Tidy data. Journal of Statistical Software 2014;59(10):1-23.
- Kuhn M, Johnson K. Feature Engineering and Selection: A Practical Approach for Predictive Models. CRC Press, 2019.
- Harrell FE Jr. Regression Modeling Strategies. 2nd ed. Springer, 2015.
- Steyerberg EW. Clinical Prediction Models. 2nd ed. Springer, 2019.
- Collins GS, Moons KGM, Dhiman P, et al. TRIPOD+AI statement. BMJ 2024;385:e078378.
- Pedregosa F, Varoquaux G, Gramfort A, et al. Scikit-learn: Machine learning in Python. Journal of Machine Learning Research 2011;12:2825-2830.