Σ
SDCalc
上級上級·15 min

標準偏差のブートストラップ法

標準偏差推定のためのブートストラップリサンプリングをマスター。パーセンタイル法、BCa法、パラメトリック・ブートストラップ法をPython実装と計算例付きで解説。

ブートストラップ:コンピュータ時代の統計革命

ブートストラップリサンプリングは、観測データから繰り返しリサンプリングすることで、任意の統計量の標本分布を推定する強力な統計手法です。1979年にブラッドレイ・エフロンが提唱し、数学的な公式や分布の仮定に頼らずに複雑な統計量の分析を可能にすることで、統計的推測に革命をもたらしました。

ブートストラップの背後にある洞察は美しくシンプルです。標本は母集団の最良の推定値です。標本から(復元抽出で)リサンプリングすることで、母集団から繰り返しサンプリングした場合に何が起こるかをシミュレートします。このアプローチは標準偏差にとって特に価値があります。従来の信頼区間の公式は正規性を仮定しますが、実際にはこの仮定が成り立たないことが多いからです。

ブートストラップは現代のデータサイエンスで不可欠になっています。任意の統計量(中央値、相関、回帰係数、ニューラルネットワークの重みなど)に対して機能し、データの基礎となる分布について仮定を置きません。

なぜ標準偏差にブートストラップを使うのか?

標準偏差の従来の信頼区間は、データが正規分布に従うことを仮定しています。この仮定が破られると(よくあることです)、これらの区間は大幅に不正確になる可能性があります。ブートストラップは分布に依存しない代替手段を提供します。

従来の方法が失敗する場合

標準偏差のカイ2乗ベースの信頼区間は正規性を仮定します。歪んだデータ(所得、反応時間、生存時間データ)では、期待される5%ではなく、20〜30%の確率で真のパラメータを外す区間を生成する可能性があります。

標準偏差にブートストラップを使う主な利点:

  • 分布の仮定不要: 正規分布、歪んだ分布、裾の重い分布のいずれでも同等に機能
  • 小標本での性能: n < 30ではパラメトリック法よりも精度が高いことが多い
  • 複雑な統計量にも対応: トリム標準偏差、MAD、カスタム変動指標にも同じアプローチが使える
  • 視覚的な洞察: ブートストラップ分布が数字だけでなく、何が起こっているかを見せてくれる

ブートストラップの手順

ブートストラップのアルゴリズムは驚くほど簡単です。n個の観測値からなる元の標本から:

1

ブートストラップ標本を抽出する

元のデータから復元抽出でn個の観測値をランダムに選びます。一部の値は複数回出現し、出現しないものもあります。
2

統計量を計算する

このブートストラップ標本の標準偏差を計算します。これが1つのブートストラップ複製です。
3

何千回も繰り返す

ステップ1〜2を何千回も繰り返します(通常B = 10,000回)。各繰り返しで1つのブートストラップSDが得られます。
4

分布を分析する

B個のブートストラップSDの集合が標本分布の近似になります。信頼区間や仮説検定に使用します。

なぜ復元抽出なのか?

復元抽出が重要です。これにより、構成が異なる標本が作られ、母集団から異なる標本を取った場合に見られるばらつきを模倣します。非復元抽出では、すべての標本が元の標本と同一になってしまいます。

何回のブートストラップ標本が必要か? B = 1,000は大まかな推定や仮説検定に十分な場合が多いです。信頼区間にはB = 10,000で安定したパーセンタイルが得られます。論文品質のBCa区間にはB = 15,000以上が推奨されます。

ブートストラップ信頼区間の方法

ブートストラップ標本から信頼区間を構築する方法がいくつかあり、それぞれにトレードオフがあります。

1. パーセンタイル法(最も簡単)

最も直感的なアプローチです。ブートストラップ分布のパーセンタイルをそのまま使用します。

パーセンタイル信頼区間

95% CI = [θ*₂.₅, θ*₉₇.₅]

10,000個のブートストラップ標本の場合、250番目と9,750番目の順序値です。シンプルですが、ブートストラップ分布が歪んでいる場合はバイアスが生じることがあります。

2. 基本(ピボタル)ブートストラップ

標本統計量とブートストラップ統計量の関係を利用します。

基本ブートストラップ信頼区間

95% CI = [2θ̂ - θ*₉₇.₅, 2θ̂ - θ*₂.₅]

θ̂は元の標本SDです。パーセンタイル区間を標本推定値の周りに「反転」させたものです。

3. BCa法(バイアス補正・加速法)

精度における最高水準。BCaはブートストラップ分布のバイアスと加速(パラメータ値に応じて標準誤差がどう変化するか)の両方を補正します。計算はより複雑ですが、2次の精度を持つ区間を提供します。

方法長所短所
パーセンタイル法シンプルで直感的歪んだデータではバイアスが生じうる
基本法対称な区間負の値が出る可能性がある
BCa法最も正確、変換に対して整合的計算コストが高い

計算例:非正規データ

応答時間(ミリ秒)の15個の測定値を考えます:245, 312, 287, 456, 234, 298, 267, 523, 289, 301, 278, 645, 256, 289, 312。このデータは右に歪んでいます(非常に遅い応答がいくつかある)。

1

標本SDを計算する

元の標本:n=15、SD = 109.8 ms
2

ブートストラップ標本を生成する

サイズ15の復元抽出標本を10,000回生成。各標本の構成は異なります。
3

ブートストラップSDを計算する

各ブートストラップ標本のSDを計算し、約60〜180の範囲の10,000個の値を得ます。
4

パーセンタイルを求める

2.5パーセンタイル:72.3 ms、97.5パーセンタイル:156.8 ms
5

95%信頼区間を構成する

95% CI: [72.3, 156.8] ms。カイ2乗による信頼区間 [79.4, 175.2](正規性を仮定)と比較してください。

ブートストラップ信頼区間は非対称(高い側がより広い)で、データの右歪みの性質を反映しています。カイ2乗信頼区間はこの非対称性を捉えません。

Python実装

複数の信頼区間法による完全なブートストラップ実装:

python
import numpy as np
from scipy import stats

def bootstrap_sd_ci(data, n_bootstrap=10000, ci=0.95, method='percentile'):
    """
    Bootstrap confidence interval for standard deviation.

    Parameters:
    -----------
    data : array-like - Original sample
    n_bootstrap : int - Number of bootstrap samples
    ci : float - Confidence level (e.g., 0.95)
    method : str - 'percentile', 'basic', or 'bca'

    Returns:
    --------
    tuple : (lower_bound, upper_bound, bootstrap_sds)
    """
    data = np.array(data)
    n = len(data)
    original_sd = np.std(data, ddof=1)

    # Generate bootstrap samples and calculate SDs
    bootstrap_sds = np.array([
        np.std(np.random.choice(data, size=n, replace=True), ddof=1)
        for _ in range(n_bootstrap)
    ])

    alpha = 1 - ci

    if method == 'percentile':
        lower = np.percentile(bootstrap_sds, 100 * alpha/2)
        upper = np.percentile(bootstrap_sds, 100 * (1 - alpha/2))

    elif method == 'basic':
        lower = 2*original_sd - np.percentile(bootstrap_sds, 100*(1-alpha/2))
        upper = 2*original_sd - np.percentile(bootstrap_sds, 100*alpha/2)

    elif method == 'bca':
        # Bias correction
        prop_less = np.mean(bootstrap_sds < original_sd)
        z0 = stats.norm.ppf(prop_less)

        # Acceleration (jackknife estimate)
        jackknife_sds = np.array([
            np.std(np.delete(data, i), ddof=1) for i in range(n)
        ])
        jack_mean = jackknife_sds.mean()
        a = np.sum((jack_mean - jackknife_sds)**3) / \
            (6 * np.sum((jack_mean - jackknife_sds)**2)**1.5)

        # Adjusted percentiles
        z_alpha = stats.norm.ppf([alpha/2, 1-alpha/2])
        adj_percentiles = stats.norm.cdf(
            z0 + (z0 + z_alpha) / (1 - a*(z0 + z_alpha))
        ) * 100
        lower = np.percentile(bootstrap_sds, adj_percentiles[0])
        upper = np.percentile(bootstrap_sds, adj_percentiles[1])

    return lower, upper, bootstrap_sds

# Example usage
response_times = [245, 312, 287, 456, 234, 298, 267, 523, 289, 301, 278, 645, 256, 289, 312]

for method in ['percentile', 'basic', 'bca']:
    lower, upper, _ = bootstrap_sd_ci(response_times, method=method)
    print(f"{method.upper():12s} 95% CI: [{lower:.1f}, {upper:.1f}]")