「全店舗の売上を月次でレポートしてほしい。ただし全店POSは月末締めしか上がってこないので、月中でも50店舗のサンプルから1000店舗の合計を当てたい」── こうした サンプルから全体を推計する作業 を統計用語で 拡大推計(grossing-up / inflation estimation) と呼びます。
視聴率調査、出口調査、売上速報、家計調査からの市場規模推定、A/Bテストの効果見込み、Webアクセス解析の総閲覧数推計 ── 業務でよく出てくる「部分から全体」の数字の裏側にはほぼ必ずこの技術が動いています。一方で、やり方を間違えると 桁が変わるレベルの誤推計 が出てしまう繊細な仕事でもある。
本記事では、拡大推計の 基本手法 → 業務での使い所 → 典型的なアンチパターン → 実装 を一気に整理します。指標を扱うすべての職種に効くテーマです。
本記事のコードを実行可能ノートブックを `/notebooks/kpi-19-extrapolation.ipynb` で配布。単純拡大・比推定・層化推定・raking それぞれの結果が、同じデータでどれだけ違うかを目で見られます。
1. 拡大推計の基本:3 つのアプローチ
| アプローチ | 概要 | 向く場面 | 誤推計リスク |
|---|---|---|---|
| 単純拡大 | サンプル合計 × (全体数 / サンプル数) | サンプリングが完全ランダム | サンプル偏ると致命的 |
| 比推定 (Ratio Estimation) | 「サンプル比率」を全体の補助情報に掛ける | 全体の補助変数が分かってる時 (例: 全店の店舗面積) | 補助変数の精度依存 |
| 層化推定 (Stratified) | 層(業態・地域・規模)別に拡大して合算 | 層ごとに異なる挙動が見込まれる時 | 層の定義に偏り |
| Post-stratification / Raking | 事後的に層比率を全体実態に合わせて補正 | サンプリング後に偏り判明 | 補正対象が多いと過剰補正 |
2. 単純拡大:最頻出だが最も危険
import pandas as pd
# サンプル50店舗の月中売上sample = pd.DataFrame({ "store_id": range(1, 51), "sales": [120, 95, 230, 88, ...], # 50件})
TOTAL_STORES = 1000inflation_factor = TOTAL_STORES / len(sample)
estimated_total = sample["sales"].sum() * inflation_factorprint(f"全店推定売上: {estimated_total:,.0f} 千円")サンプル選定が「報告が早い店舗」「都心の店舗」に偏ると、全体を過大/過小に推定。例: 報告が早い店舗 = ITに強い大型店、それを単純拡大すると田舎の小型店も大型店と仮定したことになり、桁違いの過大推計に。
3. 比推定:補助変数で「単位あたり」を補正
全店舗の補助変数(例: 店舗面積、従業員数、過去の年商)が分かっている場合、その単位あたりの数字を使って推計を補正します。サンプル偏りに対する最強の処方箋。
# サンプル50店舗 + 全店舗の補助情報(店舗面積 m²)sample = pd.DataFrame({ "store_id": range(1, 51), "sales": [120, 95, 230, 88, ...], "area": [50, 40, 100, 35, ...], # 店舗面積})
TOTAL_AREA = 65000 # 全1000店舗の合計面積(事前に分かってる)
# 単位面積あたり売上 × 全店面積sales_per_area = sample["sales"].sum() / sample["area"].sum()estimated_total = sales_per_area * TOTAL_AREAprint(f"比推定: {estimated_total:,.0f} 千円")4. 層化推定:業態・地域・規模で別計算して合算
# 全店舗の層別店舗数(事前に把握)total_by_stratum = { "都心_大型": 200, "都心_小型": 300, "郊外_大型": 150, "郊外_小型": 350,}
# サンプル中の層別データsample = pd.DataFrame({ "store_id": range(1, 51), "stratum": ["都心_大型"] * 15 + ["都心_小型"] * 15 + ["郊外_大型"] * 10 + ["郊外_小型"] * 10, "sales": [...], # 50件})
# 層別に単純拡大 → 合算total = 0for stratum, n_total in total_by_stratum.items(): s = sample[sample["stratum"] == stratum] if len(s) == 0: continue avg = s["sales"].mean() total += avg * n_total print(f" {stratum}: 平均 {avg:.0f} × {n_total}店 = {avg*n_total:,.0f}")
print(f"層化推定 合計: {total:,.0f} 千円")層内の分散が小さく、層間の分散が大きい時に劇的に精度が上がります。例: 「店舗売上は同じ業態内では似てるが、業態が変わると桁違い」のような場面。
5. Raking(反復比例補正):複数の偏りを同時に補正
「性別 × 年代 × 地域」のように、複数の補正軸でサンプルを全体実態に合わせたい時に使う技法。世論調査・市場調査の標準手法。Python では `quantipy` や自前実装が必要。
import numpy as npimport pandas as pd
# 全体の周辺分布(既知)target_sex = {"M": 0.49, "F": 0.51}target_age = {"20s": 0.20, "30s": 0.25, "40s": 0.25, "50s+": 0.30}
# サンプル(500人)np.random.seed(42)sample = pd.DataFrame({ "sex": np.random.choice(["M", "F"], 500, p=[0.6, 0.4]), # 偏り: 男性多め "age": np.random.choice(["20s", "30s", "40s", "50s+"], 500, p=[0.4, 0.3, 0.2, 0.1]), "score": np.random.normal(70, 10, 500),})sample["weight"] = 1.0
# 反復比例フィッティングfor _ in range(20): # 性別軸の補正 for s, t in target_sex.items(): cur = sample[sample["sex"] == s]["weight"].sum() / sample["weight"].sum() if cur > 0: sample.loc[sample["sex"] == s, "weight"] *= t / cur # 年代軸の補正 for a, t in target_age.items(): cur = sample[sample["age"] == a]["weight"].sum() / sample["weight"].sum() if cur > 0: sample.loc[sample["age"] == a, "weight"] *= t / cur
# 補正前後の平均スコアprint(f"単純平均: {sample['score'].mean():.2f}")print(f"Raking後: {(sample['score'] * sample['weight']).sum() / sample['weight'].sum():.2f}")6. 業務での使い所
| 業務シーン | 適切な手法 |
|---|---|
| 全店舗の月中売上速報(POS未着分を補完) | 比推定(過去年同月の店舗売上を補助変数) |
| EC の異常通知(数時間分のサンプルから日次GMV予測) | 比推定(前日同時刻比のスケーリング) |
| A/Bテストの効果サイズを「全ユーザに展開した時のインパクト」へ | 層化推定(セグメント別CTR × セグメント比率) |
| 市場調査・顧客満足度の本社報告 | Raking(性別 × 年代 × 地域) |
| 広告見込みクリック数の事前推計 | 比推定(過去同フォーマット平均CTR × 表示見込み回数) |
| GA4 サンプリング後のセッション数復元 | GA4 の Big Query export を使い、層化拡大 |
7. 5 つのアンチパターン
AP1: 「サンプルが偏ってない」と仮定する
「月中時点で売上報告が上がってきている店舗 = 自動化が進んだ大型店」のように、サンプル選定そのものに業務的な偏りが入ることが多い。サンプル特性 vs 全体特性を毎回確認すること。
AP2: 補助変数の精度を疑わない
比推定で使う「全店面積」が 実は古いデータだったり、改装で大幅に変わった店舗が含まれてたり。補助変数のメンテ頻度・精度をまず確認する。
AP3: 層を細かく切りすぎる
1 層あたり数件しかサンプルが入らない層化は、分散が爆発して逆に推計精度を悪化させる。最低でも層あたり 30 件以上を目安に。足りない層は近隣層と統合(merge)する判断が必要。
AP4: 「過去の手法」を温存して比較しない
過去の月次レポートが単純拡大で出してたところに、急に層化推定を導入すると、数字のジャンプが起きて経営層が混乱する。新旧両方を並行レポートする移行期間を 3 ヶ月設ける。
AP5: 信頼区間を一緒に出さない
「全店推定売上: 1.2 億」だけ出すと、ピンポイント値が一人歩きする。「95% 信頼区間: [1.05億 - 1.35億]」のように 不確実性レンジ を併記し、経営判断のリスク幅を伝える。
import numpy as np
def bootstrap_total(sample_sales, n_total, B=1000): estimates = [] for _ in range(B): resampled = np.random.choice(sample_sales, len(sample_sales), replace=True) estimates.append(resampled.sum() * n_total / len(sample_sales)) return np.percentile(estimates, [2.5, 50, 97.5])
low, med, high = bootstrap_total(sample["sales"].values, n_total=1000)print(f"全店推計: {med:,.0f}(95% CI: {low:,.0f} - {high:,.0f})")8. 実装の道具立て
| ライブラリ / 手法 | 強み | 向くケース |
|---|---|---|
| 自前 pandas + numpy | 依存ゼロ、ロジック透明 | 単純拡大・比推定・層化推定の大半 |
| `samplics` (Python) | 層化・raking・統計的妥当性検定が揃う | 本格的な調査統計 |
| ipfp (R) / `ipfn` (Python) | Raking の高速実装 | 多次元の事後補正 |
| survey (R) | 標準的な調査統計の教科書実装 | 大規模調査・厚労省系報告 |
| `statsmodels` の WLS | 重み付き回帰の流れで推定 | 回帰モデル組込み |
9. ふくふくの提案:拡大推計を業務に組み込む順序
- Phase 1(1 週間): 既存の単純拡大ロジックを棚卸し、サンプル偏りを定量化
- Phase 2(2-3 週間): 比推定 or 層化推定に置き換え、新旧並行レポート開始
- Phase 3(1 ヶ月): ブートストラップ信頼区間を全レポートに付与、経営層が見慣れる期間
- Phase 4(継続): サンプリング設計の見直し(無作為性の確保、層別サンプル数の調整)
10. まとめ
- 拡大推計は「サンプル → 全体」の数字を作るすべての業務の裏側にある古典技法
- 単純拡大は最頻出かつ最も危険。比推定・層化・raking のどれかに置き換えるだけで精度は劇的に変わる
- 「信頼区間を併記する」「新旧並行レポートで移行する」が現場で効く運用テクニック
- 5 つのアンチパターン(偏り無視 / 補助変数精度 / 層細分化 / 急変 / 区間欠落)に注意
関連記事 / 次の話
A/Bテストの設計と実装 → 本シリーズ EP.17・EP.18。 設計の基本 → 本シリーズ EP.01。前処理での代表値・外れ値処理 → 前処理 EP.03〜05。続編は読者リアクションに応じて、業界別ケーススタディや「拡大推計の自動監視」など随時追加していきます。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。