HOWTO · Pandas

Pandasで層化サンプリングを実行する方法

PandasのDataFrameから、グループごとに同数または母集団の比率に応じた層化サンプルを抽出する方法を学びます。

このページの内容

Pandasでは、層を表す列で定義したグループごとにサンプリングできます。グループ間の比較を重視する場合は各グループから同じ数を選び、母集団の構成比を標本に反映したい場合は、グループの大きさに応じて合計数を配分します。例を再現できるようにするには、乱数シードを固定します。

統計における層化サンプリング

層化サンプリングでは、母集団を互いに重ならない「層」と呼ばれるグループに分け、各グループから観測値を選びます。層には、学年、地域、年齢層など、分析の問いに関係する特徴を使えます。グループごとにサンプリングすれば、重要なグループを標本に含められます。ただし、それだけで選択バイアスがなくなったり、標本が母集団を代表するようになったりするわけではありません。どのような対象をサンプリング枠に含め、どう選ぶかも引き続き重要です。

推定値に関係する特徴がグループごとに異なるとき、研究者は層化サンプリングを使います。また、データの実務では、学習用データとテスト用データの両方に特定のカテゴリが含まれるように分割する際にも使われます。どの列で層を定義するか、各グループから同数を選ぶか、母集団に占める割合に応じた数を選ぶかを決めましょう。この二つの方法が答える問いは異なります。同数の抽出はグループ間の比較を重視し、比例配分はグループ同士の相対的な大きさを保ちます。

Pandasで層化サンプリングを行う

以下の例では、3つの学年グループに分かれた10行の生徒DataFrameを作成します。まず各学年から2人ずつ選ぶ均等な標本を作り、次に合計6人を比例配分で抽出します。比例配分には最大剰余方式を使います。各グループの正確な割当数を切り捨てた後、残りの枠を小数部分が大きいグループから順に割り当てます。この例のグループ人数は5人、3人、2人なので、6人の配分は3人、2人、1人になります。

import pandas as pd

students = {
    "Name": [
        "sanay", "shivesh", "rutwik", "preet", "yash",
        "mann", "pritesh", "hritesh", "raj", "tarun",
    ],
    "ID": ["001", "002", "003", "004", "005", "006", "007", "008", "009", "010"],
    "Grade": ["A", "A", "C", "B", "B", "B", "C", "A", "A", "A"],
    "Category": [2, 3, 1, 3, 2, 3, 3, 1, 2, 1],
}
df = pd.DataFrame(students)

# Take the same number from each grade.
equal_sample = df.groupby("Grade", group_keys=False).sample(n=2, random_state=7)
print("Equal-sized counts:")
print(equal_sample["Grade"].value_counts().sort_index().to_dict())

# Allocate a fixed total in proportion to each grade's population size.
target = 6
group_sizes = df["Grade"].value_counts().sort_index()
quotas = group_sizes / len(df) * target
allocation = quotas.astype(int)
places_left = target - int(allocation.sum())
remainders = (quotas - allocation).sort_values(ascending=False)
for grade in remainders.index[:places_left]:
    allocation.loc[grade] += 1

proportional_sample = pd.concat(
    [
        group.sample(n=int(allocation.loc[grade]), random_state=7)
        for grade, group in df.groupby("Grade", sort=True)
    ],
    ignore_index=True,
)
print("Proportional counts:")
print(proportional_sample["Grade"].value_counts().sort_index().to_dict())

DataFrame.groupby() はサンプリング対象のグループを作ります。詳しくは、ローカライズされたPandasのgroupby()リファレンスを参照してください。

各グループから同じ数を抽出するには、sample(n=2) で各学年から2行を選びます。そのため、最も人数の少ないグループにも少なくとも2行が必要です。満たさない場合は、グループごとの抽出数を減らすか、層の分け方を見直してください。比例配分では、target が希望する合計数です。配分時に丸めを処理するため、各グループの抽出数を合計するとその数になります。小数部分が同じグループがある場合、丸め規則によって残りの枠が割り当てられるグループは異なることがあります。分析を再現できるよう、規則を明記してください。

どちらの呼び出しも random_state=7 を使うため、入力データとPandasの環境が同じなら抽出結果を再現できます。別のランダムな標本が必要な場合は、シードを変更してください。層化によって制御できるのは各グループから抽出する観測値の数です。元データやサンプリング方法に偏りがあれば、各グループ内の観測値までランダムになるわけではありません。標本を再現する必要がある場合は、層を定義する列、配分規則、目標数、シードを記録してください。