{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cb10bc08",
   "metadata": {},
   "source": [
    "# 02 분석 — 바텀업·총량 전망, 결합 가중치 검증, 정합화\n",
    "\n",
    "**계산 구조(2026-10 정비판).** 시나리오 $s$, 연도 $t$, 산업 $i$에 대하여\n",
    "\n",
    "1. **바텀업** $\\hat y_{i,s,t}$: 산업별 차분·산업연관표·VARX(4장 선택)를 전체 표본으로 적합하고 3장 외생 경로로 2026Q1–2035Q4를 재귀 전망한다.\n",
    "   이때 상수항(드리프트)을 재설정한다. 차분 VARX의 장기 성장률은 표본(2000–2025) 평균이므로, 모든 산업의 장기 성장률을\n",
    "   $\\delta$ = (표본 산업 합계 평균 성장률 − BOKDPM 잠재성장률 1.8%)만큼 같은 폭으로 낮춘다(절편 보정). 산업 간 성장 격차와 외생·동학 계수는 그대로이다.\n",
    "2. **통제 총량의 성장인자** $g^M_{s,t} = \\dfrac{GDP^{BOKDPM}_t}{GDP^{BOKDPM}_{2025}} \\times \\dfrac{AGG_{s,t}}{AGG_{S1,t}}$: 기준선은 BOKDPM 실질GDP(잠재성장률 1.8% 수렴),\n",
    "   시나리오 간 차이는 총량 VARX(수준·산업연관표·VARX)의 S1 대비 비율이다. 3장 외생과 같은 '기준선 + 시나리오 편차' 구성이다.\n",
    "3. **결합과 정합화**: $g_{s,t} = \\exp[\\,w\\ln g^P_t + (1-w)\\ln g^M_{s,t}\\,]$, $T_{s,t} = \\big(\\sum_i VA^{실적}_{i,2025}\\big)\\,g_{s,t}$,\n",
    "   $VA_{i,s,t} = T_{s,t}\\,\\hat y_{i,s,t}/\\sum_j \\hat y_{j,s,t}$. $g^P$는 기존 전망(2024–2034판) 전산업의 성장인자, $w$는 2절의 유사표본외 검증으로 고른다.\n",
    "\n",
    "마지막 식은 같은 연도·시나리오의 **모든 산업에 같은 비례계수** $T_{s,t}/\\sum_j \\hat y_{j,s,t}$를 곱하는 결정론적 비례 벤치마킹이다. 산업 구성비는 바텀업 전망 그대로 두고 합계만 통제 총량에 맞춘다. 이는 가중최소자승(계층 예측) 사영과 다른 절차이다.\n",
    "\n",
    "**이전 판과 달라진 점.** ① 외생(3장 정비판), ② 바텀업 드리프트 재설정, ③ 통제 총량의 기준선을 총량 VARX에서 BOKDPM 기준선으로, ④ $w$ 재검증(0.45 → 0.40).\n",
    "③의 이유는 4절에 적었다. 총량 VARX는 수준 명세에서 자기 시차 계수의 합이 1보다 작아, 10년 전망에서 성장률이 기계적으로 줄어든다.\n",
    "\n",
    "**산출물.** `output/prior_trend_factors.json`, `shrink_w_validation.csv`, `wstar.json`, `bottomup_annual_{36,62,69}.csv`, `bottomup_raw_annual_{36,62,69}.csv`, `drift_anchor.csv`,\n",
    "`topdown_varx_annual.csv`, `topdown_annual.csv`, `forecast_annual_{36,62,69}.csv`, `reconcile_factors.csv`, `benchmark_only_totals.csv`, `forecast_summary_{36,62,69}.csv`,\n",
    "`forecast_total_{36,62,69}.csv`, `base_va_62.csv`, `base_va_69.csv`, `gap_decomposition.csv`, `method_comparison.csv`, `scenario_exog_decomp.csv`,\n",
    "`shap_62.csv`·`shap_62.json`·`shap_check.csv`(10절 산업별 성장 요인 분해 — 전망 갱신 카드의 요인 분해·자동 해설 자료)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d1dc434f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:16.711868Z",
     "iopub.status.busy": "2026-10-08T07:24:16.711868Z",
     "iopub.status.idle": "2026-10-08T07:24:17.341136Z",
     "shell.execute_reply": "2026-10-08T07:24:17.341136Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python 3.12.10 · pandas 2.2.3 · numpy 2.4.6 · openpyxl 3.1.5 · matplotlib 3.11.0\n",
      "  드리프트 재설정 목표 1.8% · 수축 가중치 w=0.4(이전 판 0.45) · 원본 엑셀 대조: 없음\n",
      "  기존 전망 62     있음  data\\inputs\\prev_keis_va_62.csv\n",
      "  전달 템플릿       있음  data\\inputs\\template_62_VA_v1.xlsx\n",
      "  분기 자료(상류)    있음  data\\inputs\\upstream\n",
      "  정비판 기록       있음  data\\published\n",
      "  이전 판 공표      있음  data\\published_prev\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "os.environ.setdefault(\"OPENBLAS_NUM_THREADS\", \"1\")   # numpy보다 먼저 지정한다(config.py 주석 참조)\n",
    "import json\n",
    "import numpy as np, pandas as pd\n",
    "import config as C\n",
    "import gva_results as R\n",
    "pd.set_option(\"display.float_format\", lambda v: f\"{v:,.3f}\")\n",
    "C.setup()\n",
    "import varx_engine as V\n",
    "prev = pd.read_csv(C.PREV_KEIS, encoding=\"utf-8-sig\")\n",
    "lgdp = pd.read_csv(C.UP[\"lgdp\"], index_col=0, encoding=\"utf-8-sig\", float_precision=\"round_trip\")[\"LGDP_kor\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9599b16",
   "metadata": {},
   "source": [
    "## 1. 기존 전망의 성장인자 $g^P$\n",
    "\n",
    "기존 전망 전산업 행의 2025–2035년 값을 2025년 값으로 나눈다. 이전 판 파이프라인이 저장해 쓴 `prior_trend_factors.json`과 같은지 확인한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "22168022",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:17.342140Z",
     "iopub.status.busy": "2026-10-08T07:24:17.342140Z",
     "iopub.status.idle": "2026-10-08T07:24:17.351825Z",
     "shell.execute_reply": "2026-10-08T07:24:17.351825Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이전 판 저장 파일 대비 최대 절대차: 0.0e+00\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2025</th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "      <th>2031</th>\n",
       "      <th>2032</th>\n",
       "      <th>2033</th>\n",
       "      <th>2034</th>\n",
       "      <th>2035</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>g^P (2025=1)</th>\n",
       "      <td>1.000</td>\n",
       "      <td>1.016</td>\n",
       "      <td>1.034</td>\n",
       "      <td>1.052</td>\n",
       "      <td>1.072</td>\n",
       "      <td>1.091</td>\n",
       "      <td>1.110</td>\n",
       "      <td>1.128</td>\n",
       "      <td>1.146</td>\n",
       "      <td>1.164</td>\n",
       "      <td>1.181</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>연 증가율(%)</th>\n",
       "      <td>NaN</td>\n",
       "      <td>1.600</td>\n",
       "      <td>1.750</td>\n",
       "      <td>1.800</td>\n",
       "      <td>1.842</td>\n",
       "      <td>1.780</td>\n",
       "      <td>1.719</td>\n",
       "      <td>1.660</td>\n",
       "      <td>1.603</td>\n",
       "      <td>1.548</td>\n",
       "      <td>1.494</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              2025  2026  2027  2028  2029  2030  2031  2032  2033  2034  2035\n",
       "g^P (2025=1) 1.000 1.016 1.034 1.052 1.072 1.091 1.110 1.128 1.146 1.164 1.181\n",
       "연 증가율(%)       NaN 1.600 1.750 1.800 1.842 1.780 1.719 1.660 1.603 1.548 1.494"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prior = R.prior_factors(prev)\n",
    "pub_prior = {int(k): v for k, v in json.load(open(os.path.join(C.PREV_DIR, \"prior_trend_factors.json\"), encoding=\"utf-8\")).items()}\n",
    "d_prior = max(abs(prior[y] - pub_prior[y]) for y in pub_prior)\n",
    "print(f\"이전 판 저장 파일 대비 최대 절대차: {d_prior:.1e}\")\n",
    "assert set(prior) == set(pub_prior) and d_prior < 1e-12\n",
    "R.json_dump({str(k): v for k, v in prior.items()}, os.path.join(C.OUTPUT, \"prior_trend_factors.json\"))\n",
    "gp = pd.Series(prior, name=\"g^P\")\n",
    "pd.DataFrame({\"g^P (2025=1)\": gp, \"연 증가율(%)\": gp.pct_change() * 100}).T"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ad9b38d",
   "metadata": {},
   "source": [
    "## 2. 결합 가중치 $w$의 유사표본외 검증\n",
    "\n",
    "기존 전망의 과거 판(vintage)은 남아 있지 않으므로, 기존 전망과 성격이 같은 **추세 연장**(각 산업 로그수준을 학습 표본의 평균 로그성장으로 연장)을 대용으로 쓴다.\n",
    "62분류 차분·IO·VARX를 2000Q1–2019Q4로 적합하고 2020Q1–2025Q4를 실측 외생으로 1기 앞씩 재귀 전망한 뒤, 산업별 로그수준을\n",
    "$\\tilde y = w\\cdot \\text{추세} + (1-w)\\cdot \\text{모형}$으로 결합한다. 연간 수준(십억 원)으로 환산해 산업 평균제곱오차의 제곱근(RMSE)을 구하고,\n",
    "검증 구간(2020–2022)에서 RMSE가 가장 작은 $w$를 고른 뒤 시험 구간(2023–2025)에서 성능을 보고한다. $w$는 0부터 1까지 0.05 간격으로 탐색한다.\n",
    "외생이 바뀌었으므로(실질금리 1차 차분) 이전 판(0.45)과 다른 값이 나올 수 있으며, 이 패키지는 검증으로 고른 값을 쓴다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3e7ac129",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:17.352828Z",
     "iopub.status.busy": "2026-10-08T07:24:17.352828Z",
     "iopub.status.idle": "2026-10-08T07:24:17.428148Z",
     "shell.execute_reply": "2026-10-08T07:24:17.428148Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "선택 w* = 0.4 (검증 최소, config.W_PRIOR = 0.4) · 이전 판 w* = 0.45\n",
      "시험 RMSE: 모형만 6,552.6 · 결합(w*) 6,866.8 · 추세만 7,582.4 → 결합의 모형 대비 변화 +4.8% (이전 판 -7.0%)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>w</th>\n",
       "      <th>검증 RMSE(2020–22)</th>\n",
       "      <th>시험 RMSE(2023–25)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.000</td>\n",
       "      <td>3,256.219</td>\n",
       "      <td>6,552.555</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.100</td>\n",
       "      <td>3,229.498</td>\n",
       "      <td>6,618.257</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0.200</td>\n",
       "      <td>3,210.329</td>\n",
       "      <td>6,692.604</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>0.300</td>\n",
       "      <td>3,198.877</td>\n",
       "      <td>6,775.493</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>0.400</td>\n",
       "      <td>3,195.252</td>\n",
       "      <td>6,866.801</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>0.500</td>\n",
       "      <td>3,199.514</td>\n",
       "      <td>6,966.383</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>0.600</td>\n",
       "      <td>3,211.664</td>\n",
       "      <td>7,074.080</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>0.700</td>\n",
       "      <td>3,231.648</td>\n",
       "      <td>7,189.719</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>0.800</td>\n",
       "      <td>3,259.360</td>\n",
       "      <td>7,313.119</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>0.900</td>\n",
       "      <td>3,294.642</td>\n",
       "      <td>7,444.089</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>1.000</td>\n",
       "      <td>3,337.295</td>\n",
       "      <td>7,582.437</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       w  검증 RMSE(2020–22)  시험 RMSE(2023–25)\n",
       "0  0.000         3,256.219         6,552.555\n",
       "2  0.100         3,229.498         6,618.257\n",
       "4  0.200         3,210.329         6,692.604\n",
       "6  0.300         3,198.877         6,775.493\n",
       "8  0.400         3,195.252         6,866.801\n",
       "10 0.500         3,199.514         6,966.383\n",
       "12 0.600         3,211.664         7,074.080\n",
       "14 0.700         3,231.648         7,189.719\n",
       "16 0.800         3,259.360         7,313.119\n",
       "18 0.900         3,294.642         7,444.089\n",
       "20 1.000         3,337.295         7,582.437"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vw = V.validate_shrink_w(C.UP[\"levels_62\"], C.UP[\"exog_hist\"], C.UP[\"io_62\"])\n",
    "cur = pd.DataFrame({\"w\": vw[\"ws\"], \"검증 RMSE(2020–22)\": vw[\"val\"], \"시험 RMSE(2023–25)\": vw[\"test_curve\"]})\n",
    "cur.to_csv(os.path.join(C.OUTPUT, \"shrink_w_validation.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "t = vw[\"test\"]\n",
    "wj = {\"ws\": vw[\"ws\"], \"val\": [round(r, 1) for r in vw[\"val\"]], \"wstar\": vw[\"wstar\"],\n",
    "      \"test\": {\"model\": round(t[\"model\"], 1), \"shrink\": round(t[\"shrink\"], 1), \"trend\": round(t[\"trend\"], 1)}}\n",
    "R.json_dump(wj, os.path.join(C.OUTPUT, \"wstar.json\"))\n",
    "pw = json.load(open(os.path.join(C.PREV_DIR, \"wstar.json\"), encoding=\"utf-8\"))\n",
    "print(f\"선택 w* = {vw['wstar']} (검증 최소, config.W_PRIOR = {C.W_PRIOR}) · 이전 판 w* = {pw['wstar']}\")\n",
    "print(f\"시험 RMSE: 모형만 {t['model']:,.1f} · 결합(w*) {t['shrink']:,.1f} · 추세만 {t['trend']:,.1f}\"\n",
    "      f\" → 결합의 모형 대비 변화 {(t['shrink'] / t['model'] - 1) * 100:+.1f}% (이전 판 {(pw['test']['shrink'] / pw['test']['model'] - 1) * 100:+.1f}%)\")\n",
    "assert vw[\"wstar\"] == C.W_PRIOR, \"검증으로 고른 w가 config.W_PRIOR와 다르다\"\n",
    "cur.iloc[::2]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9fdb50e",
   "metadata": {},
   "source": [
    "검증 RMSE는 $w$에 대해 완만한 U자형이며 정비판에서는 0.40에서 가장 작다(0.30–0.50 구간의 차이는 0.2% 이내). 이전 판과 달리 시험 구간(2023–2025)에서는 결합 전망이 모형 단독보다 오차가 크다.\n",
    "실질금리를 차분으로 넣은 정비판 모형의 시험 구간 오차가 이전 판보다 작아졌기 때문이다(모형 단독 RMSE가 줄었다). 선택 규칙(검증 구간 최소)은 이전 판과 같게 유지하되,\n",
    "기존 전망 결합의 시험 구간 개선 효과가 사라졌다는 점을 기록한다(README 쟁점). 또한 이 검증은 **산업별 로그수준**을 결합해 $w$를 고르고, 정합화는 같은 $w$를 **전산업 성장인자**의 결합에 쓴다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e675a2bf",
   "metadata": {},
   "source": [
    "## 3. 바텀업 전망 — 산업 VARX와 드리프트 재설정\n",
    "\n",
    "36·62·69분류에 차분·산업연관표·VARX를 전체 표본(2000Q1–2025Q4)으로 적합하고, 3장 S1–S5 외생 경로로 2026Q1–2035Q4를 1기 앞씩 재귀 전망한 뒤 연도별로 합산한다.\n",
    "\n",
    "**드리프트 재설정.** 차분 VARX $\\Delta y_t = c + \\sum_l A_l\\Delta y_{t-l} + \\Gamma x_t$의 장기 평균은 $\\mu = (I-\\sum A_l)^{-1}(c+\\Gamma\\bar x)$이며, 표본 안에서는 산업별 2000–2025년 평균 성장률이다.\n",
    "이 기간 산업 합계는 연 3.3% 성장했으나 잠재성장률은 2000년대 4–5%에서 현재 2% 안팎으로 낮아졌다(3장 평활 잠재성장률). 재설정하지 않으면 산업모형이 2000년대가 섞인 표본 평균 성장률을\n",
    "전망에 이어 붙이게 된다. 3장이 BOKDPM 정상상태를 바꾼 것과 같은 이유로, $c \\leftarrow c - (I-\\sum A_l)\\,\\delta\\,\\mathbf 1$을 써서 모든 산업의 장기 성장률을 같은 폭 $\\delta$만큼 낮춘다.\n",
    "$\\delta$는 표본 산업 합계 평균 성장률에서 BOKDPM 잠재성장률(1.8%)을 뺀 값이며, 추정한 값이 아니다.\n",
    "\n",
    "재설정하지 않은 전망(`bottomup_raw_annual_*.csv`)도 함께 저장해 8절의 괴리 분해에 쓴다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "14756c6d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:17.429176Z",
     "iopub.status.busy": "2026-10-08T07:24:17.429176Z",
     "iopub.status.idle": "2026-10-08T07:24:17.997458Z",
     "shell.execute_reply": "2026-10-08T07:24:17.996953Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>표본 산업 합계 평균 성장률(%)</th>\n",
       "      <th>목표(잠재성장률, %)</th>\n",
       "      <th>δ(연 %p)</th>\n",
       "      <th>바텀업 S1 연평균 — 재설정 전(%)</th>\n",
       "      <th>바텀업 S1 연평균 — 재설정 후(%)</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>분류</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>3.324</td>\n",
       "      <td>1.800</td>\n",
       "      <td>1.524</td>\n",
       "      <td>3.965</td>\n",
       "      <td>2.457</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>3.324</td>\n",
       "      <td>1.800</td>\n",
       "      <td>1.524</td>\n",
       "      <td>3.996</td>\n",
       "      <td>2.519</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>69</th>\n",
       "      <td>3.324</td>\n",
       "      <td>1.800</td>\n",
       "      <td>1.524</td>\n",
       "      <td>4.002</td>\n",
       "      <td>2.525</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    표본 산업 합계 평균 성장률(%)  목표(잠재성장률, %)  δ(연 %p)  바텀업 S1 연평균 — 재설정 전(%)  \\\n",
       "분류                                                                     \n",
       "36               3.324         1.800    1.524                  3.965   \n",
       "62               3.324         1.800    1.524                  3.996   \n",
       "69               3.324         1.800    1.524                  4.002   \n",
       "\n",
       "    바텀업 S1 연평균 — 재설정 후(%)  \n",
       "분류                         \n",
       "36                  2.457  \n",
       "62                  2.519  \n",
       "69                  2.525  "
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bu, bu_raw, anc = {}, {}, []\n",
    "spec, prior_k, em = C.SPEC[\"산업\"]\n",
    "for r in C.RES:\n",
    "    bu_raw[r] = V.run_forecast(C.UP[f\"levels_{r}\"], C.UP[\"exog_hist\"], C.UP[\"exog_scen\"], C.UP[f\"io_{r}\"], spec, prior_k, em)\n",
    "    bu[r] = V.run_forecast(C.UP[f\"levels_{r}\"], C.UP[\"exog_hist\"], C.UP[\"exog_scen\"], C.UP[f\"io_{r}\"], spec, prior_k, em, anchor=C.ANCHOR_GROWTH)\n",
    "    bu[r].to_csv(os.path.join(C.OUTPUT, f\"bottomup_annual_{r}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "    bu_raw[r].to_csv(os.path.join(C.OUTPUT, f\"bottomup_raw_annual_{r}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "    L, _, _, Bm, _ = V.fit_full(C.UP[f\"levels_{r}\"], C.UP[\"exog_hist\"], C.UP[f\"io_{r}\"], spec, prior_k, em)\n",
    "    tr, ta_ = R.totals(bu_raw[r]), R.totals(bu[r])\n",
    "    a25 = tr.loc[C.BASE_YEAR, \"실적\"]\n",
    "    anc.append({\"분류\": r, \"표본 산업 합계 평균 성장률(%)\": V.sum_growth_mean(L), \"목표(잠재성장률, %)\": C.ANCHOR_GROWTH,\n",
    "                \"δ(연 %p)\": V.anchor_drift(L, Bm, C.ANCHOR_GROWTH)[1],\n",
    "                \"바텀업 S1 연평균 — 재설정 전(%)\": R.cagr(a25, tr.loc[C.END_YEAR, \"S1\"], 10),\n",
    "                \"바텀업 S1 연평균 — 재설정 후(%)\": R.cagr(a25, ta_.loc[C.END_YEAR, \"S1\"], 10)})\n",
    "anc = pd.DataFrame(anc).set_index(\"분류\")\n",
    "anc.to_csv(os.path.join(C.OUTPUT, \"drift_anchor.csv\"), encoding=\"utf-8-sig\")\n",
    "tot = {r: R.totals(bu[r]) for r in C.RES}\n",
    "gap = max((tot[r][\"실적\"].dropna() - tot[62][\"실적\"].dropna()).abs().max() for r in C.RES)\n",
    "assert gap < 0.1, \"36·62·69 실적 합계가 일치하지 않는다\"\n",
    "anc"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9d2de3a",
   "metadata": {},
   "source": [
    "재설정 후에도 바텀업 합계의 연평균은 잠재성장률(1.8%)보다 높다. 나머지 차이의 대부분은 **구성 효과**이다. 각 산업이 자기 연평균으로 일정하게 성장한다고 두어도,\n",
    "성장이 빠른 산업(전자부품·통신장비 등)의 비중이 커지므로 합계 증가율은 해마다 높아진다. 아래 표의 둘째 행이 그 크기이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "eee7661a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:17.997458Z",
     "iopub.status.busy": "2026-10-08T07:24:17.997458Z",
     "iopub.status.idle": "2026-10-08T07:24:18.008060Z",
     "shell.execute_reply": "2026-10-08T07:24:18.008060Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2025년 비중 가중 산업 연평균(구성 효과 없는 합계 증가율): 2.20% · 합계 연평균 2.52%\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "      <th>2031</th>\n",
       "      <th>2032</th>\n",
       "      <th>2033</th>\n",
       "      <th>2034</th>\n",
       "      <th>2035</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>62분류 S1 바텀업 합계 증가율(%)</th>\n",
       "      <td>2.023</td>\n",
       "      <td>2.137</td>\n",
       "      <td>2.275</td>\n",
       "      <td>2.426</td>\n",
       "      <td>2.547</td>\n",
       "      <td>2.615</td>\n",
       "      <td>2.679</td>\n",
       "      <td>2.752</td>\n",
       "      <td>2.829</td>\n",
       "      <td>2.908</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>산업별 연평균 고정 시 합계 증가율(%)</th>\n",
       "      <td>2.196</td>\n",
       "      <td>2.265</td>\n",
       "      <td>2.335</td>\n",
       "      <td>2.406</td>\n",
       "      <td>2.478</td>\n",
       "      <td>2.552</td>\n",
       "      <td>2.626</td>\n",
       "      <td>2.701</td>\n",
       "      <td>2.777</td>\n",
       "      <td>2.854</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                        2026  2027  2028  2029  2030  2031  2032  2033  2034  \\\n",
       "62분류 S1 바텀업 합계 증가율(%)  2.023 2.137 2.275 2.426 2.547 2.615 2.679 2.752 2.829   \n",
       "산업별 연평균 고정 시 합계 증가율(%) 2.196 2.265 2.335 2.406 2.478 2.552 2.626 2.701 2.777   \n",
       "\n",
       "                        2035  \n",
       "62분류 S1 바텀업 합계 증가율(%)  2.908  \n",
       "산업별 연평균 고정 시 합계 증가율(%) 2.854  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b62 = bu[62]\n",
    "a62 = b62[(b62[\"시나리오\"] == \"실적\") & (b62[\"연도\"] == C.BASE_YEAR)].set_index(\"산업\")[R.VAL]\n",
    "f62 = b62[b62[\"시나리오\"] == \"S1\"].pivot(index=\"연도\", columns=\"산업\", values=R.VAL)\n",
    "g62 = f62.sum(axis=1); g62.loc[C.BASE_YEAR] = a62.sum(); g62 = g62.sort_index()\n",
    "c62 = (f62.loc[C.END_YEAR] / a62) ** 0.1                                     # 산업별 연평균 성장인자\n",
    "comp = pd.Series({y: (a62 * c62 ** (y - C.BASE_YEAR)).sum() for y in range(C.BASE_YEAR, C.END_YEAR + 1)})\n",
    "w25 = a62 / a62.sum()\n",
    "print(f\"2025년 비중 가중 산업 연평균(구성 효과 없는 합계 증가율): {((w25 * (c62 - 1)).sum()) * 100:.2f}% · 합계 연평균 {R.cagr(a62.sum(), g62[C.END_YEAR], 10):.2f}%\")\n",
    "pd.DataFrame({\"62분류 S1 바텀업 합계 증가율(%)\": g62.pct_change() * 100,\n",
    "              \"산업별 연평균 고정 시 합계 증가율(%)\": comp.pct_change() * 100}).loc[2026:2035].T"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96c51ace",
   "metadata": {},
   "source": [
    "## 4. 통제 총량 — BOKDPM 기준선과 총량 VARX의 시나리오 편차\n",
    "\n",
    "**총량 VARX만으로는 10년 총량 경로를 정하기 어렵다.** 4장에서 총량(실질GDP 한 계열)은 수준 명세가 1기 앞 예측에서 가장 정확했다. 그러나 수준 명세에서 자기 시차 계수의 합이 1보다 작으면\n",
    "(아래 출력) 추세항이 없는 모형이 일정한 수준으로 수렴하려 하므로, 전망 기간이 길수록 성장률이 기계적으로 낮아진다. 이전 판에서는 실질금리 수준의 상승 경로가 이 감소를 가렸다(6장 진단).\n",
    "정비판은 총량의 **기준선**을 장기 수렴값을 정비한 BOKDPM 실질GDP 전망(잠재성장률 1.8%로 수렴)에서 가져오고, 총량 VARX는 시나리오 외생 경로에 대한 **반응의 차이**(S1 대비 비율)를 내는 데만 쓴다.\n",
    "\n",
    "실질GDP는 산업 부가가치의 합에 순생산물세를 더한 값이며 연쇄가격 합계는 가법적이지 않으므로, 정합화에는 총량의 **수준이 아니라 2025년 대비 성장인자**만 쓴다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "31b8b084",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.009063Z",
     "iopub.status.busy": "2026-10-08T07:24:18.009063Z",
     "iopub.status.idle": "2026-10-08T07:24:18.045639Z",
     "shell.execute_reply": "2026-10-08T07:24:18.045639Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "총량 VARX(수준) 자기 시차 계수의 합: 0.9883\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>총량 VARX 연평균(%)</th>\n",
       "      <th>통제 총량(BOKDPM × VARX 편차) 연평균(%)</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>시나리오</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>S1 기준</th>\n",
       "      <td>1.393</td>\n",
       "      <td>1.913</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>S2 공급제약</th>\n",
       "      <td>1.373</td>\n",
       "      <td>1.893</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>S3 제약완화</th>\n",
       "      <td>1.413</td>\n",
       "      <td>1.934</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>S4 기술도약</th>\n",
       "      <td>1.384</td>\n",
       "      <td>1.905</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>S5 지정학</th>\n",
       "      <td>1.401</td>\n",
       "      <td>1.921</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         총량 VARX 연평균(%)  통제 총량(BOKDPM × VARX 편차) 연평균(%)\n",
       "시나리오                                                   \n",
       "S1 기준             1.393                           1.913\n",
       "S2 공급제약           1.373                           1.893\n",
       "S3 제약완화           1.413                           1.934\n",
       "S4 기술도약           1.384                           1.905\n",
       "S5 지정학            1.401                           1.921"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "      <th>2031</th>\n",
       "      <th>2032</th>\n",
       "      <th>2033</th>\n",
       "      <th>2034</th>\n",
       "      <th>2035</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>총량 VARX S1 증가율(%)</th>\n",
       "      <td>1.720</td>\n",
       "      <td>1.371</td>\n",
       "      <td>1.476</td>\n",
       "      <td>1.516</td>\n",
       "      <td>1.473</td>\n",
       "      <td>1.392</td>\n",
       "      <td>1.330</td>\n",
       "      <td>1.271</td>\n",
       "      <td>1.217</td>\n",
       "      <td>1.166</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>BOKDPM 실질GDP 증가율(%)</th>\n",
       "      <td>2.192</td>\n",
       "      <td>1.726</td>\n",
       "      <td>2.240</td>\n",
       "      <td>1.903</td>\n",
       "      <td>1.891</td>\n",
       "      <td>1.860</td>\n",
       "      <td>1.842</td>\n",
       "      <td>1.832</td>\n",
       "      <td>1.826</td>\n",
       "      <td>1.823</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                     2026  2027  2028  2029  2030  2031  2032  2033  2034  \\\n",
       "총량 VARX S1 증가율(%)   1.720 1.371 1.476 1.516 1.473 1.392 1.330 1.271 1.217   \n",
       "BOKDPM 실질GDP 증가율(%) 2.192 1.726 2.240 1.903 1.891 1.860 1.842 1.832 1.826   \n",
       "\n",
       "                     2035  \n",
       "총량 VARX S1 증가율(%)   1.166  \n",
       "BOKDPM 실질GDP 증가율(%) 1.823  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "spec_a, prior_a, em_a = C.SPEC[\"총량\"]\n",
    "agg_varx = V.run_forecast(C.UP[\"levels_agg\"], C.UP[\"exog_hist\"], C.UP[\"exog_scen\"], None, spec_a, prior_a, em_a)\n",
    "agg_varx.to_csv(os.path.join(C.OUTPUT, \"topdown_varx_annual.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "agg = R.topdown_bokdpm(lgdp, agg_varx)\n",
    "agg.to_csv(os.path.join(C.OUTPUT, \"topdown_annual.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "La, _, _, Ba, _ = V.fit_full(C.UP[\"levels_agg\"], C.UP[\"exog_hist\"], None, spec_a, prior_a, em_a)\n",
    "print(f\"총량 VARX(수준) 자기 시차 계수의 합: {Ba[:V.P, 0].sum():.4f}\")\n",
    "tv, tb_ = R.totals(agg_varx), R.totals(agg)\n",
    "rows = []\n",
    "for s in C.SCENARIOS:\n",
    "    rows.append({\"시나리오\": f\"{s} {C.SCN_NAME[s]}\",\n",
    "                 \"총량 VARX 연평균(%)\": R.cagr(tv.loc[C.BASE_YEAR, \"실적\"], tv.loc[C.END_YEAR, s], 10),\n",
    "                 \"통제 총량(BOKDPM × VARX 편차) 연평균(%)\": R.cagr(tb_.loc[C.BASE_YEAR, \"실적\"], tb_.loc[C.END_YEAR, s], 10)})\n",
    "display(pd.DataFrame(rows).set_index(\"시나리오\"))\n",
    "yy = list(range(2026, 2036))\n",
    "pd.DataFrame({\"총량 VARX S1 증가율(%)\": [100 * (tv.loc[y, \"S1\"] / (tv.loc[y - 1, \"S1\"] if y > 2026 else tv.loc[2025, \"실적\"]) - 1) for y in yy],\n",
    "              \"BOKDPM 실질GDP 증가율(%)\": [100 * (tb_.loc[y, \"S1\"] / (tb_.loc[y - 1, \"S1\"] if y > 2026 else tb_.loc[2025, \"실적\"]) - 1) for y in yy]},\n",
    "             index=yy).T"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc87c5de",
   "metadata": {},
   "source": [
    "## 5. 정합화 — 비례 벤치마킹과 기존 전망 수축 결합\n",
    "\n",
    "세 분류(36·62·69)와 다섯 시나리오에 같은 통제 총량을 적용한다. 결과는 소수 셋째 자리(백만 원 단위)에서 반올림한다.\n",
    "단계별 효과를 보기 위해 기존 전망 결합 없이 총량 벤치마킹만 한 경우($w=0$)의 합계도 함께 저장한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "346e5af7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.046643Z",
     "iopub.status.busy": "2026-10-08T07:24:18.046643Z",
     "iopub.status.idle": "2026-10-08T07:24:18.338506Z",
     "shell.execute_reply": "2026-10-08T07:24:18.338002Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[36] 정합화 완료 | S1 전산업 2035 = 2,609.0조 (연평균 +1.820%)\n",
      "[62] 정합화 완료 | S1 전산업 2035 = 2,609.0조 (연평균 +1.820%)\n",
      "[69] 정합화 완료 | S1 전산업 2035 = 2,609.0조 (연평균 +1.820%)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_d5f2f\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_d5f2f_level0_col0\" class=\"col_heading level0 col0\" >바텀업합_조원</th>\n",
       "      <th id=\"T_d5f2f_level0_col1\" class=\"col_heading level0 col1\" >총량모형_gM</th>\n",
       "      <th id=\"T_d5f2f_level0_col2\" class=\"col_heading level0 col2\" >기존전망_gP</th>\n",
       "      <th id=\"T_d5f2f_level0_col3\" class=\"col_heading level0 col3\" >결합_g</th>\n",
       "      <th id=\"T_d5f2f_level0_col4\" class=\"col_heading level0 col4\" >통제총량_조원</th>\n",
       "      <th id=\"T_d5f2f_level0_col5\" class=\"col_heading level0 col5\" >비례계수</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th class=\"index_name level0\" >연도</th>\n",
       "      <th class=\"blank col0\" >&nbsp;</th>\n",
       "      <th class=\"blank col1\" >&nbsp;</th>\n",
       "      <th class=\"blank col2\" >&nbsp;</th>\n",
       "      <th class=\"blank col3\" >&nbsp;</th>\n",
       "      <th class=\"blank col4\" >&nbsp;</th>\n",
       "      <th class=\"blank col5\" >&nbsp;</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row0\" class=\"row_heading level0 row0\" >2026</th>\n",
       "      <td id=\"T_d5f2f_row0_col0\" class=\"data row0 col0\" >2,222.5616</td>\n",
       "      <td id=\"T_d5f2f_row0_col1\" class=\"data row0 col1\" >1.0219</td>\n",
       "      <td id=\"T_d5f2f_row0_col2\" class=\"data row0 col2\" >1.0160</td>\n",
       "      <td id=\"T_d5f2f_row0_col3\" class=\"data row0 col3\" >1.0195</td>\n",
       "      <td id=\"T_d5f2f_row0_col4\" class=\"data row0 col4\" >2,221.0731</td>\n",
       "      <td id=\"T_d5f2f_row0_col5\" class=\"data row0 col5\" >0.9993</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row1\" class=\"row_heading level0 row1\" >2027</th>\n",
       "      <td id=\"T_d5f2f_row1_col0\" class=\"data row1 col0\" >2,270.0475</td>\n",
       "      <td id=\"T_d5f2f_row1_col1\" class=\"data row1 col1\" >1.0396</td>\n",
       "      <td id=\"T_d5f2f_row1_col2\" class=\"data row1 col2\" >1.0338</td>\n",
       "      <td id=\"T_d5f2f_row1_col3\" class=\"data row1 col3\" >1.0372</td>\n",
       "      <td id=\"T_d5f2f_row1_col4\" class=\"data row1 col4\" >2,259.6269</td>\n",
       "      <td id=\"T_d5f2f_row1_col5\" class=\"data row1 col5\" >0.9954</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row2\" class=\"row_heading level0 row2\" >2028</th>\n",
       "      <td id=\"T_d5f2f_row2_col0\" class=\"data row2 col0\" >2,321.6828</td>\n",
       "      <td id=\"T_d5f2f_row2_col1\" class=\"data row2 col1\" >1.0628</td>\n",
       "      <td id=\"T_d5f2f_row2_col2\" class=\"data row2 col2\" >1.0524</td>\n",
       "      <td id=\"T_d5f2f_row2_col3\" class=\"data row2 col3\" >1.0586</td>\n",
       "      <td id=\"T_d5f2f_row2_col4\" class=\"data row2 col4\" >2,306.2547</td>\n",
       "      <td id=\"T_d5f2f_row2_col5\" class=\"data row2 col5\" >0.9934</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row3\" class=\"row_heading level0 row3\" >2029</th>\n",
       "      <td id=\"T_d5f2f_row3_col0\" class=\"data row3 col0\" >2,378.0176</td>\n",
       "      <td id=\"T_d5f2f_row3_col1\" class=\"data row3 col1\" >1.0831</td>\n",
       "      <td id=\"T_d5f2f_row3_col2\" class=\"data row3 col2\" >1.0718</td>\n",
       "      <td id=\"T_d5f2f_row3_col3\" class=\"data row3 col3\" >1.0785</td>\n",
       "      <td id=\"T_d5f2f_row3_col4\" class=\"data row3 col4\" >2,349.5813</td>\n",
       "      <td id=\"T_d5f2f_row3_col5\" class=\"data row3 col5\" >0.9880</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row4\" class=\"row_heading level0 row4\" >2030</th>\n",
       "      <td id=\"T_d5f2f_row4_col0\" class=\"data row4 col0\" >2,438.5900</td>\n",
       "      <td id=\"T_d5f2f_row4_col1\" class=\"data row4 col1\" >1.1035</td>\n",
       "      <td id=\"T_d5f2f_row4_col2\" class=\"data row4 col2\" >1.0908</td>\n",
       "      <td id=\"T_d5f2f_row4_col3\" class=\"data row4 col3\" >1.0985</td>\n",
       "      <td id=\"T_d5f2f_row4_col4\" class=\"data row4 col4\" >2,392.9632</td>\n",
       "      <td id=\"T_d5f2f_row4_col5\" class=\"data row4 col5\" >0.9813</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row5\" class=\"row_heading level0 row5\" >2031</th>\n",
       "      <td id=\"T_d5f2f_row5_col0\" class=\"data row5 col0\" >2,502.3568</td>\n",
       "      <td id=\"T_d5f2f_row5_col1\" class=\"data row5 col1\" >1.1241</td>\n",
       "      <td id=\"T_d5f2f_row5_col2\" class=\"data row5 col2\" >1.1096</td>\n",
       "      <td id=\"T_d5f2f_row5_col3\" class=\"data row5 col3\" >1.1183</td>\n",
       "      <td id=\"T_d5f2f_row5_col4\" class=\"data row5 col4\" >2,436.1243</td>\n",
       "      <td id=\"T_d5f2f_row5_col5\" class=\"data row5 col5\" >0.9735</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row6\" class=\"row_heading level0 row6\" >2032</th>\n",
       "      <td id=\"T_d5f2f_row6_col0\" class=\"data row6 col0\" >2,569.4023</td>\n",
       "      <td id=\"T_d5f2f_row6_col1\" class=\"data row6 col1\" >1.1448</td>\n",
       "      <td id=\"T_d5f2f_row6_col2\" class=\"data row6 col2\" >1.1280</td>\n",
       "      <td id=\"T_d5f2f_row6_col3\" class=\"data row6 col3\" >1.1381</td>\n",
       "      <td id=\"T_d5f2f_row6_col4\" class=\"data row6 col4\" >2,479.2299</td>\n",
       "      <td id=\"T_d5f2f_row6_col5\" class=\"data row6 col5\" >0.9649</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row7\" class=\"row_heading level0 row7\" >2033</th>\n",
       "      <td id=\"T_d5f2f_row7_col0\" class=\"data row7 col0\" >2,640.1137</td>\n",
       "      <td id=\"T_d5f2f_row7_col1\" class=\"data row7 col1\" >1.1658</td>\n",
       "      <td id=\"T_d5f2f_row7_col2\" class=\"data row7 col2\" >1.1461</td>\n",
       "      <td id=\"T_d5f2f_row7_col3\" class=\"data row7 col3\" >1.1579</td>\n",
       "      <td id=\"T_d5f2f_row7_col4\" class=\"data row7 col4\" >2,522.3827</td>\n",
       "      <td id=\"T_d5f2f_row7_col5\" class=\"data row7 col5\" >0.9554</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row8\" class=\"row_heading level0 row8\" >2034</th>\n",
       "      <td id=\"T_d5f2f_row8_col0\" class=\"data row8 col0\" >2,714.8086</td>\n",
       "      <td id=\"T_d5f2f_row8_col1\" class=\"data row8 col1\" >1.1870</td>\n",
       "      <td id=\"T_d5f2f_row8_col2\" class=\"data row8 col2\" >1.1638</td>\n",
       "      <td id=\"T_d5f2f_row8_col3\" class=\"data row8 col3\" >1.1777</td>\n",
       "      <td id=\"T_d5f2f_row8_col4\" class=\"data row8 col4\" >2,565.6383</td>\n",
       "      <td id=\"T_d5f2f_row8_col5\" class=\"data row8 col5\" >0.9451</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_d5f2f_level0_row9\" class=\"row_heading level0 row9\" >2035</th>\n",
       "      <td id=\"T_d5f2f_row9_col0\" class=\"data row9 col0\" >2,793.7447</td>\n",
       "      <td id=\"T_d5f2f_row9_col1\" class=\"data row9 col1\" >1.2087</td>\n",
       "      <td id=\"T_d5f2f_row9_col2\" class=\"data row9 col2\" >1.1812</td>\n",
       "      <td id=\"T_d5f2f_row9_col3\" class=\"data row9 col3\" >1.1976</td>\n",
       "      <td id=\"T_d5f2f_row9_col4\" class=\"data row9 col4\" >2,609.0280</td>\n",
       "      <td id=\"T_d5f2f_row9_col5\" class=\"data row9 col5\" >0.9339</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea38338dd0>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rec, facs, bench = {}, [], []\n",
    "for r in C.RES:\n",
    "    rec[r], fac = R.reconcile(bu[r], agg, prior, C.W_PRIOR)\n",
    "    rec[r].to_csv(os.path.join(C.OUTPUT, f\"forecast_annual_{r}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "    facs.append(fac.assign(분류=r))\n",
    "    r0, _ = R.reconcile(bu[r], agg, prior, 0.0)\n",
    "    bench.append(R.totals(r0).stack().rename(\"합계_십억원\").reset_index().assign(분류=r))\n",
    "    ind25 = rec[r][(rec[r][\"시나리오\"] == \"실적\") & (rec[r][\"연도\"] == C.BASE_YEAR)][R.VAL].sum()\n",
    "    t35 = rec[r][(rec[r][\"시나리오\"] == \"S1\") & (rec[r][\"연도\"] == C.END_YEAR)][R.VAL].sum()\n",
    "    print(f\"[{r}] 정합화 완료 | S1 전산업 2035 = {t35/1000:,.1f}조 (연평균 {R.cagr(ind25, t35, 10):+.3f}%)\")\n",
    "fac_all = pd.concat(facs)[[\"분류\", \"시나리오\", \"연도\", \"바텀업합_십억원\", \"총량모형_gM\", \"기존전망_gP\", \"결합_g\", \"통제총량_십억원\", \"비례계수\"]]\n",
    "fac_all.to_csv(os.path.join(C.OUTPUT, \"reconcile_factors.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "pd.concat(bench)[[\"분류\", \"시나리오\", \"연도\", \"합계_십억원\"]].to_csv(os.path.join(C.OUTPUT, \"benchmark_only_totals.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "f62 = fac_all[(fac_all[\"분류\"] == 62) & (fac_all[\"시나리오\"] == \"S1\")].set_index(\"연도\")\n",
    "f62.assign(바텀업합_조원=f62[\"바텀업합_십억원\"] / 1e3, 통제총량_조원=f62[\"통제총량_십억원\"] / 1e3)[\n",
    "    [\"바텀업합_조원\", \"총량모형_gM\", \"기존전망_gP\", \"결합_g\", \"통제총량_조원\", \"비례계수\"]].style.format(\"{:,.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f86cbf44",
   "metadata": {},
   "source": [
    "비례계수가 1에 가까울수록 바텀업과 통제 총량이 가깝다. 이전 판의 62분류 S1 비례계수는 2035년 0.77이었다(바텀업 3,508조 원을 2,689조 원으로 줄였다)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff71f533",
   "metadata": {},
   "source": [
    "## 6. 비례 벤치마킹의 성질 점검\n",
    "\n",
    "(가) 같은 연도·시나리오에서 모든 산업의 조정 비율 $VA/\\hat y$가 하나의 값(비례계수)이다. (나) 따라서 산업 구성비는 바텀업 전망과 같다.\n",
    "(다) 세 분류의 통제 총량이 같으므로 전산업 합계가 분류와 관계없이 같다. (라) 구성비가 바뀌지 않으므로 정합화 결과를 다시 정합화해도 결과가 같다(멱등).\n",
    "모든 점검은 소수 셋째 자리 반올림의 범위 안에서 판정한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "466232af",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.339006Z",
     "iopub.status.busy": "2026-10-08T07:24:18.339006Z",
     "iopub.status.idle": "2026-10-08T07:24:18.365004Z",
     "shell.execute_reply": "2026-10-08T07:24:18.365004Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_906df\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th id=\"T_906df_level0_col0\" class=\"col_heading level0 col0\" >점검</th>\n",
       "      <th id=\"T_906df_level0_col1\" class=\"col_heading level0 col1\" >값</th>\n",
       "      <th id=\"T_906df_level0_col2\" class=\"col_heading level0 col2\" >기준</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td id=\"T_906df_row0_col0\" class=\"data row0 col0\" >(가) 산업 간 조정 비율의 최대 범위</td>\n",
       "      <td id=\"T_906df_row0_col1\" class=\"data row0 col1\" >3.11e-05</td>\n",
       "      <td id=\"T_906df_row0_col2\" class=\"data row0 col2\" >< 1e-4 (작은 산업의 반올림 영향)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td id=\"T_906df_row1_col0\" class=\"data row1 col0\" >(나) 산업 구성비 최대 절대차</td>\n",
       "      <td id=\"T_906df_row1_col1\" class=\"data row1 col1\" >4.12e-10</td>\n",
       "      <td id=\"T_906df_row1_col2\" class=\"data row1 col2\" >< 1e-8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td id=\"T_906df_row2_col0\" class=\"data row2 col0\" >(다) 분류 간 전산업 합계 최대차(십억 원)</td>\n",
       "      <td id=\"T_906df_row2_col1\" class=\"data row2 col1\" >8.00e-03</td>\n",
       "      <td id=\"T_906df_row2_col2\" class=\"data row2 col2\" >< 0.035 (69 × 0.0005)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td id=\"T_906df_row3_col0\" class=\"data row3 col0\" >(라) 재정합화 후 최대 절대차(십억 원)</td>\n",
       "      <td id=\"T_906df_row3_col1\" class=\"data row3 col1\" >1.00e-03</td>\n",
       "      <td id=\"T_906df_row3_col2\" class=\"data row3 col2\" >≤ 0.002</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea3bd27200>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b, r62 = bu[62], rec[62]\n",
    "m = r62.merge(b, on=[\"시나리오\", \"연도\", \"산업\"], suffixes=(\"\", \"_바텀업\"))\n",
    "m = m[m[\"시나리오\"] != \"실적\"]\n",
    "m[\"비율\"] = m[R.VAL] / m[R.VAL + \"_바텀업\"]\n",
    "spread = m.groupby([\"시나리오\", \"연도\"])[\"비율\"].agg(lambda x: x.max() - x.min()).max()\n",
    "sh_r = m[R.VAL] / m.groupby([\"시나리오\", \"연도\"])[R.VAL].transform(\"sum\")\n",
    "sh_b = m[R.VAL + \"_바텀업\"] / m.groupby([\"시나리오\", \"연도\"])[R.VAL + \"_바텀업\"].transform(\"sum\")\n",
    "T = {r: R.totals(rec[r]) for r in C.RES}\n",
    "tgap = max((T[r] - T[62]).abs().max().max() for r in C.RES)\n",
    "r62b, _ = R.reconcile(r62, agg, prior, C.W_PRIOR)\n",
    "idem = (r62b[R.VAL] - r62[R.VAL]).abs().max()\n",
    "props = pd.DataFrame([\n",
    "    {\"점검\": \"(가) 산업 간 조정 비율의 최대 범위\", \"값\": spread, \"기준\": \"< 1e-4 (작은 산업의 반올림 영향)\"},\n",
    "    {\"점검\": \"(나) 산업 구성비 최대 절대차\", \"값\": float((sh_r - sh_b).abs().max()), \"기준\": \"< 1e-8\"},\n",
    "    {\"점검\": \"(다) 분류 간 전산업 합계 최대차(십억 원)\", \"값\": tgap, \"기준\": \"< 0.035 (69 × 0.0005)\"},\n",
    "    {\"점검\": \"(라) 재정합화 후 최대 절대차(십억 원)\", \"값\": float(idem), \"기준\": \"≤ 0.002\"},\n",
    "])\n",
    "assert spread < 1e-4 and (sh_r - sh_b).abs().max() < 1e-8 and tgap < 0.035 and idem <= 0.002 + 1e-9\n",
    "props.style.format({\"값\": \"{:.2e}\"}).hide(axis=\"index\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a32625c1",
   "metadata": {},
   "source": [
    "## 7. 요약표·총계 시계열·보고서2 입력\n",
    "\n",
    "산업별 요약표(2025·2035년 수준과 연평균)와 전산업 총계 시계열(조 원, 실적과 S1–S5, 2025년 접합)을 만든다.\n",
    "정합화 후 S1(2025년 실적 + 2026–2035년 전망)을 보고서2 4장(AI 확산 시나리오) 재현 패키지의 입력 형식(`base_va_62.csv`·`base_va_69.csv`)으로 저장한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1b727e4a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.366008Z",
     "iopub.status.busy": "2026-10-08T07:24:18.366008Z",
     "iopub.status.idle": "2026-10-08T07:24:18.399385Z",
     "shell.execute_reply": "2026-10-08T07:24:18.399385Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>실적</th>\n",
       "      <th>S1</th>\n",
       "      <th>S2</th>\n",
       "      <th>S3</th>\n",
       "      <th>S4</th>\n",
       "      <th>S5</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>연도</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2000</th>\n",
       "      <td>955.100</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2010</th>\n",
       "      <td>1,460.500</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2020</th>\n",
       "      <td>1,890.800</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024</th>\n",
       "      <td>2,145.700</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2025</th>\n",
       "      <td>2,178.500</td>\n",
       "      <td>2,178.500</td>\n",
       "      <td>2,178.500</td>\n",
       "      <td>2,178.500</td>\n",
       "      <td>2,178.500</td>\n",
       "      <td>2,178.500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026</th>\n",
       "      <td>NaN</td>\n",
       "      <td>2,221.100</td>\n",
       "      <td>2,198.300</td>\n",
       "      <td>2,233.400</td>\n",
       "      <td>2,233.600</td>\n",
       "      <td>2,203.200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2030</th>\n",
       "      <td>NaN</td>\n",
       "      <td>2,393.000</td>\n",
       "      <td>2,391.100</td>\n",
       "      <td>2,394.500</td>\n",
       "      <td>2,400.100</td>\n",
       "      <td>2,393.800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2035</th>\n",
       "      <td>NaN</td>\n",
       "      <td>2,609.000</td>\n",
       "      <td>2,605.900</td>\n",
       "      <td>2,612.200</td>\n",
       "      <td>2,607.700</td>\n",
       "      <td>2,610.200</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            실적        S1        S2        S3        S4        S5\n",
       "연도                                                              \n",
       "2000   955.100       NaN       NaN       NaN       NaN       NaN\n",
       "2010 1,460.500       NaN       NaN       NaN       NaN       NaN\n",
       "2020 1,890.800       NaN       NaN       NaN       NaN       NaN\n",
       "2024 2,145.700       NaN       NaN       NaN       NaN       NaN\n",
       "2025 2,178.500 2,178.500 2,178.500 2,178.500 2,178.500 2,178.500\n",
       "2026       NaN 2,221.100 2,198.300 2,233.400 2,233.600 2,203.200\n",
       "2030       NaN 2,393.000 2,391.100 2,394.500 2,400.100 2,393.800\n",
       "2035       NaN 2,609.000 2,605.900 2,612.200 2,607.700 2,610.200"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "for r in C.RES:\n",
    "    R.summary_table(rec[r]).to_csv(os.path.join(C.OUTPUT, f\"forecast_summary_{r}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "    R.total_series(rec[r]).to_csv(os.path.join(C.OUTPUT, f\"forecast_total_{r}.csv\"), encoding=\"utf-8-sig\")\n",
    "for r in (62, 69):\n",
    "    R.base_scenario(rec[r]).to_csv(os.path.join(C.OUTPUT, f\"base_va_{r}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "R.total_series(rec[62]).loc[[2000, 2010, 2020, 2024, 2025, 2026, 2030, 2035]]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5cf20d1",
   "metadata": {},
   "source": [
    "## 8. 바텀업과 총량의 괴리 — 분해와 절차 비교\n",
    "\n",
    "62분류 S1의 2025–2035년 연평균으로 바텀업과 통제 총량의 차이를 단계별로 나눈다.\n",
    "\n",
    "- **표본 평균 성장률 효과**: 재설정 전 바텀업 − 재설정 후 바텀업. 산업모형이 2000년대가 섞인 표본 평균 성장률을 잇는 데서 생긴 차이이다.\n",
    "- **구성 효과**: 재설정 후 바텀업 − 2025년 비중 가중 산업 연평균. 성장이 빠른 산업의 비중이 커지는 데서 생긴다.\n",
    "- **나머지**: 2025년 비중 가중 산업 연평균 − 통제 총량. 외생 경로·동학과 BOKDPM 기준선의 차이이다.\n",
    "\n",
    "이어서 같은 외생으로 **이전 판 절차**(드리프트 재설정 없음, 통제 총량 = 총량 VARX, $w=0.45$)를 적용한 결과와 정비판 절차를 비교한다. 이전 판 절차로는 바텀업과 총량이 크게 벌어진다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "c7e38fe6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.400889Z",
     "iopub.status.busy": "2026-10-08T07:24:18.400889Z",
     "iopub.status.idle": "2026-10-08T07:24:18.445781Z",
     "shell.execute_reply": "2026-10-08T07:24:18.445276Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_9d044\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_9d044_level0_col0\" class=\"col_heading level0 col0\" >연평균(%)</th>\n",
       "      <th id=\"T_9d044_level0_col1\" class=\"col_heading level0 col1\" >차이(%p)</th>\n",
       "      <th id=\"T_9d044_level0_col2\" class=\"col_heading level0 col2\" >설명</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th class=\"index_name level0\" >단계</th>\n",
       "      <th class=\"blank col0\" >&nbsp;</th>\n",
       "      <th class=\"blank col1\" >&nbsp;</th>\n",
       "      <th class=\"blank col2\" >&nbsp;</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_9d044_level0_row0\" class=\"row_heading level0 row0\" >바텀업(재설정 전)</th>\n",
       "      <td id=\"T_9d044_row0_col0\" class=\"data row0 col0\" >3.996</td>\n",
       "      <td id=\"T_9d044_row0_col1\" class=\"data row0 col1\" ></td>\n",
       "      <td id=\"T_9d044_row0_col2\" class=\"data row0 col2\" ></td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_9d044_level0_row1\" class=\"row_heading level0 row1\" >바텀업(재설정 후)</th>\n",
       "      <td id=\"T_9d044_row1_col0\" class=\"data row1 col0\" >2.519</td>\n",
       "      <td id=\"T_9d044_row1_col1\" class=\"data row1 col1\" >+1.477</td>\n",
       "      <td id=\"T_9d044_row1_col2\" class=\"data row1 col2\" >표본 평균 성장률 효과</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_9d044_level0_row2\" class=\"row_heading level0 row2\" >2025년 비중 가중 산업 연평균</th>\n",
       "      <td id=\"T_9d044_row2_col0\" class=\"data row2 col0\" >2.196</td>\n",
       "      <td id=\"T_9d044_row2_col1\" class=\"data row2 col1\" >+0.323</td>\n",
       "      <td id=\"T_9d044_row2_col2\" class=\"data row2 col2\" >구성 효과</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_9d044_level0_row3\" class=\"row_heading level0 row3\" >통제 총량 g^M(BOKDPM × VARX 편차)</th>\n",
       "      <td id=\"T_9d044_row3_col0\" class=\"data row3 col0\" >1.913</td>\n",
       "      <td id=\"T_9d044_row3_col1\" class=\"data row3 col1\" >+0.282</td>\n",
       "      <td id=\"T_9d044_row3_col2\" class=\"data row3 col2\" >외생·동학과 기준선의 차이</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea3bc304a0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_c70e5\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_c70e5_level0_col0\" class=\"col_heading level0 col0\" >바텀업 연평균(%)</th>\n",
       "      <th id=\"T_c70e5_level0_col1\" class=\"col_heading level0 col1\" >총량 연평균(%)</th>\n",
       "      <th id=\"T_c70e5_level0_col2\" class=\"col_heading level0 col2\" >정합화 2035(조원)</th>\n",
       "      <th id=\"T_c70e5_level0_col3\" class=\"col_heading level0 col3\" >정합화 연평균(%)</th>\n",
       "      <th id=\"T_c70e5_level0_col4\" class=\"col_heading level0 col4\" >비례계수 2035</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th class=\"index_name level0\" >절차</th>\n",
       "      <th class=\"blank col0\" >&nbsp;</th>\n",
       "      <th class=\"blank col1\" >&nbsp;</th>\n",
       "      <th class=\"blank col2\" >&nbsp;</th>\n",
       "      <th class=\"blank col3\" >&nbsp;</th>\n",
       "      <th class=\"blank col4\" >&nbsp;</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_c70e5_level0_row0\" class=\"row_heading level0 row0\" >이전 판 절차(재설정 없음, 총량 VARX, w=0.45)</th>\n",
       "      <td id=\"T_c70e5_row0_col0\" class=\"data row0 col0\" >3.996</td>\n",
       "      <td id=\"T_c70e5_row0_col1\" class=\"data row0 col1\" >1.393</td>\n",
       "      <td id=\"T_c70e5_row0_col2\" class=\"data row0 col2\" >2,533.690</td>\n",
       "      <td id=\"T_c70e5_row0_col3\" class=\"data row0 col3\" >1.522</td>\n",
       "      <td id=\"T_c70e5_row0_col4\" class=\"data row0 col4\" >0.786</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_c70e5_level0_row1\" class=\"row_heading level0 row1\" >정비판 절차(재설정, BOKDPM 기준선, w=0.40)</th>\n",
       "      <td id=\"T_c70e5_row1_col0\" class=\"data row1 col0\" >2.519</td>\n",
       "      <td id=\"T_c70e5_row1_col1\" class=\"data row1 col1\" >1.913</td>\n",
       "      <td id=\"T_c70e5_row1_col2\" class=\"data row1 col2\" >2,609.028</td>\n",
       "      <td id=\"T_c70e5_row1_col3\" class=\"data row1 col3\" >1.820</td>\n",
       "      <td id=\"T_c70e5_row1_col4\" class=\"data row1 col4\" >0.934</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea3bd532c0>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a25 = R.totals(bu[62]).loc[C.BASE_YEAR, \"실적\"]\n",
    "cg = lambda t, s=\"S1\": R.cagr(a25, t.loc[C.END_YEAR, s], 10)\n",
    "tr_, ta_ = R.totals(bu_raw[62]), R.totals(bu[62])\n",
    "wavg = float((w25 * (c62 - 1)).sum() * 100)\n",
    "tot_ctrl = R.cagr(R.totals(agg).loc[C.BASE_YEAR, \"실적\"], R.totals(agg).loc[C.END_YEAR, \"S1\"], 10)\n",
    "gd = pd.DataFrame([\n",
    "    {\"단계\": \"바텀업(재설정 전)\", \"연평균(%)\": cg(tr_)},\n",
    "    {\"단계\": \"바텀업(재설정 후)\", \"연평균(%)\": cg(ta_), \"차이(%p)\": cg(tr_) - cg(ta_), \"설명\": \"표본 평균 성장률 효과\"},\n",
    "    {\"단계\": \"2025년 비중 가중 산업 연평균\", \"연평균(%)\": wavg, \"차이(%p)\": cg(ta_) - wavg, \"설명\": \"구성 효과\"},\n",
    "    {\"단계\": \"통제 총량 g^M(BOKDPM × VARX 편차)\", \"연평균(%)\": tot_ctrl, \"차이(%p)\": wavg - tot_ctrl, \"설명\": \"외생·동학과 기준선의 차이\"},\n",
    "]).set_index(\"단계\")\n",
    "gd.to_csv(os.path.join(C.OUTPUT, \"gap_decomposition.csv\"), encoding=\"utf-8-sig\")\n",
    "display(gd.style.format({\"연평균(%)\": \"{:.3f}\", \"차이(%p)\": \"{:+.3f}\"}, na_rep=\"\"))\n",
    "\n",
    "rows = []\n",
    "for lab, bsrc, asrc, w in [(\"이전 판 절차(재설정 없음, 총량 VARX, w=0.45)\", bu_raw, agg_varx, C.W_PRIOR_PREV),\n",
    "                           (\"정비판 절차(재설정, BOKDPM 기준선, w=0.40)\", bu, agg, C.W_PRIOR)]:\n",
    "    rr, ff = R.reconcile(bsrc[62], asrc, prior, w)\n",
    "    ta2, tb2, tr2 = R.totals(asrc), R.totals(bsrc[62]), R.totals(rr)\n",
    "    for s in C.SCENARIOS:\n",
    "        rows.append({\"절차\": lab, \"시나리오\": s, \"바텀업 연평균(%)\": cg(tb2, s),\n",
    "                     \"총량 연평균(%)\": R.cagr(ta2.loc[C.BASE_YEAR, \"실적\"], ta2.loc[C.END_YEAR, s], 10),\n",
    "                     \"정합화 2035(조원)\": tr2.loc[C.END_YEAR, s] / 1e3, \"정합화 연평균(%)\": cg(tr2, s),\n",
    "                     \"비례계수 2035\": float(ff[(ff[\"시나리오\"] == s) & (ff[\"연도\"] == C.END_YEAR)][\"비례계수\"].iloc[0])})\n",
    "mc = pd.DataFrame(rows)\n",
    "mc.to_csv(os.path.join(C.OUTPUT, \"method_comparison.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "mc[mc[\"시나리오\"] == \"S1\"].set_index(\"절차\").drop(columns=\"시나리오\").style.format(\"{:,.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73721895",
   "metadata": {},
   "source": [
    "## 9. 시나리오 차이의 외생별 분해\n",
    "\n",
    "정비판에서는 시나리오 간 차이가 작다. 2035년 합계의 시나리오 차이(각 시나리오 − S1, %)를 외생별로 나눈다. 외생 $k$의 기여는 S1 외생 경로에서 $k$ 하나만 해당 시나리오의 경로로 바꾸어 다시 전망한 값과 S1 전망의 차이이다.\n",
    "통제 총량의 시나리오 편차를 내는 총량 VARX와 62분류 바텀업(드리프트 재설정)을 함께 본다. 모형이 로그 수준에서 선형이므로 기여의 합은 전체 차이와 근사적으로 같다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2e1a90ef",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.446280Z",
     "iopub.status.busy": "2026-10-08T07:24:18.446280Z",
     "iopub.status.idle": "2026-10-08T07:24:18.703457Z",
     "shell.execute_reply": "2026-10-08T07:24:18.703457Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_ecbae\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank\" >&nbsp;</th>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_ecbae_level0_col0\" class=\"col_heading level0 col0\" >전체(%)</th>\n",
       "      <th id=\"T_ecbae_level0_col1\" class=\"col_heading level0 col1\" >유가(%)</th>\n",
       "      <th id=\"T_ecbae_level0_col2\" class=\"col_heading level0 col2\" >세계수요(%)</th>\n",
       "      <th id=\"T_ecbae_level0_col3\" class=\"col_heading level0 col3\" >실질금리(%)</th>\n",
       "      <th id=\"T_ecbae_level0_col4\" class=\"col_heading level0 col4\" >실질환율(%)</th>\n",
       "      <th id=\"T_ecbae_level0_col5\" class=\"col_heading level0 col5\" >고용_취업자(%)</th>\n",
       "      <th id=\"T_ecbae_level0_col6\" class=\"col_heading level0 col6\" >우리경기(%)</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th class=\"index_name level0\" >&nbsp;</th>\n",
       "      <th class=\"index_name level1\" >시나리오</th>\n",
       "      <th class=\"blank col0\" >&nbsp;</th>\n",
       "      <th class=\"blank col1\" >&nbsp;</th>\n",
       "      <th class=\"blank col2\" >&nbsp;</th>\n",
       "      <th class=\"blank col3\" >&nbsp;</th>\n",
       "      <th class=\"blank col4\" >&nbsp;</th>\n",
       "      <th class=\"blank col5\" >&nbsp;</th>\n",
       "      <th class=\"blank col6\" >&nbsp;</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level0_row0\" class=\"row_heading level0 row0\" rowspan=\"4\">총량 VARX</th>\n",
       "      <th id=\"T_ecbae_level1_row0\" class=\"row_heading level1 row0\" >S2</th>\n",
       "      <td id=\"T_ecbae_row0_col0\" class=\"data row0 col0\" >-0.202</td>\n",
       "      <td id=\"T_ecbae_row0_col1\" class=\"data row0 col1\" >-0.023</td>\n",
       "      <td id=\"T_ecbae_row0_col2\" class=\"data row0 col2\" >+0.069</td>\n",
       "      <td id=\"T_ecbae_row0_col3\" class=\"data row0 col3\" >+0.015</td>\n",
       "      <td id=\"T_ecbae_row0_col4\" class=\"data row0 col4\" >+0.039</td>\n",
       "      <td id=\"T_ecbae_row0_col5\" class=\"data row0 col5\" >-0.222</td>\n",
       "      <td id=\"T_ecbae_row0_col6\" class=\"data row0 col6\" >-0.080</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level1_row1\" class=\"row_heading level1 row1\" >S3</th>\n",
       "      <td id=\"T_ecbae_row1_col0\" class=\"data row1 col0\" >+0.202</td>\n",
       "      <td id=\"T_ecbae_row1_col1\" class=\"data row1 col1\" >-0.009</td>\n",
       "      <td id=\"T_ecbae_row1_col2\" class=\"data row1 col2\" >-0.035</td>\n",
       "      <td id=\"T_ecbae_row1_col3\" class=\"data row1 col3\" >-0.009</td>\n",
       "      <td id=\"T_ecbae_row1_col4\" class=\"data row1 col4\" >-0.022</td>\n",
       "      <td id=\"T_ecbae_row1_col5\" class=\"data row1 col5\" >+0.230</td>\n",
       "      <td id=\"T_ecbae_row1_col6\" class=\"data row1 col6\" >+0.045</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level1_row2\" class=\"row_heading level1 row2\" >S4</th>\n",
       "      <td id=\"T_ecbae_row2_col0\" class=\"data row2 col0\" >-0.085</td>\n",
       "      <td id=\"T_ecbae_row2_col1\" class=\"data row2 col1\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row2_col2\" class=\"data row2 col2\" >-0.076</td>\n",
       "      <td id=\"T_ecbae_row2_col3\" class=\"data row2 col3\" >-0.017</td>\n",
       "      <td id=\"T_ecbae_row2_col4\" class=\"data row2 col4\" >-0.047</td>\n",
       "      <td id=\"T_ecbae_row2_col5\" class=\"data row2 col5\" >-0.034</td>\n",
       "      <td id=\"T_ecbae_row2_col6\" class=\"data row2 col6\" >+0.090</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level1_row3\" class=\"row_heading level1 row3\" >S5</th>\n",
       "      <td id=\"T_ecbae_row3_col0\" class=\"data row3 col0\" >+0.077</td>\n",
       "      <td id=\"T_ecbae_row3_col1\" class=\"data row3 col1\" >+0.043</td>\n",
       "      <td id=\"T_ecbae_row3_col2\" class=\"data row3 col2\" >+0.029</td>\n",
       "      <td id=\"T_ecbae_row3_col3\" class=\"data row3 col3\" >+0.012</td>\n",
       "      <td id=\"T_ecbae_row3_col4\" class=\"data row3 col4\" >+0.029</td>\n",
       "      <td id=\"T_ecbae_row3_col5\" class=\"data row3 col5\" >+0.027</td>\n",
       "      <td id=\"T_ecbae_row3_col6\" class=\"data row3 col6\" >-0.064</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level0_row4\" class=\"row_heading level0 row4\" rowspan=\"4\">바텀업 62</th>\n",
       "      <th id=\"T_ecbae_level1_row4\" class=\"row_heading level1 row4\" >S2</th>\n",
       "      <td id=\"T_ecbae_row4_col0\" class=\"data row4 col0\" >-0.024</td>\n",
       "      <td id=\"T_ecbae_row4_col1\" class=\"data row4 col1\" >-0.075</td>\n",
       "      <td id=\"T_ecbae_row4_col2\" class=\"data row4 col2\" >+0.003</td>\n",
       "      <td id=\"T_ecbae_row4_col3\" class=\"data row4 col3\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row4_col4\" class=\"data row4 col4\" >-0.000</td>\n",
       "      <td id=\"T_ecbae_row4_col5\" class=\"data row4 col5\" >-0.051</td>\n",
       "      <td id=\"T_ecbae_row4_col6\" class=\"data row4 col6\" >+0.112</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level1_row5\" class=\"row_heading level1 row5\" >S3</th>\n",
       "      <td id=\"T_ecbae_row5_col0\" class=\"data row5 col0\" >-0.028</td>\n",
       "      <td id=\"T_ecbae_row5_col1\" class=\"data row5 col1\" >-0.029</td>\n",
       "      <td id=\"T_ecbae_row5_col2\" class=\"data row5 col2\" >+0.001</td>\n",
       "      <td id=\"T_ecbae_row5_col3\" class=\"data row5 col3\" >-0.000</td>\n",
       "      <td id=\"T_ecbae_row5_col4\" class=\"data row5 col4\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row5_col5\" class=\"data row5 col5\" >+0.051</td>\n",
       "      <td id=\"T_ecbae_row5_col6\" class=\"data row5 col6\" >-0.051</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level1_row6\" class=\"row_heading level1 row6\" >S4</th>\n",
       "      <td id=\"T_ecbae_row6_col0\" class=\"data row6 col0\" >-0.089</td>\n",
       "      <td id=\"T_ecbae_row6_col1\" class=\"data row6 col1\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row6_col2\" class=\"data row6 col2\" >+0.004</td>\n",
       "      <td id=\"T_ecbae_row6_col3\" class=\"data row6 col3\" >-0.006</td>\n",
       "      <td id=\"T_ecbae_row6_col4\" class=\"data row6 col4\" >+0.007</td>\n",
       "      <td id=\"T_ecbae_row6_col5\" class=\"data row6 col5\" >-0.002</td>\n",
       "      <td id=\"T_ecbae_row6_col6\" class=\"data row6 col6\" >-0.090</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_ecbae_level1_row7\" class=\"row_heading level1 row7\" >S5</th>\n",
       "      <td id=\"T_ecbae_row7_col0\" class=\"data row7 col0\" >+0.263</td>\n",
       "      <td id=\"T_ecbae_row7_col1\" class=\"data row7 col1\" >+0.163</td>\n",
       "      <td id=\"T_ecbae_row7_col2\" class=\"data row7 col2\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row7_col3\" class=\"data row7 col3\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row7_col4\" class=\"data row7 col4\" >-0.000</td>\n",
       "      <td id=\"T_ecbae_row7_col5\" class=\"data row7 col5\" >+0.000</td>\n",
       "      <td id=\"T_ecbae_row7_col6\" class=\"data row7 col6\" >+0.087</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea3bc757c0>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dec_a = V.scenario_exog_decomp(C.UP[\"levels_agg\"], C.UP[\"exog_hist\"], C.UP[\"exog_scen\"], None, *C.SPEC[\"총량\"])\n",
    "dec_b = V.scenario_exog_decomp(C.UP[\"levels_62\"], C.UP[\"exog_hist\"], C.UP[\"exog_scen\"], C.UP[\"io_62\"], *C.SPEC[\"산업\"], anchor=C.ANCHOR_GROWTH)\n",
    "dec = pd.concat({\"총량 VARX\": dec_a, \"바텀업 62\": dec_b})\n",
    "dec.to_csv(os.path.join(C.OUTPUT, \"scenario_exog_decomp.csv\"), encoding=\"utf-8-sig\")\n",
    "dec.style.format(\"{:+.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cc2d17c",
   "metadata": {},
   "source": [
    "## 10. 산업별 성장 요인 분해 — 62분류 S1(선형 SHAP)\n",
    "\n",
    "전망 갱신 카드(`forecast-va-2025-2035.html`)의 '성장 요인 분해' 막대와 자동 해설이 이 절의 산출물을 쓴다. 62분류 바텀업(드리프트 재설정 후) S1 전망의 2025Q4→2035Q4 로그 성장(×100, %p)을\n",
    "**과거추세**(상수항), **산업 동학**(자기·교차 시차), **외생 6종**의 기여로 나눈다. 차분 VARX $\\Delta y_t = c + \\sum_l A_l\\Delta y_{t-l} + \\sum_{s=0}^{2}\\Gamma_s x_{t-s}$에서는 매 분기의 전망값이 세 묶음의 합이므로\n",
    "40분기를 더하면 분해가 정확하다(성분의 합 = 10년 로그 성장). 외생 기여는 그 분기의 계수 × 외생값(기준점 0)이며, 외생 충격이 시차를 거쳐 다음 분기로 퍼지는 몫은 산업 동학에 들어간다.\n",
    "계산식은 이전 판 분해(6장 진단 패키지의 선형 SHAP, 카드의 이전 판 자료)와 같고, 달라진 것은 정비판 외생(실질금리 1차 차분 등)과 드리프트 재설정 계수이다.\n",
    "\n",
    "- **과거추세**는 재설정 후 상수항의 기여이다. 재설정은 산업 $i$의 상수항을 $(1-\\sum_{l,j}A_{l,ij})\\,\\delta/4$만큼 낮추므로, 과거추세는 재설정 전보다 $10\\,(1-\\sum_{l,j}A_{l,ij})\\,\\delta$ %p 작다.\n",
    "- 이 분해는 **정합화 이전** 바텀업 경로를 대상으로 한다. 최종 전망은 같은 연도의 모든 산업에 같은 비례계수 $k_t$를 곱하므로, 통제 총량 맞춤의 효과는 모든 산업에서 $100\\ln k_{2035}$ %p로 같다(연간 수준 기준).\n",
    "- 기여는 모형이 추정한 과거 공행(동조) 관계에 따른 분해이며 인과 효과가 아니다.\n",
    "\n",
    "점검: (가) 성분의 합 = 합계, (나) 합계 = 분해 경로의 100·ln(2035Q4/2025Q4), (다) 분해 경로의 연간 합 = 3절 바텀업 전망(`bottomup_annual_62.csv`, 소수 셋째 자리 반올림 범위), (라) 과거추세의 재설정분 = $-10\\,(1-\\sum A)\\,\\delta$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "df6285e5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.704460Z",
     "iopub.status.busy": "2026-10-08T07:24:18.704460Z",
     "iopub.status.idle": "2026-10-08T07:24:18.836975Z",
     "shell.execute_reply": "2026-10-08T07:24:18.836975Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "δ = 연 1.524%p · 비례계수 k_2035 = 0.9339 → 통제 총량 맞춤 -6.84%p(모든 산업 공통)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_326ce\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th id=\"T_326ce_level0_col0\" class=\"col_heading level0 col0\" >점검</th>\n",
       "      <th id=\"T_326ce_level0_col1\" class=\"col_heading level0 col1\" >값</th>\n",
       "      <th id=\"T_326ce_level0_col2\" class=\"col_heading level0 col2\" >허용오차</th>\n",
       "      <th id=\"T_326ce_level0_col3\" class=\"col_heading level0 col3\" >통과</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td id=\"T_326ce_row0_col0\" class=\"data row0 col0\" >(가) 성분 합 − 합계, 최대 절대차(%p)</td>\n",
       "      <td id=\"T_326ce_row0_col1\" class=\"data row0 col1\" >0.00e+00</td>\n",
       "      <td id=\"T_326ce_row0_col2\" class=\"data row0 col2\" >1.0e-09</td>\n",
       "      <td id=\"T_326ce_row0_col3\" class=\"data row0 col3\" >True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td id=\"T_326ce_row1_col0\" class=\"data row1 col0\" >(나) 합계 − 100·ln(경로 2035Q4/2025Q4), 최대 절대차(%p)</td>\n",
       "      <td id=\"T_326ce_row1_col1\" class=\"data row1 col1\" >3.89e-13</td>\n",
       "      <td id=\"T_326ce_row1_col2\" class=\"data row1 col2\" >1.0e-09</td>\n",
       "      <td id=\"T_326ce_row1_col3\" class=\"data row1 col3\" >True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td id=\"T_326ce_row2_col0\" class=\"data row2 col0\" >(다) 분해 경로 연간 합 − 바텀업 S1(bottomup_annual_62.csv), 최대 절대차(십억 원)</td>\n",
       "      <td id=\"T_326ce_row2_col1\" class=\"data row2 col1\" >4.99e-04</td>\n",
       "      <td id=\"T_326ce_row2_col2\" class=\"data row2 col2\" >5.0e-04</td>\n",
       "      <td id=\"T_326ce_row2_col3\" class=\"data row2 col3\" >True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td id=\"T_326ce_row3_col0\" class=\"data row3 col0\" >(라) 과거추세 재설정분 − (−10(1−ΣA)δ), 최대 절대차(%p)</td>\n",
       "      <td id=\"T_326ce_row3_col1\" class=\"data row3 col1\" >8.17e-14</td>\n",
       "      <td id=\"T_326ce_row3_col2\" class=\"data row3 col2\" >1.0e-09</td>\n",
       "      <td id=\"T_326ce_row3_col3\" class=\"data row3 col3\" >True</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea3bd24e60>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "spec, prior_k, em = C.SPEC[\"산업\"]\n",
    "args62 = (C.UP[\"levels_62\"], C.UP[\"exog_hist\"], C.UP[\"exog_scen\"], C.UP[\"io_62\"], spec, prior_k, em)\n",
    "sh, path = V.shap_decompose(*args62, anchor=C.ANCHOR_GROWTH, scenario=\"S1\")\n",
    "sh_raw, _ = V.shap_decompose(*args62, anchor=None, scenario=\"S1\")              # 재설정 전 분해(점검 (라)용)\n",
    "L62, _, _, B62, _ = V.fit_full(C.UP[\"levels_62\"], C.UP[\"exog_hist\"], C.UP[\"io_62\"], spec, prior_k, em)\n",
    "n62 = L62.shape[1]\n",
    "lagsum = B62[:V.P * n62, :].reshape(V.P, n62, n62).sum(axis=(0, 1))         # 방정식 i의 내생 시차 계수 합\n",
    "delta = V.anchor_drift(L62, B62, C.ANCHOR_GROWTH)[1]\n",
    "b62s1 = bu[62][bu[62][\"시나리오\"] == \"S1\"].pivot(index=\"연도\", columns=\"산업\", values=R.VAL)\n",
    "path_a = V.q_to_annual(path.drop(index=\"2025Q4\"))[b62s1.columns]\n",
    "k35 = float(fac_all[(fac_all[\"분류\"] == 62) & (fac_all[\"시나리오\"] == \"S1\") & (fac_all[\"연도\"] == C.END_YEAR)][\"비례계수\"].iloc[0])\n",
    "chk = pd.DataFrame([\n",
    "    {\"점검\": \"(가) 성분 합 − 합계, 최대 절대차(%p)\", \"값\": float((sh.drop(columns=\"total\").sum(axis=1) - sh[\"total\"]).abs().max()), \"허용오차\": 1e-9},\n",
    "    {\"점검\": \"(나) 합계 − 100·ln(경로 2035Q4/2025Q4), 최대 절대차(%p)\",\n",
    "     \"값\": float((sh[\"total\"] - 100 * np.log(path.loc[\"2035Q4\"] / path.loc[\"2025Q4\"])).abs().max()), \"허용오차\": 1e-9},\n",
    "    {\"점검\": \"(다) 분해 경로 연간 합 − 바텀업 S1(bottomup_annual_62.csv), 최대 절대차(십억 원)\",\n",
    "     \"값\": float((path_a.loc[2026:2035] - b62s1.loc[2026:2035]).abs().max().max()), \"허용오차\": 0.0005 + 1e-9},\n",
    "    {\"점검\": \"(라) 과거추세 재설정분 − (−10(1−ΣA)δ), 최대 절대차(%p)\",\n",
    "     \"값\": float(((sh[\"drift\"] - sh_raw[\"drift\"]) + 10 * (1 - lagsum) * delta).abs().max()), \"허용오차\": 1e-9},\n",
    "])\n",
    "chk[\"통과\"] = chk[\"값\"] <= chk[\"허용오차\"]\n",
    "chk.to_csv(os.path.join(C.OUTPUT, \"shap_check.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "assert chk[\"통과\"].all(), chk\n",
    "sh.to_csv(os.path.join(C.OUTPUT, \"shap_62.csv\"), encoding=\"utf-8-sig\")\n",
    "meta = {\"version\": \"2026-10 정비판\", \"scenario\": \"S1\", \"period\": \"2025Q4–2035Q4\", \"unit\": \"10년 로그 성장 기여(100·Δln, %p)\",\n",
    "        \"target\": \"정합화 이전 바텀업(드리프트 재설정 후)\", \"delta\": round(delta, 3), \"k_2035\": round(k35, 4),\n",
    "        \"reconcile_2035\": round(100 * np.log(k35), 2)}\n",
    "R.json_dump({\"exog\": V.EXOG, \"meta\": meta,\n",
    "             \"rows\": [{\"ind\": i, **{c: round(float(v), 2) for c, v in r.items()}} for i, r in sh.iterrows()]},\n",
    "            os.path.join(C.OUTPUT, \"shap_62.json\"))\n",
    "print(f\"δ = 연 {delta:.3f}%p · 비례계수 k_2035 = {k35:.4f} → 통제 총량 맞춤 {100 * np.log(k35):+.2f}%p(모든 산업 공통)\")\n",
    "chk.style.format({\"값\": \"{:.2e}\", \"허용오차\": \"{:.1e}\"}).hide(axis=\"index\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5dc38e0",
   "metadata": {},
   "source": [
    "요인별 기여의 크기를 이전 판 분해(카드의 이전 판 자료 `published_prev/va_detail_62.json`: 드리프트 재설정 없음, 실질금리 수준 투입)와 비교한다.\n",
    "이전 판에서 실질금리 수준 경로가 만들던 큰 양(+)의 기여는 1차 차분 투입에서 사라지고, 과거추세는 재설정만큼 작아진다. 아래 둘째 표는 합계 상·하위 5개 산업이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "bf0ab629",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T07:24:18.837988Z",
     "iopub.status.busy": "2026-10-08T07:24:18.837988Z",
     "iopub.status.idle": "2026-10-08T07:24:18.848014Z",
     "shell.execute_reply": "2026-10-08T07:24:18.848014Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "</style>\n",
       "<table id=\"T_5232d\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_5232d_level0_col0\" class=\"col_heading level0 col0\" >정비판 산업 평균(%p)</th>\n",
       "      <th id=\"T_5232d_level0_col1\" class=\"col_heading level0 col1\" >정비판 평균 절대 기여(%p)</th>\n",
       "      <th id=\"T_5232d_level0_col2\" class=\"col_heading level0 col2\" >이전 판 산업 평균(%p)</th>\n",
       "      <th id=\"T_5232d_level0_col3\" class=\"col_heading level0 col3\" >이전 판 평균 절대 기여(%p)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row0\" class=\"row_heading level0 row0\" >합계(10년 로그 성장)</th>\n",
       "      <td id=\"T_5232d_row0_col0\" class=\"data row0 col0\" >10.37</td>\n",
       "      <td id=\"T_5232d_row0_col1\" class=\"data row0 col1\" >21.77</td>\n",
       "      <td id=\"T_5232d_row0_col2\" class=\"data row0 col2\" >34.79</td>\n",
       "      <td id=\"T_5232d_row0_col3\" class=\"data row0 col3\" >36.97</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row1\" class=\"row_heading level0 row1\" >과거추세</th>\n",
       "      <td id=\"T_5232d_row1_col0\" class=\"data row1 col0\" >4.08</td>\n",
       "      <td id=\"T_5232d_row1_col1\" class=\"data row1 col1\" >23.53</td>\n",
       "      <td id=\"T_5232d_row1_col2\" class=\"data row1 col2\" >11.71</td>\n",
       "      <td id=\"T_5232d_row1_col3\" class=\"data row1 col3\" >29.02</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row2\" class=\"row_heading level0 row2\" >산업 동학</th>\n",
       "      <td id=\"T_5232d_row2_col0\" class=\"data row2 col0\" >5.46</td>\n",
       "      <td id=\"T_5232d_row2_col1\" class=\"data row2 col1\" >14.21</td>\n",
       "      <td id=\"T_5232d_row2_col2\" class=\"data row2 col2\" >-2.18</td>\n",
       "      <td id=\"T_5232d_row2_col3\" class=\"data row2 col3\" >18.61</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row3\" class=\"row_heading level0 row3\" >유가</th>\n",
       "      <td id=\"T_5232d_row3_col0\" class=\"data row3 col0\" >0.69</td>\n",
       "      <td id=\"T_5232d_row3_col1\" class=\"data row3 col1\" >0.90</td>\n",
       "      <td id=\"T_5232d_row3_col2\" class=\"data row3 col2\" >3.83</td>\n",
       "      <td id=\"T_5232d_row3_col3\" class=\"data row3 col3\" >4.75</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row4\" class=\"row_heading level0 row4\" >세계수요</th>\n",
       "      <td id=\"T_5232d_row4_col0\" class=\"data row4 col0\" >-0.34</td>\n",
       "      <td id=\"T_5232d_row4_col1\" class=\"data row4 col1\" >0.83</td>\n",
       "      <td id=\"T_5232d_row4_col2\" class=\"data row4 col2\" >-0.35</td>\n",
       "      <td id=\"T_5232d_row4_col3\" class=\"data row4 col3\" >0.92</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row5\" class=\"row_heading level0 row5\" >실질금리</th>\n",
       "      <td id=\"T_5232d_row5_col0\" class=\"data row5 col0\" >0.02</td>\n",
       "      <td id=\"T_5232d_row5_col1\" class=\"data row5 col1\" >0.06</td>\n",
       "      <td id=\"T_5232d_row5_col2\" class=\"data row5 col2\" >19.97</td>\n",
       "      <td id=\"T_5232d_row5_col3\" class=\"data row5 col3\" >25.19</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row6\" class=\"row_heading level0 row6\" >실질환율</th>\n",
       "      <td id=\"T_5232d_row6_col0\" class=\"data row6 col0\" >0.02</td>\n",
       "      <td id=\"T_5232d_row6_col1\" class=\"data row6 col1\" >0.72</td>\n",
       "      <td id=\"T_5232d_row6_col2\" class=\"data row6 col2\" >0.06</td>\n",
       "      <td id=\"T_5232d_row6_col3\" class=\"data row6 col3\" >0.27</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row7\" class=\"row_heading level0 row7\" >고용_취업자</th>\n",
       "      <td id=\"T_5232d_row7_col0\" class=\"data row7 col0\" >-0.01</td>\n",
       "      <td id=\"T_5232d_row7_col1\" class=\"data row7 col1\" >0.02</td>\n",
       "      <td id=\"T_5232d_row7_col2\" class=\"data row7 col2\" >1.39</td>\n",
       "      <td id=\"T_5232d_row7_col3\" class=\"data row7 col3\" >1.74</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_5232d_level0_row8\" class=\"row_heading level0 row8\" >우리경기</th>\n",
       "      <td id=\"T_5232d_row8_col0\" class=\"data row8 col0\" >0.45</td>\n",
       "      <td id=\"T_5232d_row8_col1\" class=\"data row8 col1\" >0.78</td>\n",
       "      <td id=\"T_5232d_row8_col2\" class=\"data row8 col2\" >0.37</td>\n",
       "      <td id=\"T_5232d_row8_col3\" class=\"data row8 col3\" >0.55</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x1ea3bd53ec0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>total</th>\n",
       "      <th>drift</th>\n",
       "      <th>dynamics</th>\n",
       "      <th>유가</th>\n",
       "      <th>세계수요</th>\n",
       "      <th>실질금리</th>\n",
       "      <th>실질환율</th>\n",
       "      <th>고용_취업자</th>\n",
       "      <th>우리경기</th>\n",
       "      <th>최대외생</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>산업</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>23.의료ㆍ정밀ㆍ광학기기ㆍ시계</th>\n",
       "      <td>74.960</td>\n",
       "      <td>97.780</td>\n",
       "      <td>-24.450</td>\n",
       "      <td>1.140</td>\n",
       "      <td>0.500</td>\n",
       "      <td>0.030</td>\n",
       "      <td>-0.570</td>\n",
       "      <td>-0.010</td>\n",
       "      <td>0.530</td>\n",
       "      <td>유가</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비</th>\n",
       "      <td>74.790</td>\n",
       "      <td>78.000</td>\n",
       "      <td>-3.880</td>\n",
       "      <td>0.850</td>\n",
       "      <td>0.200</td>\n",
       "      <td>0.070</td>\n",
       "      <td>-0.860</td>\n",
       "      <td>-0.010</td>\n",
       "      <td>0.410</td>\n",
       "      <td>실질환율</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>58.사회복지 서비스업</th>\n",
       "      <td>45.460</td>\n",
       "      <td>52.720</td>\n",
       "      <td>-7.650</td>\n",
       "      <td>0.170</td>\n",
       "      <td>-0.330</td>\n",
       "      <td>-0.080</td>\n",
       "      <td>0.450</td>\n",
       "      <td>-0.030</td>\n",
       "      <td>0.200</td>\n",
       "      <td>실질환율</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>57.의료ㆍ보건업</th>\n",
       "      <td>44.800</td>\n",
       "      <td>51.460</td>\n",
       "      <td>-7.060</td>\n",
       "      <td>0.210</td>\n",
       "      <td>-0.350</td>\n",
       "      <td>-0.070</td>\n",
       "      <td>0.420</td>\n",
       "      <td>-0.020</td>\n",
       "      <td>0.220</td>\n",
       "      <td>실질환율</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>47.보험ㆍ연금업</th>\n",
       "      <td>44.540</td>\n",
       "      <td>30.150</td>\n",
       "      <td>15.730</td>\n",
       "      <td>-0.540</td>\n",
       "      <td>0.190</td>\n",
       "      <td>0.140</td>\n",
       "      <td>-0.220</td>\n",
       "      <td>0.020</td>\n",
       "      <td>-0.920</td>\n",
       "      <td>우리경기</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9.섬유ㆍ의복</th>\n",
       "      <td>-33.150</td>\n",
       "      <td>-30.670</td>\n",
       "      <td>-4.300</td>\n",
       "      <td>1.780</td>\n",
       "      <td>-0.420</td>\n",
       "      <td>0.050</td>\n",
       "      <td>-0.580</td>\n",
       "      <td>-0.030</td>\n",
       "      <td>1.020</td>\n",
       "      <td>유가</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10.가죽제품</th>\n",
       "      <td>-33.590</td>\n",
       "      <td>-32.810</td>\n",
       "      <td>-2.540</td>\n",
       "      <td>1.800</td>\n",
       "      <td>-0.490</td>\n",
       "      <td>0.050</td>\n",
       "      <td>-0.570</td>\n",
       "      <td>-0.030</td>\n",
       "      <td>1.000</td>\n",
       "      <td>유가</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5.금속ㆍ비금속광업</th>\n",
       "      <td>-34.120</td>\n",
       "      <td>-48.830</td>\n",
       "      <td>11.400</td>\n",
       "      <td>0.060</td>\n",
       "      <td>0.380</td>\n",
       "      <td>-0.010</td>\n",
       "      <td>1.620</td>\n",
       "      <td>-0.010</td>\n",
       "      <td>1.250</td>\n",
       "      <td>실질환율</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.석탄ㆍ원유ㆍ천연가스</th>\n",
       "      <td>-34.150</td>\n",
       "      <td>-61.670</td>\n",
       "      <td>23.770</td>\n",
       "      <td>0.180</td>\n",
       "      <td>0.340</td>\n",
       "      <td>-0.000</td>\n",
       "      <td>1.550</td>\n",
       "      <td>-0.010</td>\n",
       "      <td>1.680</td>\n",
       "      <td>우리경기</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14.석탄ㆍ석유제품</th>\n",
       "      <td>-60.600</td>\n",
       "      <td>-46.440</td>\n",
       "      <td>-19.620</td>\n",
       "      <td>6.210</td>\n",
       "      <td>-2.070</td>\n",
       "      <td>-0.000</td>\n",
       "      <td>-0.270</td>\n",
       "      <td>-0.000</td>\n",
       "      <td>1.600</td>\n",
       "      <td>유가</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                         total   drift  dynamics     유가   세계수요   실질금리   실질환율  \\\n",
       "산업                                                                             \n",
       "23.의료ㆍ정밀ㆍ광학기기ㆍ시계        74.960  97.780   -24.450  1.140  0.500  0.030 -0.570   \n",
       "22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비  74.790  78.000    -3.880  0.850  0.200  0.070 -0.860   \n",
       "58.사회복지 서비스업            45.460  52.720    -7.650  0.170 -0.330 -0.080  0.450   \n",
       "57.의료ㆍ보건업               44.800  51.460    -7.060  0.210 -0.350 -0.070  0.420   \n",
       "47.보험ㆍ연금업               44.540  30.150    15.730 -0.540  0.190  0.140 -0.220   \n",
       "9.섬유ㆍ의복                -33.150 -30.670    -4.300  1.780 -0.420  0.050 -0.580   \n",
       "10.가죽제품                -33.590 -32.810    -2.540  1.800 -0.490  0.050 -0.570   \n",
       "5.금속ㆍ비금속광업             -34.120 -48.830    11.400  0.060  0.380 -0.010  1.620   \n",
       "4.석탄ㆍ원유ㆍ천연가스           -34.150 -61.670    23.770  0.180  0.340 -0.000  1.550   \n",
       "14.석탄ㆍ석유제품             -60.600 -46.440   -19.620  6.210 -2.070 -0.000 -0.270   \n",
       "\n",
       "                        고용_취업자   우리경기  최대외생  \n",
       "산업                                           \n",
       "23.의료ㆍ정밀ㆍ광학기기ㆍ시계        -0.010  0.530    유가  \n",
       "22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비  -0.010  0.410  실질환율  \n",
       "58.사회복지 서비스업            -0.030  0.200  실질환율  \n",
       "57.의료ㆍ보건업               -0.020  0.220  실질환율  \n",
       "47.보험ㆍ연금업                0.020 -0.920  우리경기  \n",
       "9.섬유ㆍ의복                 -0.030  1.020    유가  \n",
       "10.가죽제품                 -0.030  1.000    유가  \n",
       "5.금속ㆍ비금속광업              -0.010  1.250  실질환율  \n",
       "4.석탄ㆍ원유ㆍ천연가스            -0.010  1.680  우리경기  \n",
       "14.석탄ㆍ석유제품              -0.000  1.600    유가  "
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vd_prev = json.load(open(os.path.join(C.PREV_DIR, \"va_detail_62.json\"), encoding=\"utf-8\"))\n",
    "sp = pd.DataFrame([{\"total\": x[\"shap\"][\"total\"], \"drift\": x[\"shap\"][\"drift\"], \"dynamics\": x[\"shap\"][\"dynamics\"], **x[\"shap\"][\"exog\"]}\n",
    "                   for x in vd_prev[\"inds\"]])\n",
    "cols = [\"total\", \"drift\", \"dynamics\"] + V.EXOG\n",
    "NAME = {\"total\": \"합계(10년 로그 성장)\", \"drift\": \"과거추세\", \"dynamics\": \"산업 동학\"}\n",
    "cmp_ = pd.DataFrame({\"정비판 산업 평균(%p)\": sh[cols].mean().values, \"정비판 평균 절대 기여(%p)\": sh[cols].abs().mean().values,\n",
    "                     \"이전 판 산업 평균(%p)\": sp[cols].mean().values, \"이전 판 평균 절대 기여(%p)\": sp[cols].abs().mean().values},\n",
    "                    index=[NAME.get(c, c) for c in cols])\n",
    "display(cmp_.style.format(\"{:.2f}\"))\n",
    "top = sh.assign(최대외생=sh[V.EXOG].abs().idxmax(axis=1)).sort_values(\"total\", ascending=False)\n",
    "pd.concat([top.head(5), top.tail(5)])[cols + [\"최대외생\"]].round(2)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
