{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a8152bab",
   "metadata": {},
   "source": [
    "# 02 분석 — 베이지언 VARX 12개 옵션의 추정과 예측력 평가\n",
    "\n",
    "**모형.** 산업 $n$개의 내생 벡터 $y_t$에 대해\n",
    "\n",
    "$$y_t = \\sum_{l=1}^{4} A_l\\, y_{t-l} \\;+\\; \\alpha\\, ect_{t-1} \\;+\\; \\sum_{s=0}^{2} \\Gamma_s\\, x_{t-s} \\;+\\; c \\;+\\; e_t$$\n",
    "\n",
    "이다. $x_t$는 외생 6종이며 VAR 옵션에서는 뺀다. 오차수정항 $ect$는 VECM 명세에만 들어간다.\n",
    "\n",
    "| 축 | 선택지 | 내용 |\n",
    "|---|---|---|\n",
    "| 명세 | 차분 | $y=\\Delta(100\\log VA)$, 계수 사전평균 0 |\n",
    "| | 수준 | $y=100\\log VA$, 자기 1차 시차 계수의 사전평균 1(임의보행) |\n",
    "| | VECM | $y=\\Delta(100\\log VA)$, $ect_i = 100\\log VA_i - 100\\log(\\text{총량})$, 공적분 벡터 $(1,-1)$ 고정 |\n",
    "| 사전 | 미네소타 | 자기 시차 $\\lambda^2/l^2$, 교차 시차 $\\lambda^2/l^2\\cdot\\sigma_i^2/\\sigma_j^2$ |\n",
    "| | 산업연관표 | 교차 시차에 $(\\lvert W_{ij}\\rvert+0.05)$를 곱한다(후방연쇄가 강한 쌍은 느슨하게) |\n",
    "| 외생 | VARX / VAR | 외생 6종의 동시·1·2기 시차 포함 여부 |\n",
    "\n",
    "외생 계수의 사전분산은 $4\\lambda^2$, 오차수정항은 $\\lambda^2$, 상수는 $10^6$이다. $\\lambda=0.2$, 시차 $P=4$, 외생 시차 $Q=2$이다.\n",
    "\n",
    "**추정.** 방정식마다 정규 사전분포와 정규 우도를 결합한 사후평균을 쓴다. 오차분산 $\\sigma_i^2$는 단변량 AR(4) 잔차분산으로 고정한다.\n",
    "\n",
    "$$\\hat b_i = \\left(V_i^{-1} + X'X/\\sigma_i^2\\right)^{-1}\\left(V_i^{-1} b_{0,i} + X'y_i/\\sigma_i^2\\right)$$\n",
    "\n",
    "닫힌 형태의 계산이므로 표본추출(MCMC)이나 난수가 없고, 같은 입력이면 같은 결과가 나온다.\n",
    "\n",
    "**평가.** 모든 명세를 수준(100·log VA)의 1기 앞 예측으로 환산해 산업·분기 전체의 RMSE를 구한다(단위는 근사적으로 % 수준 오차).\n",
    "train RMSE는 2000–2021 적합값, valid RMSE는 train 계수로 2022–2023을 예측한 값, test RMSE는 train+valid로 다시 적합한 계수로 2024–2025를 예측한 값이다.\n",
    "예측 시점의 시차값과 외생은 실적을 쓴다(조건부 1기 앞 예측). 옵션 선택은 valid, 최종 성능 보고는 test 기준이다.\n",
    "\n",
    "**산출물.** `output/grid_{36,62,69,AGG}.csv`, `grid_prev_{36,62,69,AGG}.csv`, `check_prev_vs_new.csv`, `real_rate_coef.csv`, `real_rate_sign.csv`, `check_io36_normalized.csv`, `lambda_sensitivity.csv`, `decomposition_check_62.csv`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c4f92d0f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:22.283367Z",
     "iopub.status.busy": "2026-10-08T00:05:22.282868Z",
     "iopub.status.idle": "2026-10-08T00:05:22.763695Z",
     "shell.execute_reply": "2026-10-08T00:05:22.763695Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python 3.12.10 · pandas 2.2.3 · numpy 2.4.6 · matplotlib 3.11.0 · BLAS 스레드 1\n",
      "표본 2000Q1–2025Q4 · train ~2021Q4 · valid ~2023Q4 · test 이후 · 옵션 12개 · P=4, Q=2, λ=0.2\n",
      "  있음  data\\inputs\\levels_sa.csv\n",
      "  있음  data\\inputs\\levels_62.csv\n",
      "  있음  data\\inputs\\levels_69.csv\n",
      "  있음  data\\inputs\\levels_agg.csv\n",
      "  있음  data\\inputs\\io_linkage_W.csv\n",
      "  있음  data\\inputs\\io_linkage_62.csv\n",
      "  있음  data\\inputs\\io_linkage_69.csv\n",
      "  있음  data\\inputs\\exog_hist_v2_20260828.csv\n",
      "  있음  data\\inputs\\exog_hist_v2.csv\n",
      "{'36': (104, 36), '62': (104, 62), '69': (104, 69), 'AGG': (104, 1)}\n"
     ]
    }
   ],
   "source": [
    "import config as C          # 선형대수 스레드를 1개로 고정하므로 numpy보다 먼저 불러온다\n",
    "import os, hashlib, time\n",
    "import numpy as np, pandas as pd\n",
    "import varx_grid as V\n",
    "pd.set_option(\"display.float_format\", lambda v: f\"{v:,.3f}\")\n",
    "pd.set_option(\"display.max_columns\", 30)\n",
    "C.setup()\n",
    "\n",
    "def rd(name):\n",
    "    # 소수 끝자리까지 원값을 되살리도록 round_trip 파서를 쓴다\n",
    "    return pd.read_csv(os.path.join(C.INTERIM, name), index_col=0, encoding=\"utf-8-sig\", float_precision=\"round_trip\")\n",
    "\n",
    "split = rd(\"split.csv\")[\"구간\"]\n",
    "EX = {\"new\": rd(\"exog_new.csv\"), \"prev\": rd(\"exog_prev.csv\")}\n",
    "PANEL = {}\n",
    "for g in C.GRIDS:\n",
    "    pan = rd(f\"logpanel_{g}.csv\")\n",
    "    f = os.path.join(C.INTERIM, f\"io_{g}.csv\")\n",
    "    PANEL[g] = (pan.drop(columns=C.GDP_COL), pan[C.GDP_COL], rd(f\"io_{g}.csv\") if os.path.exists(f) else None)\n",
    "print({g: PANEL[g][0].shape for g in C.GRIDS})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0945b527",
   "metadata": {},
   "source": [
    "## 1. 표본 분할\n",
    "\n",
    "train 88분기로 계수를 추정하고, valid 8분기로 옵션을 고르고, test 8분기로 최종 성능을 확인한다.\n",
    "차분·VECM 명세는 첫 분기의 차분이 없으므로 train 적합 구간이 한 분기 짧다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2d0785b9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:22.764698Z",
     "iopub.status.busy": "2026-10-08T00:05:22.764698Z",
     "iopub.status.idle": "2026-10-08T00:05:22.772037Z",
     "shell.execute_reply": "2026-10-08T00:05:22.772037Z"
    }
   },
   "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>끝</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>구간</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>train</th>\n",
       "      <td>88</td>\n",
       "      <td>2000Q1</td>\n",
       "      <td>2021Q4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>valid</th>\n",
       "      <td>8</td>\n",
       "      <td>2022Q1</td>\n",
       "      <td>2023Q4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>test</th>\n",
       "      <td>8</td>\n",
       "      <td>2024Q1</td>\n",
       "      <td>2025Q4</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       분기 수      시작       끝\n",
       "구간                         \n",
       "train    88  2000Q1  2021Q4\n",
       "valid     8  2022Q1  2023Q4\n",
       "test      8  2024Q1  2025Q4"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "seg = split.value_counts().reindex([\"train\", \"valid\", \"test\"])\n",
    "pd.DataFrame({\"분기 수\": seg, \"시작\": [split[split == s].index[0] for s in seg.index], \"끝\": [split[split == s].index[-1] for s in seg.index]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "843c763d",
   "metadata": {},
   "source": [
    "## 2. 산업연관표 사전분포의 구조 — 62분류 예시\n",
    "\n",
    "반도체가 속한 62분류 '전자부품·컴퓨터·영상·음향·통신장비' 방정식에서, 다른 산업의 시차 계수에 대한 사전분산 배율(미네소타 대비)을 보인다.\n",
    "배율이 1보다 크면 미네소타보다 느슨하고, 0.05에 가까우면 계수를 0 쪽으로 강하게 묶는다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "530b8cd7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:22.773040Z",
     "iopub.status.busy": "2026-10-08T00:05:22.773040Z",
     "iopub.status.idle": "2026-10-08T00:05:22.779218Z",
     "shell.execute_reply": "2026-10-08T00:05:22.778713Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "방정식: 22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비 · 배율 평균 0.61 · 중앙값 0.23\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|+0.05)</th>\n",
       "      <th>사전 표준편차 배율</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>28.가구ㆍ기타제품</th>\n",
       "      <td>4.339</td>\n",
       "      <td>2.083</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>34.도매ㆍ소매업</th>\n",
       "      <td>3.778</td>\n",
       "      <td>1.944</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29.전기ㆍ가스ㆍ증기ㆍ공기조절공급업</th>\n",
       "      <td>3.222</td>\n",
       "      <td>1.795</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18.고무ㆍ플라스틱제품</th>\n",
       "      <td>2.230</td>\n",
       "      <td>1.493</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20.1차 금속</th>\n",
       "      <td>1.881</td>\n",
       "      <td>1.372</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.임업</th>\n",
       "      <td>0.100</td>\n",
       "      <td>0.316</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.어업</th>\n",
       "      <td>0.100</td>\n",
       "      <td>0.316</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.석탄ㆍ원유ㆍ천연가스</th>\n",
       "      <td>0.100</td>\n",
       "      <td>0.316</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                     사전분산 배율(|W|+0.05)  사전 표준편차 배율\n",
       "28.가구ㆍ기타제품                       4.339       2.083\n",
       "34.도매ㆍ소매업                        3.778       1.944\n",
       "29.전기ㆍ가스ㆍ증기ㆍ공기조절공급업              3.222       1.795\n",
       "18.고무ㆍ플라스틱제품                     2.230       1.493\n",
       "20.1차 금속                         1.881       1.372\n",
       "2.임업                             0.100       0.316\n",
       "3.어업                             0.100       0.316\n",
       "4.석탄ㆍ원유ㆍ천연가스                     0.100       0.316"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "L62, gdp62, W62 = PANEL[\"62\"]\n",
    "eq = next(c for c in L62.columns if c.startswith(\"22.\"))\n",
    "mult = W62.loc[eq].drop(eq).abs() + V.IO_FLOOR\n",
    "tbl = pd.concat([mult.nlargest(5), mult.nsmallest(3)]).rename(\"사전분산 배율(|W|+0.05)\").to_frame()\n",
    "tbl[\"사전 표준편차 배율\"] = np.sqrt(tbl.iloc[:, 0])\n",
    "print(f\"방정식: {eq} · 배율 평균 {mult.mean():.2f} · 중앙값 {mult.median():.2f}\")\n",
    "tbl"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdbbe524",
   "metadata": {},
   "source": [
    "## 3. 12개 옵션 추정 — 36·62·69개 산업과 총량\n",
    "\n",
    "네 분류 모두 기본값(λ=0.2)과 정비판 외생(실질금리 1차 차분)으로 12개 옵션을 추정한다. 결과는 반올림하지 않고 저장한다.\n",
    "총량(AGG)은 내생 계열이 하나이므로 교차 시차가 없어 두 사전분포가 같은 모형이 되고, 오차수정항이 항상 0이어서 VECM이 차분과 같아진다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "99e05831",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:22.780216Z",
     "iopub.status.busy": "2026-10-08T00:05:22.779716Z",
     "iopub.status.idle": "2026-10-08T00:05:25.550261Z",
     "shell.execute_reply": "2026-10-08T00:05:25.549755Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " 36: 산업 36개 ·  0.2초 · valid 최소 차분_io_varx"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " 62: 산업 62개 ·  0.8초 · valid 최소 차분_io_varx\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " 69: 산업 69개 ·  1.7초 · valid 최소 차분_io_varx\n",
      "AGG: 산업  1개 ·  0.0초 · valid 최소 수준_minnesota_var = 수준_io_var\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 tr th {\n",
       "        text-align: left;\n",
       "    }\n",
       "\n",
       "    .dataframe thead tr:last-of-type th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th colspan=\"2\" halign=\"left\">36</th>\n",
       "      <th colspan=\"2\" halign=\"left\">62</th>\n",
       "      <th colspan=\"2\" halign=\"left\">69</th>\n",
       "      <th colspan=\"2\" halign=\"left\">AGG</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th>RMSE_valid</th>\n",
       "      <th>RMSE_test</th>\n",
       "      <th>RMSE_valid</th>\n",
       "      <th>RMSE_test</th>\n",
       "      <th>RMSE_valid</th>\n",
       "      <th>RMSE_test</th>\n",
       "      <th>RMSE_valid</th>\n",
       "      <th>RMSE_test</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",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>차분_io_varx</th>\n",
       "      <td>6.744</td>\n",
       "      <td>3.106</td>\n",
       "      <td>6.181</td>\n",
       "      <td>3.306</td>\n",
       "      <td>6.230</td>\n",
       "      <td>3.449</td>\n",
       "      <td>0.489</td>\n",
       "      <td>0.632</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>차분_minnesota_varx</th>\n",
       "      <td>7.296</td>\n",
       "      <td>3.626</td>\n",
       "      <td>6.188</td>\n",
       "      <td>3.332</td>\n",
       "      <td>6.232</td>\n",
       "      <td>3.525</td>\n",
       "      <td>0.489</td>\n",
       "      <td>0.632</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>차분_io_var</th>\n",
       "      <td>6.885</td>\n",
       "      <td>2.916</td>\n",
       "      <td>6.316</td>\n",
       "      <td>3.274</td>\n",
       "      <td>6.358</td>\n",
       "      <td>3.443</td>\n",
       "      <td>0.564</td>\n",
       "      <td>0.728</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>차분_minnesota_var</th>\n",
       "      <td>7.405</td>\n",
       "      <td>3.597</td>\n",
       "      <td>6.355</td>\n",
       "      <td>3.322</td>\n",
       "      <td>6.389</td>\n",
       "      <td>3.581</td>\n",
       "      <td>0.564</td>\n",
       "      <td>0.728</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_io_varx</th>\n",
       "      <td>8.660</td>\n",
       "      <td>4.153</td>\n",
       "      <td>7.490</td>\n",
       "      <td>4.091</td>\n",
       "      <td>7.929</td>\n",
       "      <td>4.969</td>\n",
       "      <td>0.489</td>\n",
       "      <td>0.632</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_minnesota_varx</th>\n",
       "      <td>8.111</td>\n",
       "      <td>4.173</td>\n",
       "      <td>7.600</td>\n",
       "      <td>4.155</td>\n",
       "      <td>8.067</td>\n",
       "      <td>4.963</td>\n",
       "      <td>0.489</td>\n",
       "      <td>0.632</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_io_var</th>\n",
       "      <td>8.739</td>\n",
       "      <td>4.196</td>\n",
       "      <td>7.837</td>\n",
       "      <td>4.215</td>\n",
       "      <td>8.344</td>\n",
       "      <td>4.867</td>\n",
       "      <td>0.564</td>\n",
       "      <td>0.728</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_minnesota_var</th>\n",
       "      <td>8.387</td>\n",
       "      <td>4.166</td>\n",
       "      <td>7.906</td>\n",
       "      <td>4.286</td>\n",
       "      <td>8.395</td>\n",
       "      <td>5.008</td>\n",
       "      <td>0.564</td>\n",
       "      <td>0.728</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_io_varx</th>\n",
       "      <td>10.325</td>\n",
       "      <td>3.804</td>\n",
       "      <td>12.476</td>\n",
       "      <td>4.485</td>\n",
       "      <td>12.475</td>\n",
       "      <td>4.442</td>\n",
       "      <td>0.419</td>\n",
       "      <td>0.554</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_minnesota_varx</th>\n",
       "      <td>13.939</td>\n",
       "      <td>4.755</td>\n",
       "      <td>13.426</td>\n",
       "      <td>4.539</td>\n",
       "      <td>12.944</td>\n",
       "      <td>5.128</td>\n",
       "      <td>0.419</td>\n",
       "      <td>0.554</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_io_var</th>\n",
       "      <td>10.417</td>\n",
       "      <td>3.827</td>\n",
       "      <td>13.566</td>\n",
       "      <td>4.907</td>\n",
       "      <td>13.491</td>\n",
       "      <td>4.735</td>\n",
       "      <td>0.414</td>\n",
       "      <td>0.554</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_minnesota_var</th>\n",
       "      <td>14.823</td>\n",
       "      <td>5.178</td>\n",
       "      <td>14.118</td>\n",
       "      <td>4.866</td>\n",
       "      <td>13.795</td>\n",
       "      <td>5.279</td>\n",
       "      <td>0.414</td>\n",
       "      <td>0.554</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            36                   62                   69  \\\n",
       "                    RMSE_valid RMSE_test RMSE_valid RMSE_test RMSE_valid   \n",
       "옵션                                                                         \n",
       "차분_io_varx               6.744     3.106      6.181     3.306      6.230   \n",
       "차분_minnesota_varx        7.296     3.626      6.188     3.332      6.232   \n",
       "차분_io_var                6.885     2.916      6.316     3.274      6.358   \n",
       "차분_minnesota_var         7.405     3.597      6.355     3.322      6.389   \n",
       "VECM_io_varx             8.660     4.153      7.490     4.091      7.929   \n",
       "VECM_minnesota_varx      8.111     4.173      7.600     4.155      8.067   \n",
       "VECM_io_var              8.739     4.196      7.837     4.215      8.344   \n",
       "VECM_minnesota_var       8.387     4.166      7.906     4.286      8.395   \n",
       "수준_io_varx              10.325     3.804     12.476     4.485     12.475   \n",
       "수준_minnesota_varx       13.939     4.755     13.426     4.539     12.944   \n",
       "수준_io_var               10.417     3.827     13.566     4.907     13.491   \n",
       "수준_minnesota_var        14.823     5.178     14.118     4.866     13.795   \n",
       "\n",
       "                                     AGG            \n",
       "                    RMSE_test RMSE_valid RMSE_test  \n",
       "옵션                                                  \n",
       "차분_io_varx              3.449      0.489     0.632  \n",
       "차분_minnesota_varx       3.525      0.489     0.632  \n",
       "차분_io_var               3.443      0.564     0.728  \n",
       "차분_minnesota_var        3.581      0.564     0.728  \n",
       "VECM_io_varx            4.969      0.489     0.632  \n",
       "VECM_minnesota_varx     4.963      0.489     0.632  \n",
       "VECM_io_var             4.867      0.564     0.728  \n",
       "VECM_minnesota_var      5.008      0.564     0.728  \n",
       "수준_io_varx              4.442      0.419     0.554  \n",
       "수준_minnesota_varx       5.128      0.419     0.554  \n",
       "수준_io_var               4.735      0.414     0.554  \n",
       "수준_minnesota_var        5.279      0.414     0.554  "
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "grids, times = {}, {}\n",
    "for g in C.GRIDS:\n",
    "    L, gdp, W = PANEL[g]\n",
    "    t0 = time.time()\n",
    "    grids[g] = V.run_grid(L, gdp, EX[\"new\"], W, split, C.P, C.Q, C.LAM)\n",
    "    times[g] = time.time() - t0\n",
    "    grids[g].to_csv(os.path.join(C.OUTPUT, f\"grid_{g}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "    print(f\"{g:>3}: 산업 {L.shape[1]:>2}개 · {times[g]:4.1f}초 · valid 최소 {' = '.join(V.select(grids[g]))}\")\n",
    "wide = pd.concat({g: d.set_index(\"옵션\")[[\"RMSE_valid\", \"RMSE_test\"]] for g, d in grids.items()}, axis=1)\n",
    "wide.sort_values((\"62\", \"RMSE_valid\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1e932bc",
   "metadata": {},
   "source": [
    "## 4. 점검 A — 이전 판 그리드의 재현과 정비의 영향\n",
    "\n",
    "같은 계산을 이전 판 외생(2026-08-28판, 실질금리 수준)으로 반복한다. 결과는 이전 공표 그리드(`data/published_prev/`)와 반올림 범위에서 같아야 하며(03 검증),\n",
    "외생이 없는 VAR 옵션은 두 판에서 결과가 같아야 한다. 두 판의 차이(정비판 − 이전 판)가 실질금리 정의 변경의 영향이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "9ee904d6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:25.551258Z",
     "iopub.status.busy": "2026-10-08T00:05:25.551258Z",
     "iopub.status.idle": "2026-10-08T00:05:28.643005Z",
     "shell.execute_reply": "2026-10-08T00:05:28.642501Z"
    }
   },
   "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>최대 |Δtrain|</th>\n",
       "      <th>최대 |Δvalid|</th>\n",
       "      <th>최대 |Δtest|</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",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>0.015</td>\n",
       "      <td>0.049</td>\n",
       "      <td>0.068</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>0.011</td>\n",
       "      <td>0.013</td>\n",
       "      <td>0.084</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>69</th>\n",
       "      <td>0.015</td>\n",
       "      <td>0.022</td>\n",
       "      <td>0.046</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>AGG</th>\n",
       "      <td>0.049</td>\n",
       "      <td>0.064</td>\n",
       "      <td>0.046</td>\n",
       "      <td>수준_minnesota_var = 수준_io_var</td>\n",
       "      <td>수준_minnesota_var = 수준_io_var</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     최대 |Δtrain|  최대 |Δvalid|  최대 |Δtest|                      선택(이전 판)  \\\n",
       "분류                                                                        \n",
       "36         0.015        0.049       0.068                    차분_io_varx   \n",
       "62         0.011        0.013       0.084                    차분_io_varx   \n",
       "69         0.015        0.022       0.046                    차분_io_varx   \n",
       "AGG        0.049        0.064       0.046  수준_minnesota_var = 수준_io_var   \n",
       "\n",
       "                          선택(정비판)  \n",
       "분류                                 \n",
       "36                     차분_io_varx  \n",
       "62                     차분_io_varx  \n",
       "69                     차분_io_varx  \n",
       "AGG  수준_minnesota_var = 수준_io_var  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chk_rows, summ, prev = [], [], {}\n",
    "M = [\"RMSE_train\", \"RMSE_valid\", \"RMSE_test\"]\n",
    "for g in C.GRIDS:\n",
    "    L, gdp, W = PANEL[g]\n",
    "    prev[g] = V.run_grid(L, gdp, EX[\"prev\"], W, split, C.P, C.Q, C.LAM)\n",
    "    prev[g].to_csv(os.path.join(C.OUTPUT, f\"grid_prev_{g}.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "    d = grids[g].set_index(\"옵션\")[M] - prev[g].set_index(\"옵션\")[M]\n",
    "    chk_rows.append(grids[g].assign(분류=g).merge(d.add_prefix(\"Δ\").reset_index(), on=\"옵션\"))\n",
    "    assert d[d.index.str.endswith(\"_var\")].abs().max().max() == 0.0, \"외생이 없는 옵션이 달라졌다\"\n",
    "    summ.append({\"분류\": g, \"최대 |Δtrain|\": d[\"RMSE_train\"].abs().max(), \"최대 |Δvalid|\": d[\"RMSE_valid\"].abs().max(),\n",
    "                 \"최대 |Δtest|\": d[\"RMSE_test\"].abs().max(),\n",
    "                 \"선택(이전 판)\": \" = \".join(V.select(prev[g])), \"선택(정비판)\": \" = \".join(V.select(grids[g]))})\n",
    "pd.concat(chk_rows).to_csv(os.path.join(C.OUTPUT, \"check_prev_vs_new.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "exog_summary = pd.DataFrame(summ).set_index(\"분류\")\n",
    "exog_summary.to_csv(os.path.join(C.OUTPUT, \"check_prev_vs_new_summary.csv\"), encoding=\"utf-8-sig\")\n",
    "exog_summary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bfd077e",
   "metadata": {},
   "source": [
    "36·62·69분류의 선택(차분·산업연관표·VARX)은 바뀌지 않는다. 총량(AGG)은 정비 전후 모두 수준·VAR이 valid 최소이다.\n",
    "5장은 총량 경로를 BOKDPM 기준선에서 가져오고, 총량 VARX(수준·산업연관표·VARX)는 시나리오 간 차이를 계산하는 데만 쓴다(5장 패키지)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83dc49ee",
   "metadata": {},
   "source": [
    "## 4-1. 실질금리 계수의 부호 점검\n",
    "\n",
    "선택 옵션(차분·산업연관표·VARX)을 5장 전망과 같이 전체 표본(2000–2025)으로 적합하고, 산업별 실질금리 계수(동시·1기·2기 시차와 합계)를 두 판에서 비교한다.\n",
    "IS 곡선에 따르면 실질금리가 오르면 산출이 줄므로 계수는 음(−)이어야 한다. 이전 판은 실질금리 수준을 성장률 식에 넣어, 두 계열의 공통 하락 추세가 양(+)의 계수로 나타났다.\n",
    "가중평균은 2025년 부가가치 비중으로 낸다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "88c1e0d7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:28.644007Z",
     "iopub.status.busy": "2026-10-08T00:05:28.643005Z",
     "iopub.status.idle": "2026-10-08T00:05:28.911383Z",
     "shell.execute_reply": "2026-10-08T00:05:28.910880Z"
    }
   },
   "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>동시항 음수 비율(%)</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",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th rowspan=\"2\" valign=\"top\">36</th>\n",
       "      <th>정비판(1차 차분)</th>\n",
       "      <td>36</td>\n",
       "      <td>52.778</td>\n",
       "      <td>50.000</td>\n",
       "      <td>0.043</td>\n",
       "      <td>0.119</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>이전 판(수준)</th>\n",
       "      <td>36</td>\n",
       "      <td>27.778</td>\n",
       "      <td>19.444</td>\n",
       "      <td>0.148</td>\n",
       "      <td>0.213</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"2\" valign=\"top\">62</th>\n",
       "      <th>정비판(1차 차분)</th>\n",
       "      <td>62</td>\n",
       "      <td>43.548</td>\n",
       "      <td>45.161</td>\n",
       "      <td>0.014</td>\n",
       "      <td>0.015</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>이전 판(수준)</th>\n",
       "      <td>62</td>\n",
       "      <td>25.806</td>\n",
       "      <td>19.355</td>\n",
       "      <td>0.101</td>\n",
       "      <td>0.195</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"2\" valign=\"top\">69</th>\n",
       "      <th>정비판(1차 차분)</th>\n",
       "      <td>69</td>\n",
       "      <td>47.826</td>\n",
       "      <td>44.928</td>\n",
       "      <td>0.013</td>\n",
       "      <td>0.010</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>이전 판(수준)</th>\n",
       "      <td>69</td>\n",
       "      <td>27.536</td>\n",
       "      <td>24.638</td>\n",
       "      <td>0.099</td>\n",
       "      <td>0.198</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"2\" valign=\"top\">AGG(수준·io·varx)</th>\n",
       "      <th>정비판(1차 차분)</th>\n",
       "      <td>1</td>\n",
       "      <td>100.000</td>\n",
       "      <td>0.000</td>\n",
       "      <td>-0.126</td>\n",
       "      <td>0.065</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>이전 판(수준)</th>\n",
       "      <td>1</td>\n",
       "      <td>0.000</td>\n",
       "      <td>0.000</td>\n",
       "      <td>0.025</td>\n",
       "      <td>0.140</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            산업 수  동시항 음수 비율(%)  합계 음수 비율(%)  가중평균 동시항  가중평균 합계\n",
       "분류              판                                                             \n",
       "36              정비판(1차 차분)    36        52.778       50.000     0.043    0.119\n",
       "                이전 판(수준)      36        27.778       19.444     0.148    0.213\n",
       "62              정비판(1차 차분)    62        43.548       45.161     0.014    0.015\n",
       "                이전 판(수준)      62        25.806       19.355     0.101    0.195\n",
       "69              정비판(1차 차분)    69        47.826       44.928     0.013    0.010\n",
       "                이전 판(수준)      69        27.536       24.638     0.099    0.198\n",
       "AGG(수준·io·varx) 정비판(1차 차분)     1       100.000        0.000    -0.126    0.065\n",
       "                이전 판(수준)       1         0.000        0.000     0.025    0.140"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "coef_rows, sign_rows = [], []\n",
    "for g in [\"36\", \"62\", \"69\"]:\n",
    "    L, gdp, W = PANEL[g]\n",
    "    sig = V.ar_sigma(L.diff().dropna(), C.P)\n",
    "    wts = np.exp(L.iloc[-4:] / 100).sum()\n",
    "    for lab, key in [(\"정비판(1차 차분)\", \"new\"), (\"이전 판(수준)\", \"prev\")]:\n",
    "        B, groups = V.fit_option(L, gdp, EX[key], W, list(L.index), \"차분\", \"io\", \"varx\", sig, C.P, C.Q, C.LAM)\n",
    "        ct = V.exog_coef_table(B, groups, list(L.columns), C.EXOG, \"실질금리\")\n",
    "        coef_rows.append(ct.assign(분류=g, 판=lab).rename_axis(\"산업\").reset_index())\n",
    "        sign_rows.append({\"분류\": g, \"판\": lab, **V.sign_summary(ct, wts)})\n",
    "L, gdp, _ = PANEL[\"AGG\"]\n",
    "sig = V.ar_sigma(L.dropna(), C.P)\n",
    "for lab, key in [(\"정비판(1차 차분)\", \"new\"), (\"이전 판(수준)\", \"prev\")]:\n",
    "    B, groups = V.fit_option(L, gdp, EX[key], None, list(L.index), \"수준\", \"io\", \"varx\", sig, C.P, C.Q, C.LAM)\n",
    "    ct = V.exog_coef_table(B, groups, list(L.columns), C.EXOG, \"실질금리\")\n",
    "    coef_rows.append(ct.assign(분류=\"AGG\", 판=lab).rename_axis(\"산업\").reset_index())\n",
    "    sign_rows.append({\"분류\": \"AGG(수준·io·varx)\", \"판\": lab, **V.sign_summary(ct, pd.Series(1.0, index=ct.index))})\n",
    "pd.concat(coef_rows).to_csv(os.path.join(C.OUTPUT, \"real_rate_coef.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "sign = pd.DataFrame(sign_rows).set_index([\"분류\", \"판\"])\n",
    "sign.to_csv(os.path.join(C.OUTPUT, \"real_rate_sign.csv\"), encoding=\"utf-8-sig\")\n",
    "sign"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee5bd16e",
   "metadata": {},
   "source": [
    "정비판에서는 음(−)의 계수를 가진 산업이 절반 안팎으로 늘고, 부가가치 가중평균 계수가 0에 가까워진다. 이전 판의 체계적인 양(+)의 계수(추세 공행)는 사라졌다.\n",
    "다만 가중평균이 뚜렷한 음수가 되지는 않는다. 산업 성장률에 대한 실질금리 변화의 효과는 산업마다 방향이 다르고 전체로는 작게 추정된다는 뜻이며,\n",
    "부호 제약이나 경기 공행 성분의 직교화는 이 정비의 범위 밖이다(README 쟁점). 차분 투입에서는 실질금리가 일정하면 효과가 0이므로,\n",
    "금리 수준이 높게 유지된다고 성장률이 계속 높아지는 이전 판의 문제는 구조적으로 생기지 않는다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f735274",
   "metadata": {},
   "source": [
    "## 5. 점검 B — 36분류 산업연관 행렬의 정규화\n",
    "\n",
    "01 3절에서 보았듯 36 행렬은 62·69와 달리 정규화되지 않았다. 62·69와 같은 방식(비대각 양수 평균 1, 하한 0.05)으로 정규화한 행렬로 36분류를 다시 추정해,\n",
    "'산업연관표 사전이 미네소타보다 낫다'는 결론이 배분(어느 쌍을 느슨하게 하는가)에서 오는지 전체 수축 강도에서 오는지 확인한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d7dc022d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:28.912385Z",
     "iopub.status.busy": "2026-10-08T00:05:28.912385Z",
     "iopub.status.idle": "2026-10-08T00:05:29.157472Z",
     "shell.execute_reply": "2026-10-08T00:05:29.156968Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                 평균   중앙값     최소     최대  평균 가중(|W|+0.05)\n",
      "원값(공표 그리드)    0.037 0.013 -0.000  1.212            0.087\n",
      "정규화(62·69 방식) 1.000 0.341  0.050 33.039            1.050\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "valid 최소(정규화 W): 차분_io_varx\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 tr th {\n",
       "        text-align: left;\n",
       "    }\n",
       "\n",
       "    .dataframe thead tr:last-of-type th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th colspan=\"3\" halign=\"left\">원값 W(공표)</th>\n",
       "      <th colspan=\"3\" halign=\"left\">정규화 W</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th>RMSE_train</th>\n",
       "      <th>RMSE_valid</th>\n",
       "      <th>RMSE_test</th>\n",
       "      <th>RMSE_train</th>\n",
       "      <th>RMSE_valid</th>\n",
       "      <th>RMSE_test</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>차분_io_varx</th>\n",
       "      <td>3.691</td>\n",
       "      <td>6.744</td>\n",
       "      <td>3.106</td>\n",
       "      <td>2.973</td>\n",
       "      <td>6.983</td>\n",
       "      <td>3.376</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>차분_io_var</th>\n",
       "      <td>4.016</td>\n",
       "      <td>6.885</td>\n",
       "      <td>2.916</td>\n",
       "      <td>3.192</td>\n",
       "      <td>7.099</td>\n",
       "      <td>3.244</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>차분_minnesota_varx</th>\n",
       "      <td>2.308</td>\n",
       "      <td>7.296</td>\n",
       "      <td>3.626</td>\n",
       "      <td>2.308</td>\n",
       "      <td>7.296</td>\n",
       "      <td>3.626</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>차분_minnesota_var</th>\n",
       "      <td>2.507</td>\n",
       "      <td>7.405</td>\n",
       "      <td>3.597</td>\n",
       "      <td>2.507</td>\n",
       "      <td>7.405</td>\n",
       "      <td>3.597</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_minnesota_varx</th>\n",
       "      <td>1.982</td>\n",
       "      <td>8.111</td>\n",
       "      <td>4.173</td>\n",
       "      <td>1.982</td>\n",
       "      <td>8.111</td>\n",
       "      <td>4.173</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_io_varx</th>\n",
       "      <td>3.039</td>\n",
       "      <td>8.660</td>\n",
       "      <td>4.153</td>\n",
       "      <td>2.515</td>\n",
       "      <td>8.241</td>\n",
       "      <td>3.914</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_minnesota_var</th>\n",
       "      <td>2.131</td>\n",
       "      <td>8.387</td>\n",
       "      <td>4.166</td>\n",
       "      <td>2.131</td>\n",
       "      <td>8.387</td>\n",
       "      <td>4.166</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VECM_io_var</th>\n",
       "      <td>3.285</td>\n",
       "      <td>8.739</td>\n",
       "      <td>4.196</td>\n",
       "      <td>2.688</td>\n",
       "      <td>8.464</td>\n",
       "      <td>3.884</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_io_varx</th>\n",
       "      <td>3.248</td>\n",
       "      <td>10.325</td>\n",
       "      <td>3.804</td>\n",
       "      <td>2.734</td>\n",
       "      <td>12.641</td>\n",
       "      <td>4.139</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_io_var</th>\n",
       "      <td>3.592</td>\n",
       "      <td>10.417</td>\n",
       "      <td>3.827</td>\n",
       "      <td>2.946</td>\n",
       "      <td>13.465</td>\n",
       "      <td>4.402</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_minnesota_varx</th>\n",
       "      <td>2.203</td>\n",
       "      <td>13.939</td>\n",
       "      <td>4.755</td>\n",
       "      <td>2.203</td>\n",
       "      <td>13.939</td>\n",
       "      <td>4.755</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>수준_minnesota_var</th>\n",
       "      <td>2.385</td>\n",
       "      <td>14.823</td>\n",
       "      <td>5.178</td>\n",
       "      <td>2.385</td>\n",
       "      <td>14.823</td>\n",
       "      <td>5.178</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                      원값 W(공표)                           정규화 W             \\\n",
       "                    RMSE_train RMSE_valid RMSE_test RMSE_train RMSE_valid   \n",
       "옵션                                                                          \n",
       "차분_io_varx               3.691      6.744     3.106      2.973      6.983   \n",
       "차분_io_var                4.016      6.885     2.916      3.192      7.099   \n",
       "차분_minnesota_varx        2.308      7.296     3.626      2.308      7.296   \n",
       "차분_minnesota_var         2.507      7.405     3.597      2.507      7.405   \n",
       "VECM_minnesota_varx      1.982      8.111     4.173      1.982      8.111   \n",
       "VECM_io_varx             3.039      8.660     4.153      2.515      8.241   \n",
       "VECM_minnesota_var       2.131      8.387     4.166      2.131      8.387   \n",
       "VECM_io_var              3.285      8.739     4.196      2.688      8.464   \n",
       "수준_io_varx               3.248     10.325     3.804      2.734     12.641   \n",
       "수준_io_var                3.592     10.417     3.827      2.946     13.465   \n",
       "수준_minnesota_varx        2.203     13.939     4.755      2.203     13.939   \n",
       "수준_minnesota_var         2.385     14.823     5.178      2.385     14.823   \n",
       "\n",
       "                               \n",
       "                    RMSE_test  \n",
       "옵션                             \n",
       "차분_io_varx              3.376  \n",
       "차분_io_var               3.244  \n",
       "차분_minnesota_varx       3.626  \n",
       "차분_minnesota_var        3.597  \n",
       "VECM_minnesota_varx     4.173  \n",
       "VECM_io_varx            3.914  \n",
       "VECM_minnesota_var      4.166  \n",
       "VECM_io_var             3.884  \n",
       "수준_io_varx              4.139  \n",
       "수준_io_var               4.402  \n",
       "수준_minnesota_varx       4.755  \n",
       "수준_minnesota_var        5.178  "
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "L36, gdp36, W36 = PANEL[\"36\"]\n",
    "W36n = V.normalize_io(W36)\n",
    "print(pd.DataFrame({\"원값(공표 그리드)\": V.offdiag_summary(W36), \"정규화(62·69 방식)\": V.offdiag_summary(W36n)}).T.to_string(float_format=lambda v: f\"{v:.3f}\"))\n",
    "g36n = V.run_grid(L36, gdp36, EX[\"new\"], W36n, split, C.P, C.Q, C.LAM)\n",
    "g36n.to_csv(os.path.join(C.OUTPUT, \"check_io36_normalized.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "print(\"\\nvalid 최소(정규화 W):\", \" = \".join(V.select(g36n)))\n",
    "cmp = pd.DataFrame({(lab, m): d.set_index(\"옵션\")[m] for lab, d in [(\"원값 W(공표)\", grids[\"36\"]), (\"정규화 W\", g36n)] for m in M})\n",
    "cmp.sort_values((\"정규화 W\", \"RMSE_valid\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8b54130",
   "metadata": {},
   "source": [
    "정규화하면 산업연관표 사전의 수축이 약해져 train RMSE가 내려가고 valid RMSE는 올라간다(위 표). 정규화 여부가 선택을 바꾸는지는 위 출력의 'valid 최소'로 확인한다.\n",
    "36분류에서 두 사전의 차이 가운데 일부는 전체 수축 강도의 차이이다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65bdd48b",
   "metadata": {},
   "source": [
    "## 6. 민감도 — 전체 수축 강도 λ\n",
    "\n",
    "λ는 그리드에서 조정하지 않고 사전에 정한 값(0.2)이다. 0.05·0.1·0.4에서 valid 기준 선택이 유지되는지 확인한다.\n",
    "사전분포 비교는 같은 명세·외생(차분·VARX)에서 산업연관표와 미네소타의 valid RMSE 차이(io − 미네소타)로 본다. 이 값이 음수이면 산업연관표가 더 정확하다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7de0b595",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:29.158471Z",
     "iopub.status.busy": "2026-10-08T00:05:29.158471Z",
     "iopub.status.idle": "2026-10-08T00:05:38.619980Z",
     "shell.execute_reply": "2026-10-08T00:05:38.619476Z"
    }
   },
   "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>0.050</th>\n",
       "      <th>0.100</th>\n",
       "      <th>0.200</th>\n",
       "      <th>0.400</th>\n",
       "    </tr>\n",
       "    <tr>\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>차분_minnesota_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>차분_minnesota_varx</td>\n",
       "      <td>차분_minnesota_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_minnesota_varx</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>69</th>\n",
       "      <td>차분_minnesota_varx</td>\n",
       "      <td>차분_minnesota_varx</td>\n",
       "      <td>차분_io_varx</td>\n",
       "      <td>차분_minnesota_varx</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>AGG</th>\n",
       "      <td>수준_minnesota_varx = 수준_io_varx</td>\n",
       "      <td>수준_minnesota_varx = 수준_io_varx</td>\n",
       "      <td>수준_minnesota_var = 수준_io_var</td>\n",
       "      <td>수준_minnesota_var = 수준_io_var</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "λ                             0.050                           0.100  \\\n",
       "분류                                                                    \n",
       "36                차분_minnesota_varx                      차분_io_varx   \n",
       "62                차분_minnesota_varx               차분_minnesota_varx   \n",
       "69                차분_minnesota_varx               차분_minnesota_varx   \n",
       "AGG  수준_minnesota_varx = 수준_io_varx  수준_minnesota_varx = 수준_io_varx   \n",
       "\n",
       "λ                           0.200                         0.400  \n",
       "분류                                                               \n",
       "36                     차분_io_varx                    차분_io_varx  \n",
       "62                     차분_io_varx             차분_minnesota_varx  \n",
       "69                     차분_io_varx             차분_minnesota_varx  \n",
       "AGG  수준_minnesota_var = 수준_io_var  수준_minnesota_var = 수준_io_var  "
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = []\n",
    "for lam in C.LAM_SENSITIVITY:\n",
    "    for g in C.GRIDS:\n",
    "        L, gdp, W = PANEL[g]\n",
    "        d = grids[g] if lam == C.LAM else V.run_grid(L, gdp, EX[\"new\"], W, split, C.P, C.Q, lam)\n",
    "        best = V.select(d)\n",
    "        r = d.set_index(\"옵션\")\n",
    "        rows.append({\"λ\": lam, \"분류\": g, \"valid 최소 옵션\": \" = \".join(best),\n",
    "                     \"RMSE_valid\": r.loc[best[0], \"RMSE_valid\"], \"RMSE_test\": r.loc[best[0], \"RMSE_test\"],\n",
    "                     \"차분_io_varx valid\": r.loc[\"차분_io_varx\", \"RMSE_valid\"],\n",
    "                     \"차분_minnesota_varx valid\": r.loc[\"차분_minnesota_varx\", \"RMSE_valid\"],\n",
    "                     \"io − 미네소타\": r.loc[\"차분_io_varx\", \"RMSE_valid\"] - r.loc[\"차분_minnesota_varx\", \"RMSE_valid\"],\n",
    "                     \"차분_io_varx 순위\": int(d.index[d[\"옵션\"] == \"차분_io_varx\"][0]) + 1})\n",
    "lam_tbl = pd.DataFrame(rows)\n",
    "lam_tbl.to_csv(os.path.join(C.OUTPUT, \"lambda_sensitivity.csv\"), index=False, encoding=\"utf-8-sig\")\n",
    "lam_tbl.pivot(index=\"분류\", columns=\"λ\", values=\"valid 최소 옵션\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9ec56cad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:38.620979Z",
     "iopub.status.busy": "2026-10-08T00:05:38.620479Z",
     "iopub.status.idle": "2026-10-08T00:05:38.627741Z",
     "shell.execute_reply": "2026-10-08T00:05:38.627741Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "명세·외생(차분·VARX) 선택 유지: 12/12\n",
      "62·69분류 io − 미네소타 valid 차이의 최대 절댓값: 0.170\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 tr th {\n",
       "        text-align: left;\n",
       "    }\n",
       "\n",
       "    .dataframe thead tr:last-of-type th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th colspan=\"4\" halign=\"left\">RMSE_valid</th>\n",
       "      <th colspan=\"4\" halign=\"left\">io − 미네소타</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>λ</th>\n",
       "      <th>0.050</th>\n",
       "      <th>0.100</th>\n",
       "      <th>0.200</th>\n",
       "      <th>0.400</th>\n",
       "      <th>0.050</th>\n",
       "      <th>0.100</th>\n",
       "      <th>0.200</th>\n",
       "      <th>0.400</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",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>6.192</td>\n",
       "      <td>6.384</td>\n",
       "      <td>6.744</td>\n",
       "      <td>7.477</td>\n",
       "      <td>0.079</td>\n",
       "      <td>-0.009</td>\n",
       "      <td>-0.551</td>\n",
       "      <td>-1.298</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>5.166</td>\n",
       "      <td>5.406</td>\n",
       "      <td>6.181</td>\n",
       "      <td>7.374</td>\n",
       "      <td>0.051</td>\n",
       "      <td>0.021</td>\n",
       "      <td>-0.007</td>\n",
       "      <td>0.170</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>69</th>\n",
       "      <td>5.314</td>\n",
       "      <td>5.517</td>\n",
       "      <td>6.230</td>\n",
       "      <td>7.331</td>\n",
       "      <td>0.086</td>\n",
       "      <td>0.059</td>\n",
       "      <td>-0.002</td>\n",
       "      <td>0.109</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   RMSE_valid                   io − 미네소타                     \n",
       "λ       0.050 0.100 0.200 0.400     0.050  0.100  0.200  0.400\n",
       "분류                                                            \n",
       "36      6.192 6.384 6.744 7.477     0.079 -0.009 -0.551 -1.298\n",
       "62      5.166 5.406 6.181 7.374     0.051  0.021 -0.007  0.170\n",
       "69      5.314 5.517 6.230 7.331     0.086  0.059 -0.002  0.109"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "three = lam_tbl[lam_tbl[\"분류\"] != \"AGG\"]\n",
    "spec_ok = three[\"valid 최소 옵션\"].str.startswith(\"차분_\") & three[\"valid 최소 옵션\"].str.endswith(\"_varx\")\n",
    "print(f\"명세·외생(차분·VARX) 선택 유지: {spec_ok.sum()}/{len(three)}\")\n",
    "print(f\"62·69분류 io − 미네소타 valid 차이의 최대 절댓값: {three[three['분류'] != '36']['io − 미네소타'].abs().max():.3f}\")\n",
    "three.pivot(index=\"분류\", columns=\"λ\", values=[\"RMSE_valid\", \"io − 미네소타\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "212dc602",
   "metadata": {},
   "source": [
    "36·62·69분류에서는 λ를 바꾸어도 명세(차분)와 외생(VARX)의 선택이 유지된다. 사전분포의 선택은 λ에 따라 달라진다. 62·69분류에서 산업연관표와 미네소타의\n",
    "valid RMSE 차이는 0.15 이하이며, λ=0.2에서만 산업연관표가 앞서고 0.05·0.1·0.4에서는 미네소타가 앞선다. 36분류는 λ=0.05를 빼면 산업연관표가\n",
    "앞서지만, 5절에서 보았듯 그 차이의 일부는 36 행렬이 정규화되지 않아 생긴 전체 수축 강도의 차이이다. 또한 valid RMSE는 λ가 작을수록(수축이 강할수록)\n",
    "작아지므로, λ=0.2는 valid 기준으로 최적인 값이 아니다. 공표 결론 가운데 '산업연관표가 미네소타보다 낫다'는 부분은 λ=0.2에 조건부인 결과로 읽어야 한다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e1a3f70",
   "metadata": {},
   "source": [
    "## 7. 기여 분해(SHAP 방식)의 방법\n",
    "\n",
    "5장 전망과 6장 진단은 산업별 10년 누적 로그성장을 **드리프트(상수)·동학(내생 시차)·외생 6종**의 기여로 나눈다.\n",
    "선형 모형에서 입력 $k$의 SHAP 값은 '계수 × (입력값 − 기준값)'이며 기준값을 0으로 두면 각 분기 예측은 열 블록별 곱의 합으로 정확히 나뉜다.\n",
    "\n",
    "$$\\widehat{\\Delta y}_{i,t} = \\underbrace{c_i}_{\\text{드리프트}} + \\underbrace{\\sum_l a_{l,i}'\\,\\hat y_{t-l}}_{\\text{동학}} + \\sum_{k=1}^{6}\\underbrace{\\sum_{s=0}^{2}\\gamma_{s,ik}\\,x_{k,t-s}}_{\\text{외생 }k}$$\n",
    "\n",
    "재귀 예측에서는 내생 시차 자리에 직전 예측값이 들어가므로, 외생의 간접 효과(외생 → 다른 산업 → 시차)는 '동학'에 포함된다.\n",
    "원래 스크립트는 `scratchpad/shap_decompose.py`(통합 번들 `industry/code/shap_decompose.py`)이며, 시나리오별 분해 결과는 6장 「진단과 보완」에서 다룬다.\n",
    "\n",
    "여기서는 방법의 가법성만 확인한다. 62분류 차분·산업연관표·VARX를 train+valid로 적합하고, test 8분기(2024Q1–2025Q4)를 실적 외생으로 재귀 예측해 분해한다.\n",
    "분해 값은 방법 점검용이며 해석하지 않는다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "02d5f665",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:38.628751Z",
     "iopub.status.busy": "2026-10-08T00:05:38.628751Z",
     "iopub.status.idle": "2026-10-08T00:05:38.671835Z",
     "shell.execute_reply": "2026-10-08T00:05:38.671835Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "가법성 오차(누적예측 − 기여 합) 최대 1.4e-14\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>누적예측</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>22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비</th>\n",
       "      <td>18.146</td>\n",
       "      <td>16.203</td>\n",
       "      <td>4.174</td>\n",
       "      <td>-1.192</td>\n",
       "      <td>-0.119</td>\n",
       "      <td>-0.099</td>\n",
       "      <td>-0.835</td>\n",
       "      <td>-0.038</td>\n",
       "      <td>0.051</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26.자동차</th>\n",
       "      <td>9.904</td>\n",
       "      <td>-3.163</td>\n",
       "      <td>14.042</td>\n",
       "      <td>-2.602</td>\n",
       "      <td>1.902</td>\n",
       "      <td>0.011</td>\n",
       "      <td>-0.524</td>\n",
       "      <td>-0.043</td>\n",
       "      <td>0.282</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>33.건설업</th>\n",
       "      <td>3.187</td>\n",
       "      <td>2.460</td>\n",
       "      <td>-0.242</td>\n",
       "      <td>0.982</td>\n",
       "      <td>0.547</td>\n",
       "      <td>0.048</td>\n",
       "      <td>-0.955</td>\n",
       "      <td>0.236</td>\n",
       "      <td>0.112</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>46.금융업</th>\n",
       "      <td>8.838</td>\n",
       "      <td>3.222</td>\n",
       "      <td>4.970</td>\n",
       "      <td>0.658</td>\n",
       "      <td>-0.292</td>\n",
       "      <td>-0.098</td>\n",
       "      <td>0.175</td>\n",
       "      <td>0.291</td>\n",
       "      <td>-0.088</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                         누적예측   드리프트     동학     유가   세계수요   실질금리   실질환율  \\\n",
       "22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비 18.146 16.203  4.174 -1.192 -0.119 -0.099 -0.835   \n",
       "26.자동차                  9.904 -3.163 14.042 -2.602  1.902  0.011 -0.524   \n",
       "33.건설업                  3.187  2.460 -0.242  0.982  0.547  0.048 -0.955   \n",
       "46.금융업                  8.838  3.222  4.970  0.658 -0.292 -0.098  0.175   \n",
       "\n",
       "                        고용_취업자   우리경기  \n",
       "22.전자부품ㆍ컴퓨터ㆍ영상ㆍ음향ㆍ통신장비  -0.038  0.051  \n",
       "26.자동차                  -0.043  0.282  \n",
       "33.건설업                   0.236  0.112  \n",
       "46.금융업                   0.291 -0.088  "
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tr_va = [x for x in L62.index if split[x] != \"test\"]\n",
    "te = [x for x in L62.index if split[x] == \"test\"]\n",
    "sig = V.ar_sigma(L62.diff().dropna(), C.P)\n",
    "B, _ = V.fit_option(L62, gdp62, EX[\"new\"], W62, tr_va, \"차분\", \"io\", \"varx\", sig, C.P, C.Q, C.LAM)\n",
    "dec = V.decompose_recursive(B, L62, EX[\"new\"], te, C.EXOG, C.P, C.Q)\n",
    "gap = dec[\"누적예측\"] - dec.drop(columns=\"누적예측\").sum(axis=1)\n",
    "print(f\"가법성 오차(누적예측 − 기여 합) 최대 {gap.abs().max():.1e}\")\n",
    "assert gap.abs().max() < 1e-8\n",
    "dec.to_csv(os.path.join(C.OUTPUT, \"decomposition_check_62.csv\"), encoding=\"utf-8-sig\")\n",
    "dec.loc[[c for c in dec.index if c.split(\".\")[0] in (\"22\", \"26\", \"33\", \"46\")]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3ea88e2c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:38.672839Z",
     "iopub.status.busy": "2026-10-08T00:05:38.672839Z",
     "iopub.status.idle": "2026-10-08T00:05:38.675727Z",
     "shell.execute_reply": "2026-10-08T00:05:38.675727Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "02 소요시간(초): {'36': 0.2, '62': 0.8, '69': 1.7, 'AGG': 0.0}\n",
      "check_io36_normalized.csv                1,267 bytes\n",
      "check_prev_vs_new.csv                    6,823 bytes\n",
      "check_prev_vs_new_summary.csv              524 bytes\n",
      "decomposition_check_62.csv              12,637 bytes\n",
      "grid_36.csv                              1,260 bytes\n",
      "grid_62.csv                              1,259 bytes\n",
      "grid_69.csv                              1,265 bytes\n",
      "grid_AGG.csv                             1,245 bytes\n",
      "grid_prev_36.csv                         1,266 bytes\n",
      "grid_prev_62.csv                         1,262 bytes\n",
      "grid_prev_69.csv                         1,259 bytes\n",
      "grid_prev_AGG.csv                        1,243 bytes\n",
      "lambda_sensitivity.csv                   2,141 bytes\n",
      "real_rate_coef.csv                      44,114 bytes\n",
      "real_rate_sign.csv                         929 bytes\n"
     ]
    }
   ],
   "source": [
    "print(\"02 소요시간(초):\", {g: round(t, 1) for g, t in times.items()})\n",
    "for f in sorted(os.listdir(C.OUTPUT)):\n",
    "    print(f\"{f:<36} {os.path.getsize(os.path.join(C.OUTPUT, f)):>9,d} bytes\")"
   ]
  }
 ],
 "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
}
