0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

傾向スコアマッチングの非復元抽出

0
Posted at

概要

因果推論における傾向スコアマッチング(非復元抽出)を実装します。
コードの核となる部分は、最近傍法による全データ間の距離をとった後、距離の昇順で逐次ペアマッチングをし、一度マッチさせたデータは探索先から外すようにしています。

コード

ライブラリとサンプルデータ

import pandas as pd
import numpy as np
from scipy import stats
from sklearn.neighbors import NearestNeighbors

# 標準偏差を1、データサイズを50に
std = 1
n = 50

# 探索元サンプルデータ
target = pd.DataFrame(
    data=stats.norm.rvs(loc=0, scale=std, size=n, random_state=0),
    index=range(0, n),
    columns=['col']
)
# 探索先サンプルデータ
population = pd.DataFrame(
    data=stats.norm.rvs(loc=0, scale=std, size=n, random_state=1),
    index=range(0, n),
    columns=['col']
)

ペアマッチング

def matching_data(target, population, caliper, n_neighbors=1):
    """
        最近傍法により近似した値を探索し、見つかったらそのindexと距離をDataFrame化して返す

        Parameters
        ----------
        target : pd.Series
            検索対象データ(インデックス,検索値)
        population : pd.Series
            検索先データ(インデックス,検索値)
        caliper : float
            マッチング時に許容する誤差範囲

        Returns
        -------
        result_arr : pd.DataFrame
            target_index : int
                検索対象インデックス番号
            population_index : int
                検索先インデックス番号
            distance : float
                ヒットしたデータ間の距離
            distance_rank : int
                distanceの昇順順位
    """

    # 最近傍探索
    neigh = NearestNeighbors(n_neighbors=n_neighbors, metric='euclidean')
    neigh.fit(population.to_numpy().reshape(1, -1).T)

    # ユークリッド距離と探索先のインデックス
    distances, indexes = neigh.kneighbors(target.to_numpy().reshape(1, -1).T, 
return_distance=True)
    distances, indexes = distances.reshape(-1), indexes.reshape(-1)

    # ユークリッド距離の昇順順位を取得
    distance_ranks = np.argsort(np.argsort(distances))

    # 結合する配列を初期化
    result_arr = np.empty([0, 4])

    # ユークリッド距離の小さい順から紐づけ
    # 非復元抽出のため一度紐づけたdistances,indexesはskipする
    # 各配列(distance_ranks,distances,indexes)の添え字は同一であるため、distance_ranksの添え字に基づいて処理
    
    for i in range(len(distance_ranks)):
        idx = np.where(i==distance_ranks)[0].item()

        # result_arrの1列目(population_idx)に値が格納されていない場合,かつcaliper以内のdistancesに限定
        if (~np.isin(result_arr[:, 1], indexes[idx]).any()) and (distances[idx].item()<=caliper):
            target_idx = idx
            population_idx = indexes[idx].item()
            distance = distances[idx].item()
            distance_rank = distance_ranks[idx].item()
            new_row = np.array([target_idx, population_idx, distance, distance_rank])

            result_arr = np.vstack([result_arr, new_row])

        else: pass

    return pd.DataFrame(
        data=result_arr,
        columns=['target_index', 'population_index', 'distance', 'distance_rank']
    ).astype({'target_index': int, 'population_index': int, 'distance': float, 'distance_rank': int})

mapping_df = matching_data(target['col'], population['col'], std*0.2) # キャリパーは標準偏差の0.2倍

マッチング結果

result_df = pd.merge(mapping_df, target, how='left', left_on='target_index', right_index=True)
result_df = pd.merge(result_df, population, how='left', left_on='population_index', right_index=True)
result_df.rename(columns={'col_x': 'target_col', 'col_y': 'population_col'}, inplace=True)
result_df
target_index population_index distance distance_rank target_col population_col
0 32 41 0.000157 0 -0.887786 -0.887629
1 22 4 0.000971 1 0.864436 0.865408
2 13 48 0.001516 2 0.121675 0.120159
3 38 13 0.003272 3 -0.387327 -0.384054
4 26 18 0.003545 4 0.045759 0.042214
5 27 40 0.004652 5 -0.187184 -0.191836
6 23 42 0.004993 6 -0.742165 -0.747158
7 18 8 0.005971 7 0.313068 0.319039
8 29 10 0.007251 8 1.469359 1.462108
9 19 33 0.008890 10 -0.854096 -0.845206
10 44 2 0.018520 13 -0.509652 -0.528172
11 12 39 0.018994 14 0.761038 0.742044
12 0 6 0.019241 15 1.764052 1.744812
13 8 26 0.019671 16 -0.103219 -0.122890
14 39 12 0.020114 17 -0.302303 -0.322417
15 7 16 0.021071 19 -0.151357 -0.172428
16 40 3 0.024416 21 -1.048553 -1.072969
17 35 46 0.034567 24 0.156349 0.190915
18 21 49 0.036415 27 0.653619 0.617203
19 45 31 0.041321 28 -0.438074 -0.396754
20 5 27 0.041508 29 -0.977278 -0.935769
21 6 22 0.048498 30 0.950088 0.901591
22 37 21 0.057656 31 1.202380 1.144724
23 14 23 0.058631 32 0.443863 0.502494
24 33 11 0.079344 36 -1.980796 -2.060141
25 46 36 0.135485 41 -1.252795 -1.117310
26 3 47 0.140638 42 2.240893 2.100255
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?