傾向スコアマッチングにおける「1対1マッチング」と「1対Nマッチング」の違い

# はじめに
傾向スコアマッチングにおける、「1対1マッチング」と「1対Nマッチング」の違いを考察していきます。
この記事は、こちらの記事の続きになります↓

https://qiita.com/UKI_datascience/items/72280588d89665a7282e

---

# 傾向スコアマッチング
因果推論の手法の一つで、「**似たような条件のデータ同士**」をペアにすることで、2つのグループをできるだけ公平に比べることができます。

---

### マッチングの種類
傾向スコアマッチングでは、対象群と対照群の間で傾向スコア（介入を受ける確率がどの程度か）を基準にしてマッチングを行います。

そのうえで、各処置群（介入を受けたグループ）の個体に対して、傾向スコアが最も近い（類似した）対照群（介入を受けていないグループ）の個体を**1つだけ**選ぶ方法を「**1対1マッチング**」、2つ以上選ぶ方法を「**1対Nマッチング**」と言われます。

前回の記事では「**1対1マッチング**」を使用したので、今回の記事では「**1対Nマッチング**」を使用して、違いを考察していこうと思います。

---

### 使用コード
今回使用したコードは、前回の記事で紹介したコードとほとんど同じなので、変更した部分だけ紹介します。

**前回のコード（変更前）**
```python
from scipy.spatial import distance

# 傾向スコアを float 型に変換
df['propensity_score'] = df['propensity_score'].astype(float)

# 処置群と対照群のデータ
treated = df[df['smoker'] == 1].copy()
control = df[df['smoker'] == 0].copy()

# 最近傍マッチング
matched_indices = []
for i, row in treated.iterrows():
    # 適切な形式に変換
    treated_score = np.array([[row['propensity_score']]])  # 2D array にする
    control_scores = control[['propensity_score']].to_numpy()  # 2D array にする
    
    # 最近傍マッチングのインデックスを取得
    min_idx = distance.cdist(treated_score, control_scores).argmin()
    
    # インデックスをリストに追加
    matched_indices.append(control.index[min_idx])

# マッチング後のデータ
matched_control = df.loc[matched_indices].copy()
matched_treated = treated.copy()

# マッチング後のデータセット作成
matched_df = pd.concat([matched_treated, matched_control])

# マッチング後の傾向スコア分布確認
sns.histplot(matched_df[matched_df['smoker'] == 1]['propensity_score'], color='red', alpha=0.5, label='Smoker', kde=True)
sns.histplot(matched_df[matched_df['smoker'] == 0]['propensity_score'], color='blue', alpha=0.5, label='Non-Smoker', kde=True)
plt.legend()
plt.title("Distribution of Propensity Scores After Matching")
plt.show()
```

**今回のコード（変更後）**
```python
from scipy.spatial import distance

N = 3

# 傾向スコアを float 型に変換
df['propensity_score'] = df['propensity_score'].astype(float)

# 処置群と対照群のデータ
treated = df[df['smoker'] == 1].copy()
control = df[df['smoker'] == 0].copy()

# 最近傍マッチング（1対N）
matched_control_indices = []  # マッチした対照群のインデックス
matched_treated_indices = []  # 対応する処置群のインデックス

# 最近傍マッチング
matched_indices = []
for i, row in treated.iterrows():
    # 適切な形式に変換
    treated_score = np.array([[row['propensity_score']]])  # 2D array にする
    control_scores = control[['propensity_score']].to_numpy()  # 2D array にする

    distances = distance.cdist(treated_score, control_scores).flatten()
    nearest_indices = distances.argsort()[:N]
    
    matched_control_indices.extend(control.index[nearest_indices])
    matched_treated_indices.extend([i] * N)
    
# マッチング後のデータ
matched_treated = treated.loc[matched_treated_indices].reset_index(drop=True)
matched_control = control.loc[matched_control_indices].reset_index(drop=True)

# マッチング後のデータセット作成
matched_df = pd.concat([matched_treated, matched_control]).reset_index(drop=True)

# マッチング後の傾向スコア分布確認
sns.histplot(matched_df[matched_df['smoker'] == 1]['propensity_score'], color='red', alpha=0.5, label='Smoker', kde=True)
sns.histplot(matched_df[matched_df['smoker'] == 0]['propensity_score'], color='blue', alpha=0.5, label='Non-Smoker', kde=True)
plt.legend()
plt.title("Distribution of Propensity Scores After Matching")
plt.show()
```

### 分析結果
#### 傾向スコアの分布
**1対1マッチング**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457746/15aaec5b-f0d5-433b-8bbd-f7daa7afaf1b.png)
**1対Nマッチング**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457746/e349efe7-5b3a-40ee-8f22-073d0af23c69.png)

「1対1」よりも「1対N」の方がサンプルサイズが増えるため、マッチング後の分布の形がよりなめらかになるものの、分布のばらつきはやや広がっている。

---

#### 各説明変数のバランス
**1対1マッチング**
| Matching Stage      | Smoker | Age    | BMI    | Children |
|--------------------|--------|--------|--------|----------|
| **Before Matching** | 0 (Non-Smoker) | 39.39  | 30.65  | 1.09  |
|                    | 1 (Smoker)     | 38.51  | 30.71  | 1.11  |
| **After Matching**  | 0 (Non-Smoker) | 38.43  | 31.51  | 1.03  |
|                    | 1 (Smoker)     | 38.51  | 30.71  | 1.11  |

**1対Nマッチング**
| Matching Stage      | Smoker | Age    | BMI    | Children |
|--------------------|--------|--------|--------|----------|
| **Before Matching** | 0 (Non-Smoker) | 39.39  | 30.65  | 1.09  |
|                    | 1 (Smoker)     | 38.51  | 30.71  | 1.11  |
| **After Matching**  | 0 (Non-Smoker) | 38.39  | 30.73  | 1.08  |
|                    | 1 (Smoker)     | 38.51  | 30.71  | 1.11  |

**年齢**（```age```）
- マッチング前：0.88歳
- 1対1：0.08歳
- 1対N：0.12歳
「1対N」の方がやや差が大きいが、十分にマッチしていると考えられる。

**BMI**（**bmi**）
- マッチング前：-0.06
- 1対1：0.80
- 1対N：0.02
1対1では、バランスが悪化してしまったが、1対Nでは改善された。

**子どもの数**（children）
- マッチング前：-0.02
- 1対1：-0.08
- 1対N：-0.03
ともに若干バランスが悪化しているが、「1対N」の方がバランスが維持されている。

**総合**
年齢においては、「1対1」の方が優れていたが、全体的に見ると、「1対N」の方がバランスの取れた形になっている。

---

#### 医療費の平均比較

**1対1マッチング**
```
処置群（喫煙者）の医療費平均: 32050.23
対照群（非喫煙者）の医療費平均: 7670.83
t値: 31.79, p値: 0.0000
```

**1対Nマッチング**
```
処置群（喫煙者）の医療費平均: 32050.23
対照群（非喫煙者）の医療費平均: 7863.58
t値: 54.05, p値: 0.0000
```

最終的な結果には大きな変化は見られず、どちらの場合でも有意な差が確認された。

---

# まとめ

### **「1対1マッチング」と「1対Nマッチング」の違い**
| 比較項目            | **1対1マッチング** | **1対Nマッチング** |
|--------------------|------------------|------------------|
| **マッチング方法** | 喫煙者1人に対して、最も類似した非喫煙者1人を選択 | 喫煙者1人に対して、類似した複数（N人）の非喫煙者を選択 |
| **サンプルサイズ** | 限られる（喫煙者と非喫煙者のペア数が等しい） | 非喫煙者のデータをより多く活用でき、サンプルサイズが増える |
| **バランスの取りやすさ** | 厳密なマッチングが可能（年齢など特定の変数のバランスを最適化しやすい） | 全体のバランスが良くなりやすく、サンプルの多様性が増す |
| **統計的パワー** | サンプルが少ないため低くなりがち | サンプルが多いため高くなる |
| **外的妥当性** | 選ばれたサンプルに依存しやすく、一般化しにくい | サンプルサイズが大きいため、より一般化しやすい |
| **計算コスト** | 比較的低い | より多くのマッチングを考慮するため計算コストが高い |

---

### **どのように使い分ければよいか？**

| **目的** | **推奨マッチング方法** | **理由** |
|----------|----------------|----------------|
| **厳密な比較をしたい（交絡因子の影響を最小化）** | **1対1マッチング** | 交絡因子をより厳密にコントロールできるため |
| **統計的パワーを高めたい（サンプルサイズを増やす）** | **1対Nマッチング** | 多くの非喫煙者データを活用できるため |
| **全体のバランスを取りたい（外的妥当性を確保）** | **1対Nマッチング** | より多くのサンプルを利用し、母集団に近いデータを得られるため |
| **少数のデータでも分析可能な方法が必要** | **1対1マッチング** | サンプルが少ない場合、過剰なN:1マッチングは難しくなるため |
| **解釈しやすい結果が欲しい** | **1対1マッチング** | 1対1の比較は直感的に理解しやすいため |
| **データを最大限活用したい** | **1対Nマッチング** | すべての非喫煙者データを活用できるため |

---

# おわりに
これらの結果を踏まえて、このような結論になるかと思います。

- **厳密な比較や因果推定を重視するなら「1対1マッチング」**
- **統計的パワーやデータの汎用性を重視するなら「1対Nマッチング」**

どちらにもメリット・デメリットがあるため、目的に応じて適切なマッチング方法を選択することが重要です。