{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5927b995",
   "metadata": {},
   "source": [
    "# 01 데이터 입수 — 산업 패널, 산업연관 행렬, 외생 6종\n",
    "\n",
    "**목적.** 4장 산업모형의 12개 옵션 비교에 필요한 입력을 불러오고 검증한다.\n",
    "\n",
    "**2026-10 정비판.** 3장이 실질금리 외생의 투입형을 수준에서 1차 차분으로 바꾸었다(정상상태 정비와 함께). 이 패키지는 그 외생으로 그리드를 다시 계산하고,\n",
    "이전 판 공표 그리드(2026-08-28 외생, 실질금리 수준)도 그대로 재현하여 두 판을 비교한다.\n",
    "\n",
    "원자료의 입수와 가공은 이 장의 범위가 아니다. 산업별 분기 실질부가가치 패널과 산업연관 행렬은 **2장 「데이터」**(s2-data)에서,\n",
    "외생 6종의 과거값은 **3장 「거시엔진과 시나리오」**(s3-macro)에서 만든다. 이 패키지는 두 장의 산출물 사본을 `data/inputs/`에 동결해 두고,\n",
    "파일 해시와 구조를 확인한 뒤 분석용 중간 산출물을 만든다. 각 파일의 원래 위치·생성 프로그램·작성 시각은 `data/inputs/PROVENANCE.md`에 적었다.\n",
    "\n",
    "| 입력 | 내용 | 만든 곳 |\n",
    "|---|---|---|\n",
    "| `levels_sa.csv` | 36개 산업 분기 실질부가가치(계절조정, 십억 원, 1980Q1–2025Q4)와 실질 GDP(`총량GDP`) | 2장(한국은행 국민계정) |\n",
    "| `levels_62.csv`, `levels_69.csv` | 62·69개 산업 분기 실질부가가치(2000Q1–2025Q4)와 산업 합계(`총량GDP` 열) | 2장(36→62 배분, 62→69 분화) |\n",
    "| `levels_agg.csv` | 총량 단일 계열(36 패널의 실질 GDP) | 4장 총량 그리드용(2026-09-01) |\n",
    "| `io_linkage_W.csv` | 36×36 산업연관 연계강도(생산유발계수) | 2장(산업연관표) |\n",
    "| `io_linkage_62.csv`, `io_linkage_69.csv` | 62·69 연계강도(2020년 생산유발계수, 비대각 평균 1로 정규화) | 2장(ECOS 271Y122) |\n",
    "| `exog_hist_v2.csv` | 외생 6종 투입형 과거값 — **정비판**(실질금리 1차 차분, 2000년 실질금리 보정 포함) | 3장 산출물(2026-10 정비) |\n",
    "| `exog_hist_v2_20260828.csv` | 외생 6종 투입형 과거값 — 이전 공표 그리드가 쓴 판(실질금리 수준) | 3장 산출물(2026-08-28판 복원) |\n",
    "\n",
    "**산출물.** `data/interim/` — 분석용 100·log 패널(`logpanel_*.csv`), 정렬한 산업연관 행렬(`io_*.csv`), 표본에 맞춘 외생(`exog_*.csv`), 표본 분할(`split.csv`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e670af14",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.326191Z",
     "iopub.status.busy": "2026-10-08T00:05:20.325188Z",
     "iopub.status.idle": "2026-10-08T00:05:20.781152Z",
     "shell.execute_reply": "2026-10-08T00:05:20.781152Z"
    }
   },
   "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"
     ]
    }
   ],
   "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()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad185fea",
   "metadata": {},
   "source": [
    "## 1. 파일 무결성 — SHA-256\n",
    "\n",
    "`config.py`에 적어 둔 해시와 입력 파일의 해시가 같아야 한다. 하나라도 다르면 이후 결과가 공표 수치와 달라질 수 있으므로 여기서 멈춘다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d36d74ad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.782156Z",
     "iopub.status.busy": "2026-10-08T00:05:20.782156Z",
     "iopub.status.idle": "2026-10-08T00:05:20.791174Z",
     "shell.execute_reply": "2026-10-08T00:05:20.791174Z"
    }
   },
   "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>크기(bytes)</th>\n",
       "      <th>SHA-256(앞 16자)</th>\n",
       "      <th>일치</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>levels_sa.csv</td>\n",
       "      <td>51177</td>\n",
       "      <td>ccfc8dad3c8cd7b0</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>levels_62.csv</td>\n",
       "      <td>105355</td>\n",
       "      <td>00469e892aac4742</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>levels_69.csv</td>\n",
       "      <td>122145</td>\n",
       "      <td>c8a5adcedfa4635c</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>levels_agg.csv</td>\n",
       "      <td>4748</td>\n",
       "      <td>f04021ff282e5387</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>io_linkage_W.csv</td>\n",
       "      <td>11035</td>\n",
       "      <td>cf11efc744586d35</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>io_linkage_62.csv</td>\n",
       "      <td>67817</td>\n",
       "      <td>3f747a97c3804541</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>io_linkage_69.csv</td>\n",
       "      <td>79382</td>\n",
       "      <td>1f57d01c6867b456</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>exog_hist_v2_20260828.csv</td>\n",
       "      <td>5481</td>\n",
       "      <td>e0b0064e0c7cd600</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>exog_hist_v2.csv</td>\n",
       "      <td>5521</td>\n",
       "      <td>d142ff2992b5ae3e</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                          파일  크기(bytes)    SHA-256(앞 16자)    일치\n",
       "0              levels_sa.csv      51177  ccfc8dad3c8cd7b0  True\n",
       "1              levels_62.csv     105355  00469e892aac4742  True\n",
       "2              levels_69.csv     122145  c8a5adcedfa4635c  True\n",
       "3             levels_agg.csv       4748  f04021ff282e5387  True\n",
       "4           io_linkage_W.csv      11035  cf11efc744586d35  True\n",
       "5          io_linkage_62.csv      67817  3f747a97c3804541  True\n",
       "6          io_linkage_69.csv      79382  1f57d01c6867b456  True\n",
       "7  exog_hist_v2_20260828.csv       5481  e0b0064e0c7cd600  True\n",
       "8           exog_hist_v2.csv       5521  d142ff2992b5ae3e  True"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def sha256(p):\n",
    "    h = hashlib.sha256()\n",
    "    with open(p, \"rb\") as f:\n",
    "        h.update(f.read())\n",
    "    return h.hexdigest()\n",
    "\n",
    "rows = []\n",
    "for name, expect in C.INPUT_SHA256.items():\n",
    "    p = os.path.join(C.INPUTS, name)\n",
    "    got = sha256(p)\n",
    "    rows.append({\"파일\": name, \"크기(bytes)\": os.path.getsize(p), \"SHA-256(앞 16자)\": got[:16], \"일치\": got == expect})\n",
    "chk = pd.DataFrame(rows)\n",
    "assert chk[\"일치\"].all(), \"입력 파일이 동결본과 다르다 — data/inputs/PROVENANCE.md를 확인한다\"\n",
    "chk"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "087e76be",
   "metadata": {},
   "source": [
    "## 2. 산업 패널 — 36·62·69개 산업과 총량\n",
    "\n",
    "모형은 표본 2000Q1–2025Q4(104분기)의 100·log 실질부가가치를 쓴다. 36개 패널은 1980년부터 있으나 표본 밖 분기는 버린다.\n",
    "표본 안에 결측이나 0 이하 값이 없어야 로그 변환이 성립한다.\n",
    "\n",
    "62·69개 패널은 36개 공표 산업의 분기값을 2020년 산업연관표 부가가치 비중으로 나눈 것이다(69분류는 일부 산업을 다시 분화했다).\n",
    "비중이 고정이면 같은 모산업에서 나온 산업은 분기 성장률이 같으므로, 아래 표의 '서로 다른 성장률 계열' 수가 산업 수보다 적다.\n",
    "이 점은 분류 간 RMSE를 비교할 때 고려해야 한다(03 표 1의 주석)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8928a416",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.792416Z",
     "iopub.status.busy": "2026-10-08T00:05:20.792416Z",
     "iopub.status.idle": "2026-10-08T00:05:20.817106Z",
     "shell.execute_reply": "2026-10-08T00:05:20.817106Z"
    }
   },
   "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>0 이하 값</th>\n",
       "      <th>서로 다른 성장률 계열</th>\n",
       "      <th>2025Q4 산업 합계(조원)</th>\n",
       "      <th>2025Q4 총량 열(조원)</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>36</td>\n",
       "      <td>1980Q1–2025Q4</td>\n",
       "      <td>104</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>36</td>\n",
       "      <td>549.415</td>\n",
       "      <td>585.964</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>62</td>\n",
       "      <td>2000Q1–2025Q4</td>\n",
       "      <td>104</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>32</td>\n",
       "      <td>549.415</td>\n",
       "      <td>549.415</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>69</th>\n",
       "      <td>69</td>\n",
       "      <td>2000Q1–2025Q4</td>\n",
       "      <td>104</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>34</td>\n",
       "      <td>549.415</td>\n",
       "      <td>549.415</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>AGG</th>\n",
       "      <td>1</td>\n",
       "      <td>1980Q1–2025Q4</td>\n",
       "      <td>104</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>585.964</td>\n",
       "      <td>585.964</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     산업 수         원자료 기간  표본 분기  표본 결측  0 이하 값  서로 다른 성장률 계열  \\\n",
       "분류                                                             \n",
       "36     36  1980Q1–2025Q4    104      0       0            36   \n",
       "62     62  2000Q1–2025Q4    104      0       0            32   \n",
       "69     69  2000Q1–2025Q4    104      0       0            34   \n",
       "AGG     1  1980Q1–2025Q4    104      0       0             1   \n",
       "\n",
       "     2025Q4 산업 합계(조원)  2025Q4 총량 열(조원)  \n",
       "분류                                      \n",
       "36            549.415          585.964  \n",
       "62            549.415          549.415  \n",
       "69            549.415          549.415  \n",
       "AGG           585.964          585.964  "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "qs = V.quarters(C.SAMPLE_START, C.SAMPLE_END)\n",
    "raw, rows = {}, []\n",
    "for g in C.GRIDS:\n",
    "    lev = pd.read_csv(C.LEVELS[g], index_col=0, encoding=\"utf-8-sig\")\n",
    "    raw[g] = lev\n",
    "    s = lev.reindex(qs)\n",
    "    inds = [c for c in lev.columns if c != C.GDP_COL]\n",
    "    growth = (100 * np.log(s[inds])).diff().dropna().round(8)\n",
    "    rows.append({\"분류\": g, \"산업 수\": len(inds), \"원자료 기간\": f\"{lev.index[0]}–{lev.index[-1]}\", \"표본 분기\": len(qs),\n",
    "                 \"표본 결측\": int(s.isna().sum().sum()), \"0 이하 값\": int((s <= 0).sum().sum()),\n",
    "                 \"서로 다른 성장률 계열\": growth.T.drop_duplicates().shape[0],\n",
    "                 \"2025Q4 산업 합계(조원)\": s[inds].iloc[-1].sum() / 1e3, \"2025Q4 총량 열(조원)\": s[C.GDP_COL].iloc[-1] / 1e3})\n",
    "panel = pd.DataFrame(rows).set_index(\"분류\")\n",
    "assert (panel[\"표본 결측\"] == 0).all() and (panel[\"0 이하 값\"] == 0).all()\n",
    "panel"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b664cda8",
   "metadata": {},
   "source": [
    "세 분류의 산업 합계는 분기마다 같아야 한다(배분·분화가 합계를 보존한다). 반면 `총량GDP` 열의 정의는 분류마다 다르다.\n",
    "36·총량 패널은 실질 GDP(순생산물세 포함)이고, 62·69 패널은 산업 합계(부가가치 기준)이다. 이 열은 VECM 명세의 오차수정항\n",
    "기준으로만 쓰이므로, VECM의 장기 기준점이 36분류와 62·69분류에서 다르다(README 쟁점 5)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "46e4bec5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.818109Z",
     "iopub.status.busy": "2026-10-08T00:05:20.818109Z",
     "iopub.status.idle": "2026-10-08T00:05:20.824262Z",
     "shell.execute_reply": "2026-10-08T00:05:20.823758Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "산업 합계 최대 차이: 36–62 2.9e-10 · 62–69 3.5e-10 (십억 원)\n",
      "62·69 패널 총량 열 ÷ 36 패널 실질 GDP: 0.916–0.959\n",
      "총량 패널: 내생 계열 '총량'과 기준 열 '총량GDP'가 같은 계열이다(36 패널의 실질 GDP).\n"
     ]
    }
   ],
   "source": [
    "sums = pd.DataFrame({g: raw[g].reindex(qs).drop(columns=C.GDP_COL).sum(axis=1) for g in [\"36\", \"62\", \"69\"]})\n",
    "d3662, d6269 = (sums[\"36\"] - sums[\"62\"]).abs().max(), (sums[\"62\"] - sums[\"69\"]).abs().max()\n",
    "print(f\"산업 합계 최대 차이: 36–62 {d3662:.1e} · 62–69 {d6269:.1e} (십억 원)\")\n",
    "assert d3662 < 1e-6 and d6269 < 1e-6\n",
    "ratio = raw[\"62\"][C.GDP_COL].reindex(qs) / raw[\"36\"][C.GDP_COL].reindex(qs)\n",
    "print(f\"62·69 패널 총량 열 ÷ 36 패널 실질 GDP: {ratio.min():.3f}–{ratio.max():.3f}\")\n",
    "assert (raw[\"AGG\"][C.GDP_COL].reindex(qs) == raw[\"36\"][C.GDP_COL].reindex(qs)).all()\n",
    "assert (raw[\"AGG\"][\"총량\"] == raw[\"AGG\"][C.GDP_COL]).all()\n",
    "print(\"총량 패널: 내생 계열 '총량'과 기준 열 '총량GDP'가 같은 계열이다(36 패널의 실질 GDP).\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bd5cfef",
   "metadata": {},
   "source": [
    "## 3. 산업연관 행렬 — 후방연쇄 강도\n",
    "\n",
    "$W_{ij}$는 산업 $j$의 최종수요 한 단위가 산업 $i$의 생산을 유발하는 정도(생산유발계수, 레온티에프 역행렬 원소)를 산업 단위로 묶은 값이다.\n",
    "산업연관표 사전분포는 방정식 $i$에서 산업 $j$의 시차 계수에 대한 미네소타 사전분산에 $(|W_{ij}|+0.05)$를 곱한다.\n",
    "연관이 강한 쌍은 계수를 느슨하게, 약한 쌍은 0 쪽으로 강하게 수축한다.\n",
    "\n",
    "62·69 행렬은 비대각 평균을 1로 정규화해 전체 수축량을 미네소타와 맞췄다. 36 행렬은 정규화하지 않은 생산유발계수 평균이 그대로 들어가 있어\n",
    "교차 시차의 평균 가중이 0.1보다 작다. 즉 36분류에서는 산업연관표 사전이 미네소타보다 전반적으로 훨씬 강하게 수축한다(02 5절에서 점검)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "f6f23881",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.824762Z",
     "iopub.status.busy": "2026-10-08T00:05:20.824762Z",
     "iopub.status.idle": "2026-10-08T00:05:20.834653Z",
     "shell.execute_reply": "2026-10-08T00:05:20.834653Z"
    }
   },
   "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",
       "      <th>평균 가중(|W|+0.05)</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",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>io_linkage_W.csv</td>\n",
       "      <td>36×36</td>\n",
       "      <td>0.037</td>\n",
       "      <td>0.013</td>\n",
       "      <td>-0.000</td>\n",
       "      <td>1.212</td>\n",
       "      <td>0.087</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>io_linkage_62.csv</td>\n",
       "      <td>62×62</td>\n",
       "      <td>0.959</td>\n",
       "      <td>0.330</td>\n",
       "      <td>0.050</td>\n",
       "      <td>27.093</td>\n",
       "      <td>1.009</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>69</th>\n",
       "      <td>io_linkage_69.csv</td>\n",
       "      <td>69×69</td>\n",
       "      <td>0.897</td>\n",
       "      <td>0.273</td>\n",
       "      <td>0.050</td>\n",
       "      <td>28.772</td>\n",
       "      <td>0.947</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                   파일     크기    평균   중앙값     최소     최대  평균 가중(|W|+0.05)\n",
       "분류                                                                     \n",
       "36   io_linkage_W.csv  36×36 0.037 0.013 -0.000  1.212            0.087\n",
       "62  io_linkage_62.csv  62×62 0.959 0.330  0.050 27.093            1.009\n",
       "69  io_linkage_69.csv  69×69 0.897 0.273  0.050 28.772            0.947"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "io_rows, W = [], {}\n",
    "for g in [\"36\", \"62\", \"69\"]:\n",
    "    inds = [c for c in raw[g].columns if c != C.GDP_COL]\n",
    "    W[g] = V.read_io(C.IO[g], inds)\n",
    "    assert W[g] is not None and list(W[g].index) == inds and list(W[g].columns) == inds, f\"{g}: 행렬과 패널의 산업이 다르다\"\n",
    "    io_rows.append({\"분류\": g, \"파일\": os.path.basename(C.IO[g]), \"크기\": f\"{W[g].shape[0]}×{W[g].shape[1]}\", **V.offdiag_summary(W[g])})\n",
    "pd.DataFrame(io_rows).set_index(\"분류\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23112312",
   "metadata": {},
   "source": [
    "## 4. 외생 6종 — 정비판과 이전 판\n",
    "\n",
    "외생 6종은 3장의 BOKDPM 관측변수를 조합한 값이며, 적분 차수에 맞춰 바꾼 과거값(2000Q1–2025Q4)이다.\n",
    "\n",
    "| 변수 | 구성(BOKDPM 관측) | 정비판 투입형 | 이전 판 |\n",
    "|---|---|---|---|\n",
    "| 유가 | 두바이유(LOIL_us) | 1차 차분 | 같음 |\n",
    "| 세계수요 | 미국 산출갭(Y_us) | 수준 | 같음 |\n",
    "| 실질금리 | 회사채(3년, AA−) 수익률(CB_kor) − 전년동기 물가상승률 | **1차 차분** | 수준 |\n",
    "| 실질환율 | 원/달러 환율 + 미국 물가 − 한국 물가(로그) | 1차 차분 | 같음 |\n",
    "| 고용_취업자 | 경제활동인구 × (1 − 실업률) | 100·Δlog | 같음 |\n",
    "| 우리경기 | 한국 산출갭(Y_kor) | 수준 | 같음 |\n",
    "\n",
    "두 판은 **실질금리 열만** 다르다. 실질금리 수준은 2000–2025년 동안 하락 추세를 지녀(3장 02 7절의 단위근 검정) 성장률을 내생으로 하는 차분 명세와 적분차수가 맞지 않았다.\n",
    "정비판은 1차 차분을 쓴다. 이전 판은 2000년 네 분기의 실질금리를 2001Q1 값(3.5972)으로 역채움한 판이며(1999년 물가 미확보), 정비판은 1999년 소비자물가로 계산한 값의 차분이다.\n",
    "나머지 다섯 외생은 관측자료가 같으므로 같다(평활값의 수치 오차로 소수 넷째 자리 한 단위까지 다를 수 있다)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ab93c948",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.835657Z",
     "iopub.status.busy": "2026-10-08T00:05:20.835657Z",
     "iopub.status.idle": "2026-10-08T00:05:20.842625Z",
     "shell.execute_reply": "2026-10-08T00:05:20.842625Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "열별 최대 절대차(정비판 − 이전 판): {'유가': 0.0001, '세계수요': 0.0, '실질금리': 5.9111, '실질환율': 0.0, '고용_취업자': 0.0, '우리경기': 0.0}\n",
      "이전 판 실질금리(수준) 2000–2025 평균 1.69% · 정비판 실질금리(차분) 평균 -0.068%p\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>정비판: 실질금리 1차 차분</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>분기</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2000Q1</th>\n",
       "      <td>3.597</td>\n",
       "      <td>0.121</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2000Q2</th>\n",
       "      <td>3.597</td>\n",
       "      <td>0.121</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2000Q3</th>\n",
       "      <td>3.597</td>\n",
       "      <td>-2.190</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2000Q4</th>\n",
       "      <td>3.597</td>\n",
       "      <td>-0.194</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2001Q1</th>\n",
       "      <td>3.597</td>\n",
       "      <td>-2.314</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2001Q2</th>\n",
       "      <td>2.697</td>\n",
       "      <td>-0.900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2001Q3</th>\n",
       "      <td>2.457</td>\n",
       "      <td>-0.240</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2001Q4</th>\n",
       "      <td>3.496</td>\n",
       "      <td>1.039</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "        이전 판: 실질금리 수준  정비판: 실질금리 1차 차분\n",
       "분기                                    \n",
       "2000Q1          3.597            0.121\n",
       "2000Q2          3.597            0.121\n",
       "2000Q3          3.597           -2.190\n",
       "2000Q4          3.597           -0.194\n",
       "2001Q1          3.597           -2.314\n",
       "2001Q2          2.697           -0.900\n",
       "2001Q3          2.457           -0.240\n",
       "2001Q4          3.496            1.039"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ex_new = V.read_exog(C.EXOG_HIST, C.EXOG, qs)\n",
    "ex_prev = V.read_exog(C.EXOG_HIST_PREV, C.EXOG, qs)\n",
    "assert not ex_new.isna().any().any() and not ex_prev.isna().any().any()\n",
    "d = (ex_new - ex_prev).abs().max()\n",
    "print(\"열별 최대 절대차(정비판 − 이전 판):\", d.round(4).to_dict())\n",
    "assert (d.drop(\"실질금리\") <= 1.0001e-4).all(), \"실질금리 밖의 외생이 이전 판과 다르다\"\n",
    "print(f\"이전 판 실질금리(수준) 2000–2025 평균 {ex_prev['실질금리'].mean():.2f}% · 정비판 실질금리(차분) 평균 {ex_new['실질금리'].mean():+.3f}%p\")\n",
    "pd.DataFrame({\"이전 판: 실질금리 수준\": ex_prev.loc[\"2000Q1\":\"2001Q4\", \"실질금리\"],\n",
    "              \"정비판: 실질금리 1차 차분\": ex_new.loc[\"2000Q1\":\"2001Q4\", \"실질금리\"]})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fbb3a258",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.843128Z",
     "iopub.status.busy": "2026-10-08T00:05:20.843128Z",
     "iopub.status.idle": "2026-10-08T00:05:20.851387Z",
     "shell.execute_reply": "2026-10-08T00:05:20.851387Z"
    }
   },
   "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",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>유가</th>\n",
       "      <td>0.947</td>\n",
       "      <td>15.445</td>\n",
       "      <td>-77.064</td>\n",
       "      <td>33.937</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>세계수요</th>\n",
       "      <td>-0.678</td>\n",
       "      <td>1.967</td>\n",
       "      <td>-9.245</td>\n",
       "      <td>2.452</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>실질금리</th>\n",
       "      <td>-0.068</td>\n",
       "      <td>0.690</td>\n",
       "      <td>-2.314</td>\n",
       "      <td>2.091</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>실질환율</th>\n",
       "      <td>0.270</td>\n",
       "      <td>3.843</td>\n",
       "      <td>-9.152</td>\n",
       "      <td>18.647</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>고용_취업자</th>\n",
       "      <td>0.315</td>\n",
       "      <td>0.492</td>\n",
       "      <td>-2.339</td>\n",
       "      <td>2.005</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>우리경기</th>\n",
       "      <td>0.004</td>\n",
       "      <td>1.441</td>\n",
       "      <td>-3.646</td>\n",
       "      <td>4.249</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           평균   표준편차      최소     최대\n",
       "유가      0.947 15.445 -77.064 33.937\n",
       "세계수요   -0.678  1.967  -9.245  2.452\n",
       "실질금리   -0.068  0.690  -2.314  2.091\n",
       "실질환율    0.270  3.843  -9.152 18.647\n",
       "고용_취업자  0.315  0.492  -2.339  2.005\n",
       "우리경기    0.004  1.441  -3.646  4.249"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ex_new.describe().T[[\"mean\", \"std\", \"min\", \"max\"]].rename(columns={\"mean\": \"평균\", \"std\": \"표준편차\", \"min\": \"최소\", \"max\": \"최대\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5a2d82d",
   "metadata": {},
   "source": [
    "## 5. 분석용 중간 산출물\n",
    "\n",
    "다음 노트북(`02_분석.ipynb`)은 아래 파일만 읽는다. 패널은 100·log 변환 후 표본 104분기로 자른 값이고, 산업연관 행렬은 패널의 산업 순서로 정렬한 값이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7acc51ad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-08T00:05:20.852890Z",
     "iopub.status.busy": "2026-10-08T00:05:20.852890Z",
     "iopub.status.idle": "2026-10-08T00:05:20.885044Z",
     "shell.execute_reply": "2026-10-08T00:05:20.885044Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train    88\n",
      "valid     8\n",
      "test      8\n",
      "\n",
      "exog_new.csv               5,521 bytes\n",
      "exog_prev.csv              5,481 bytes\n",
      "io_36.csv                 11,035 bytes\n",
      "io_62.csv                 66,374 bytes\n",
      "io_69.csv                 77,785 bytes\n",
      "logpanel_36.csv           71,234 bytes\n",
      "logpanel_62.csv          120,122 bytes\n",
      "logpanel_69.csv          133,194 bytes\n",
      "logpanel_AGG.csv           4,756 bytes\n",
      "split.csv                  1,466 bytes\n"
     ]
    }
   ],
   "source": [
    "split = V.split_tags(qs, C.TRAIN_END, C.VALID_END)\n",
    "split.rename(\"구간\").rename_axis(\"분기\").to_csv(os.path.join(C.INTERIM, \"split.csv\"), encoding=\"utf-8-sig\")\n",
    "for g in C.GRIDS:\n",
    "    L, gdp = V.read_levels(C.LEVELS[g], C.GDP_COL, qs)\n",
    "    pd.concat([gdp.rename(C.GDP_COL), L], axis=1).rename_axis(\"분기\").to_csv(\n",
    "        os.path.join(C.INTERIM, f\"logpanel_{g}.csv\"), encoding=\"utf-8-sig\")\n",
    "    if g in W:\n",
    "        W[g].to_csv(os.path.join(C.INTERIM, f\"io_{g}.csv\"), encoding=\"utf-8-sig\")\n",
    "ex_new.rename_axis(\"분기\").to_csv(os.path.join(C.INTERIM, \"exog_new.csv\"), encoding=\"utf-8-sig\")\n",
    "ex_prev.rename_axis(\"분기\").to_csv(os.path.join(C.INTERIM, \"exog_prev.csv\"), encoding=\"utf-8-sig\")\n",
    "print(split.value_counts().reindex([\"train\", \"valid\", \"test\"]).to_string(), end=\"\\n\\n\")\n",
    "for f in sorted(os.listdir(C.INTERIM)):\n",
    "    print(f\"{f:<22} {os.path.getsize(os.path.join(C.INTERIM, 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
}
