研究者: 傾向スコア逆確率重み付け(IPTW)解析を行っているのですが、査読者や指導医から「重みの分布を箱ひげ図(Boxplot)で確認したか?」と聞かれました。なぜ箱ひげ図が必要で、どう描いて外れ値を処置すればいいのでしょうか……?
統計ER: IPTWはたった1〜2例の「極端に大きな重み」によって結果全体が歪むリスクがあります!箱ひげ図を描くことで重みの異常(外れ値)を一目で発見できますよ。
ggplot2を使えばエラーなく確実で美しいプロットが出力でき、summary(W)で数値を確認してからWeightIt::trim()でトリミング処置を行うのが鉄板のフローです。さっそく手順を解説しましょう!
傾向スコアを用いた逆確率重み付け(IPTW: Inverse Probability of Treatment Weighting)解析を実施する際、指導医や論文の査読者(Reviewer)から以下のような指摘を受けたことはないだろうか。
- 「算出した重み(Weights)の分布を箱ひげ図(Boxplot)で確認したか?」
- 「極端に大きな重み(Extreme Weights / 外れ値)を持つ症例が存在しないか?」
- 「重み付けによって、調整前の背景因子(年齢や検査値など)の分布はどのように変化したか?」
IPTW解析は交絡調整の強力な手法だが、裏側で「重みの巨大化」が起きていると、たった1〜2例の極端な症例によってオッズ比やハザード比が大きく歪んでしまう脆さを持っている。
この問題を事前に発見し、解析の健全性を担保するために不可欠なのが「箱ひげ図によるビジュアル確認」である。
本記事では、WeightIt や ggplot2 パッケージを活用し、重みの外れ値チェックから連続共変量のバランス確認、外れ値が見つかった際のトリミング(Trimming)処理までをRでスマートに完結させる手順を解説する。
IPTW解析で箱ひげ図を描くべき「2つの決定的な理由」
IPTW解析において箱ひげ図を使用する目的は、大きく分けて以下の2点に集約される。
1. 算出した「重み(Weights)」の分布と極端値(Extreme Weights)の確認
IPTWにおける重み $w_i$ は、傾向スコア $e_i$ の逆数($1 / e_i$ や $1 / (1 – e_i)$)として計算される。
もし傾向スコアが 0 や 1 に極めて近い症例(例: $e_i = 0.005$)が存在すると、その重みは $1 / 0.005 = 200$ と巨大化してしまう。このような「巨大な重みを持つ症例」が存在すると、その1例のデータが解析結果全体を支配してしまい、推定量の分散(標準誤差)が飛躍的に増大する。
箱ひげ図を描くことで、ひげの先から大きく飛び出した点(外れ値)の有無や、重みの中央値・分布の広がりが一目でチェックできる。
2. 調整前後(Unadjusted vs Adjusted)での「共変量バランス」の視覚的確認
一般に交絡バランスの確認には「標準化平均差(SMD)」を並べたラブプロット(Love Plot)が使われる。しかし、SMDはあくまで「平均値の差」しか見ていない。
連続変数(例: 年齢、BMI、CRP値など)について重み付け前後の箱ひげ図を描くことで、「平均値だけでなく、中央値や四分位範囲(IQR)、データの広がり全体が群間で綺麗に揃ったか」をより厳密に実証することが可能になる。
確実かつ美麗!ggplot2 で箱ひげ図を描く実践フロー
Rでエラーを起こさず、かつ論文や報告書向けに美しくカスタマイズできる手法が、ggplot2 の geom_boxplot() を使用する方法である。
あらかじめ必要なパッケージをインストール・読み込んでおこう。
library(tidyverse)
library(WeightIt)
library(patchwork) # グラフを左右に並べる用
Step 1: サンプルデータの作成とIPTW重みの計算
まずは動作確認用のサンプルデータを作成し、WeightIt で重みを計算する。
# サンプルデータの作成
set.seed(123)
n <- 300
df_raw <- tibble(
treat = sample(c(0, 1), n, replace = TRUE, prob = c(0.6, 0.4)),
age = rnorm(n, mean = 65, sd = 10),
bmi = rnorm(n, mean = 24, sd = 4)
)
# IPTW重みの計算
W <- weightit(treat ~ age + bmi, data = df_raw, method = "glm", estimand = "ATE")
# 重みと治療変数を元のデータフレームに結合
df_plot <- df_raw %>%
mutate(
weight = W$weights,
treat = factor(treat) # グラフ描画用にFactor化
)
Step 2: 重み(Weights)の箱ひげ図チェック
作成したデータを用いて、治療群ごとの重みの分布を箱ひげ図として出力する。データの実際の散らばりも可視化するため、ドットプロット(geom_jitter)も重ねて描写する。
# 1. 重み自体の分布を箱ひげ図で確認
ggplot(df_plot, aes(x = treat, y = weight, fill = treat)) +
geom_boxplot(alpha = 0.7, outlier.color = "red", outlier.size = 2) +
geom_jitter(width = 0.1, alpha = 0.2) +
labs(
title = "Distribution of IPTW Weights by Treatment Group",
x = "Treatment Group",
y = "Inverse Probability Weight"
) +
theme_minimal()
ひげの先から赤色で示された「外れ値」がどれほど突出しているかを視覚的に確認する。

Step 3: summary(W) による重みの数値チェック
トリミング(剪定)を行う前に、客観的な数値基準を確認する。WeightIt オブジェクトに対して summary() 関数を実行すると、重みの最小値・中央値・最大値や、上位パーセンタイルの数値を一括で確認できる。
# 重みの統計量を数値で詳細確認
summary(W)
確認のポイント:
出力内の Summary of weights: セクションにある Max(最大値)を確認する。ここが「20」や「50」など極端に大きい数値になっている場合、たった1つの症例が解析結果全体を歪めているリスクが高いと判断できる。
Step 4: 外れ値が存在する場合のトリミング処理(WeightIt::trim)
summary(W) で巨大な最大値を確認した場合、重みのトリミング(Trimming / Truncation) を行う。WeightIt の trim() 関数を使い、上位1%(99パーセンタイル)を超える巨大な重みを上限値へクリッピングする。
# 重みの上位 1%(99パーセンタイル)でトリミングを実行
W_trimmed <- trim(W, at = 0.99)
# トリミング後のデータを再結合
df_plot_trimmed <- df_raw %>%
mutate(
weight = W_trimmed$weights,
treat = factor(treat)
)
# トリミング後の重みの箱ひげ図を再描画して確認
ggplot(df_plot_trimmed, aes(x = treat, y = weight, fill = treat)) +
geom_boxplot(alpha = 0.7, outlier.color = "red") +
labs(title = "Trimmed IPTW Weights", x = "Treatment Group", y = "Weights") +
theme_minimal()
トリミングを実行することで巨大すぎた重みが抑制され、箱ひげ図上の極端な外れ値が解消される。
Step 5: 連続共変量(年齢など)の調整前後バランスを箱ひげ図で比較する
トリミングを終えた綺麗な重み(weight)を使って、特定の共変量(例: age)の分布が重み付けによってどう揃ったかを確認する。ggplot2 の箱ひげ図では weight = weight 引数を渡すことで調整後の分布を描写できる。
# 調整前(Unadjusted)の箱ひげ図
p_unadj <- ggplot(df_plot_trimmed, aes(x = treat, y = age, fill = treat)) +
geom_boxplot(alpha = 0.6) +
labs(title = "Unadjusted (Age)", x = "Group", y = "Age") +
theme_minimal() + theme(legend.position = "none")
# 調整後(IPTW Adjusted)の箱ひげ図(weight引数を指定)
p_adj <- ggplot(df_plot_trimmed, aes(x = treat, y = age, fill = treat, weight = weight)) +
geom_boxplot(alpha = 0.6) +
labs(title = "IPTW Adjusted (Age)", x = "Group", y = "Age") +
theme_minimal() + theme(legend.position = "none")
# patchwork パッケージで左右に並べて表示
p_unadj + p_adj
「調整前(左)の群間差」と「調整後(右)に分布の中央値や広がりが綺麗に重なり合った様子」を左右に並べることで、背景因子の調整プロセスを視覚的・説得力をもって論文の付録等でアピールできる。
【まとめ】コピペで動く!重み確認からトリミング・箱ひげ図描画の一括コード
本記事で解説した「重み算出 ➔ 箱ひげ図確認 ➔ 数値確認 ➔ トリミング ➔ 共変量バランス確認」の一連のパイプラインコードを以下にまとめた。自身の手元のデータセット名・変数名に置き換えて活用してほしい。
# === IPTW解析における「箱ひげ図」確認・外れ値処理テンプレート ===
library(tidyverse)
library(WeightIt)
library(patchwork)
# 1. IPTW重みの計算
W <- weightit(
treat ~ age + bmi, # 治療変数 ~ 共変量
data = df_raw,
method = "glm",
estimand = "ATE"
)
# 2. 【可視化】重み(Weights)の箱ひげ図チェック
df_plot <- df_raw %>% mutate(weight = W$weights, treat = factor(treat))
ggplot(df_plot, aes(x = treat, y = weight, fill = treat)) +
geom_boxplot(alpha = 0.7, outlier.color = "red") +
geom_jitter(width = 0.1, alpha = 0.2) +
theme_minimal()
# 3. 【数値確認】summary(W) で最大値や上位%点のチェック
summary(W)
# 4. 【外れ値処置】上位 1% でトリミング処理
W_clean <- trim(W, at = 0.99)
df_clean <- df_raw %>% mutate(weight = W_clean$weights, treat = factor(treat))
# 5. 【効果確認】連続共変量(例: age)の調整前後バランスを箱ひげ図で左右比較
p1 <- ggplot(df_clean, aes(x = treat, y = age, fill = treat)) +
geom_boxplot(alpha = 0.6) + labs(title = "Unadjusted") + theme_minimal()
p2 <- ggplot(df_clean, aes(x = treat, y = age, fill = treat, weight = weight)) +
geom_boxplot(alpha = 0.6) + labs(title = "IPTW Adjusted") + theme_minimal()
p1 + p2 # 左右並べてプロット出力
IPTW解析を行う際は、単に P 値やハザード比を算出するだけでなく、事前に ggplot2 の箱ひげ図と summary(W) の数値で重みの分布と外れ値の有無を確認する習慣をつけておこう。
確実な前処理とチェックを行うことで、査読での突っ込みを防ぎ、信頼性の高い臨床研究論文を完成させることができる。





コメント