{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 논문 1 · 챕터 1 — AR · VAR · BVAR\n",
    "\n",
    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/sdkparkforbi/gva-scenario-2025-2035/blob/main/ch1/code/ch1_ar_var_bvar.ipynb)\n",
    "\n",
    "> **Google Colab에서 바로 실행**됩니다. 배지를 누른 뒤 `런타임 → 모두 실행`.\n",
    "> 데이터는 아래 셀에서 자동으로 내려받고, 그림은 셀 실행 시 바로 표시됩니다.\n",
    "\n",
    "각 셀에는 **셀 번호 · 셀 제목**이 달려 있고, 코드에는 줄별 **주석**을 넣었습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 0. 준비 — Colab 환경과 데이터"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 1 · 한글 폰트 (koreanize-matplotlib) =====\n",
    "# 그림의 한글이 깨지지 않도록 koreanize-matplotlib 를 설치하고 불러옵니다.\n",
    "# import 하는 순간 matplotlib 한글 폰트가 자동 설정됩니다.\n",
    "_font_ok = False\n",
    "try:\n",
    "    import koreanize_matplotlib                       # 이미 설치돼 있으면 바로 사용\n",
    "    _font_ok = True\n",
    "except Exception:\n",
    "    try:\n",
    "        import subprocess, sys\n",
    "        subprocess.run([sys.executable,'-m','pip','install','-q','koreanize-matplotlib'], check=False)\n",
    "        import koreanize_matplotlib                   # 설치 후 다시 불러오기\n",
    "        _font_ok = True\n",
    "    except Exception:\n",
    "        _font_ok = False\n",
    "if not _font_ok:                                      # 폴백: 나눔폰트 직접 설치·등록\n",
    "    import subprocess\n",
    "    subprocess.run(['apt-get','-qq','-y','install','fonts-nanum'], check=False)\n",
    "    import matplotlib.font_manager as fm, matplotlib.pyplot as plt\n",
    "    _p = '/usr/share/fonts/truetype/nanum/NanumGothic.ttf'\n",
    "    try:\n",
    "        fm.fontManager.addfont(_p)\n",
    "        plt.rcParams['font.family'] = fm.FontProperties(fname=_p).get_name()  # 실제 등록 이름 사용\n",
    "    except Exception: pass\n",
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams['axes.unicode_minus'] = False           # 마이너스 기호 깨짐 방지\n",
    "print('한글 폰트 준비 완료')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 2 · 데이터 다운로드 =====\n",
    "# 이 챕터의 입력 데이터를 서버에서 로컬 data/ 폴더로 내려받습니다.\n",
    "import os, urllib.request, urllib.parse\n",
    "BASE = \"https://aiforalab.com/gva-scenario-2025-2035/ch1/data/\"          # 서버의 이 챕터 데이터 폴더\n",
    "FILES = [\"dlog_rates_sa.csv\"]\n",
    "os.makedirs(\"data\", exist_ok=True)\n",
    "for f in FILES:                                      # 한글 파일명은 URL 인코딩 후 요청\n",
    "    urllib.request.urlretrieve(BASE + urllib.parse.quote(f), os.path.join('data', f))\n",
    "print('다운로드 완료:', len(FILES), '개 ->', sorted(os.listdir('data')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 방법 구현과 계산"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 3 · 라이브러리 임포트·설정 =====\n",
    "import os, json\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib.colors import TwoSlopeNorm\n",
    "from scipy.cluster.hierarchy import linkage, dendrogram\n",
    "from scipy.spatial.distance import squareform"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 4 · Colab 경로: 데이터=data/, 그림=figures/ =====\n",
    "# ----- Colab 경로: 데이터=data/, 그림=figures/ -----\n",
    "import os\n",
    "DATA = \"data\"; PREP = \"data\"; FIG = \"figures\"; OUT = \"figures\"\n",
    "os.makedirs(\"data\", exist_ok=True); os.makedirs(\"figures\", exist_ok=True)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 5 · 계산 =====\n",
    "plt.rcParams[\"axes.unicode_minus\"] = False\n",
    "plt.rcParams[\"savefig.dpi\"] = 120\n",
    "plt.rcParams[\"axes.grid\"] = True\n",
    "plt.rcParams[\"grid.alpha\"] = 0.3"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 6 · 계산 =====\n",
    "A, B = \"컴퓨터, 전자 및 광학기기 제조업\", \"전기장비 제조업\"\n",
    "LA, LB = \"컴퓨터·전자\", \"전기장비\"\n",
    "P, LAM, WIN, TESTN = 4, 0.1, 30.0, 20     # 시차, 수축, 윈저, 테스트분기수\n",
    "NB = \"#1f4e79\"; NR = \"#c0392b\"; NG = \"#2e7d32\""
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 7 · 데이터 로드 =====\n",
    "rate = pd.read_csv(os.path.join(PREP, \"dlog_rates_sa.csv\"), encoding=\"utf-8-sig\", index_col=0)\n",
    "rate.index = pd.PeriodIndex(rate.index, freq=\"Q\")\n",
    "allg = rate.drop(columns=[\"총량GDP\"]).dropna().clip(-WIN, WIN)\n",
    "g = allg[[A, B]]\n",
    "idx = g.index\n",
    "m = g.values                      # (T,2)\n",
    "T = m.shape[0]; n = 2\n",
    "test_start = T - TESTN            # 테스트 타깃 시작 인덱스\n",
    "corrAB = float(np.corrcoef(m[:, 0], m[:, 1])[0, 1])"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 8 · 모형별 h-step 예측 함수 (train으로 추정, dynamic iterate) =====\n",
    "# ---------- 모형별 h-step 예측 함수 (train으로 추정, dynamic iterate) ----------\n",
    "def build_XY(mt, p):\n",
    "    Y = mt[p:]; TT = Y.shape[0]; nn = mt.shape[1]\n",
    "    X = np.ones((TT, nn*p+1))\n",
    "    for l in range(1, p+1):\n",
    "        X[:, (l-1)*nn:l*nn] = mt[p-l:-l]\n",
    "    X[:, -1] = 1.0\n",
    "    return Y, X"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 9 · 함수 정의: ar_sigma =====\n",
    "def ar_sigma(mt, p):\n",
    "    s = np.zeros(mt.shape[1])\n",
    "    for i in range(mt.shape[1]):\n",
    "        y = mt[:, i]; Y = y[p:]\n",
    "        Z = np.column_stack([y[p-l:len(y)-l] for l in range(1, p+1)] + [np.ones(len(Y))])\n",
    "        b, *_ = np.linalg.lstsq(Z, Y, rcond=None); s[i] = (Y - Z@b).std(ddof=Z.shape[1])\n",
    "    return s"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 10 · 함수 정의: iterate =====\n",
    "def iterate(B, mt, p, h):\n",
    "    buf = [r.copy() for r in mt[-p:]]\n",
    "    for _ in range(h):\n",
    "        x = np.ones(n*p+1)\n",
    "        for l in range(1, p+1):\n",
    "            x[(l-1)*n:l*n] = buf[-l]\n",
    "        buf.append(x @ B)\n",
    "    return buf[-1]"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 11 · 함수 정의: fit_AR =====\n",
    "def fit_AR(mt, p, h):                      # 개별 AR(4), 각 계열 독립\n",
    "    out = np.zeros(n)\n",
    "    for i in range(n):\n",
    "        y = mt[:, i]; Y = y[p:]\n",
    "        Z = np.column_stack([y[p-l:len(y)-l] for l in range(1, p+1)] + [np.ones(len(Y))])\n",
    "        b, *_ = np.linalg.lstsq(Z, Y, rcond=None)\n",
    "        buf = list(y[-p:])\n",
    "        for _ in range(h):\n",
    "            xz = [buf[-l] for l in range(1, p+1)] + [1.0]; buf.append(float(np.dot(b, xz)))\n",
    "        out[i] = buf[-1]\n",
    "    return out"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 12 · 함수 정의: fit_VAR =====\n",
    "def fit_VAR(mt, p, h):                      # OLS VAR\n",
    "    Y, X = build_XY(mt, p); B = np.linalg.lstsq(X, Y, rcond=None)[0]\n",
    "    return iterate(B, mt, p, h), B"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 13 · 함수 정의: fit_BVAR =====\n",
    "def fit_BVAR(mt, p, h, lam=LAM):            # 미네소타 BVAR\n",
    "    Y, X = build_XY(mt, p); sig = ar_sigma(mt, p)\n",
    "    om = np.zeros(n*p+1)\n",
    "    for l in range(1, p+1):\n",
    "        for j in range(n): om[(l-1)*n+j] = lam**2/(l**2*sig[j]**2)\n",
    "    om[-1] = 1e6\n",
    "    Ob = np.linalg.inv(np.diag(1/om) + X.T@X); B = Ob @ (X.T@Y)\n",
    "    return iterate(B, mt, p, h), B"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 14 · 재귀(확장창) 테스트 평가: 1단계 & 1년(4Q) =====\n",
    "# ---------- 재귀(확장창) 테스트 평가: 1단계 & 1년(4Q) ----------\n",
    "def evaluate(model):\n",
    "    res = {1: {\"act\": [], \"fc\": [], \"q\": []}, 4: {\"act\": [], \"fc\": [], \"q\": []}}\n",
    "    for k in range(test_start, T):                   # 타깃 인덱스\n",
    "        for h in (1, 4):\n",
    "            o = k - h                                # 예측 원점\n",
    "            if o < P + 4:\n",
    "                continue\n",
    "            train = m[:o+1]\n",
    "            if model == \"AR\":\n",
    "                fc = fit_AR(train, P, h)\n",
    "            elif model == \"VAR\":\n",
    "                fc, _ = fit_VAR(train, P, h)\n",
    "            else:\n",
    "                fc, _ = fit_BVAR(train, P, h)\n",
    "            res[h][\"act\"].append(m[k]); res[h][\"fc\"].append(fc); res[h][\"q\"].append(idx[k])\n",
    "    return res\n",
    "\n",
    "EV = {mo: evaluate(mo) for mo in [\"AR\", \"VAR\", \"BVAR\"]}"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 15 · 함수 정의: rmse =====\n",
    "def rmse(model, h, i):\n",
    "    a = np.array(EV[model][h][\"act\"])[:, i]; f = np.array(EV[model][h][\"fc\"])[:, i]\n",
    "    return float(np.sqrt(np.mean((a-f)**2)))\n",
    "\n",
    "RM = {mo: {h: [rmse(mo, h, i) for i in range(n)] for h in (1, 4)} for mo in EV}\n",
    "\n",
    "# 전표본 VAR-OLS vs BVAR 계수(비교용)\n",
    "_, Bvar = fit_VAR(m, P, 1); _, Bbvar = fit_BVAR(m, P, 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 결과와 그림"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 16 · 그림 1: 데이터 + 상관 =====\n",
    "# ================= 그림 1: 데이터 + 상관 =================\n",
    "tt = idx.to_timestamp(how=\"end\")\n",
    "fig, ax = plt.subplots(1, 2, figsize=(13, 4.3))\n",
    "ax[0].axhline(0, color=\"grey\", lw=.6)\n",
    "ax[0].plot(tt, m[:, 0], color=NB, lw=.8, label=LA)\n",
    "ax[0].plot(tt, m[:, 1], color=NR, lw=.8, alpha=.8, label=LB)\n",
    "ax[0].axvspan(idx[test_start].to_timestamp(how=\"end\"), tt[-1], color=\"orange\", alpha=.12)\n",
    "ax[0].text(idx[test_start].to_timestamp(how=\"end\"), ax[0].get_ylim()[1]*.9, \" Test\", color=\"#b26a00\", fontsize=9)\n",
    "ax[0].set_title(f\"(a) 두 산업 분기 성장률 (오렌지=Test 최근 {TESTN}분기)\"); ax[0].set_ylabel(\"%\"); ax[0].legend(fontsize=8)\n",
    "ax[1].scatter(m[:, 0], m[:, 1], s=14, color=NB, alpha=.5, edgecolor=\"none\")\n",
    "ax[1].set_title(f\"(b) 두 산업 성장률 산점 — 상관 {corrAB:.2f}\"); ax[1].set_xlabel(LA+\" %\"); ax[1].set_ylabel(LB+\" %\")\n",
    "fig.suptitle(\"그림 1. 데이터: 컴퓨터·전자 ~ 전기장비 (상관 높은 쌍)\", fontweight=\"bold\")\n",
    "fig.tight_layout(rect=[0, 0, 1, 0.94]); fig.savefig(os.path.join(OUT, \"fig1_data.png\"), bbox_inches=\"tight\"); plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 17 · 그림 2/3: AR, VAR 예측 추종 =====\n",
    "# ================= 그림 2/3: AR, VAR 예측 추종 =================\n",
    "def plot_track(model, fname, title):\n",
    "    fig, ax = plt.subplots(1, 2, figsize=(13, 4.3))\n",
    "    for i, (lab) in enumerate([LA, LB]):\n",
    "        q1 = [q.to_timestamp(how=\"end\") for q in EV[model][1][\"q\"]]\n",
    "        q4 = [q.to_timestamp(how=\"end\") for q in EV[model][4][\"q\"]]\n",
    "        a1 = np.array(EV[model][1][\"act\"])[:, i]\n",
    "        f1 = np.array(EV[model][1][\"fc\"])[:, i]\n",
    "        f4 = np.array(EV[model][4][\"fc\"])[:, i]\n",
    "        ax[i].axhline(0, color=\"grey\", lw=.6)\n",
    "        ax[i].plot(q1, a1, \"o-\", ms=3, color=\"#333\", label=\"실측\")\n",
    "        ax[i].plot(q1, f1, \"s-\", ms=3, color=NB, label=f\"1단계 (RMSE {RM[model][1][i]:.2f})\")\n",
    "        ax[i].plot(q4, f4, \"^--\", ms=3, color=NR, label=f\"1년앞 다이내믹 (RMSE {RM[model][4][i]:.2f})\")\n",
    "        ax[i].set_title(f\"{lab}\"); ax[i].set_ylabel(\"%\"); ax[i].legend(fontsize=8)\n",
    "    fig.suptitle(title, fontweight=\"bold\")\n",
    "    fig.tight_layout(rect=[0, 0, 1, 0.93]); fig.savefig(os.path.join(OUT, fname), bbox_inches=\"tight\"); plt.show()\n",
    "\n",
    "plot_track(\"AR\", \"fig2_ar.png\", \"그림 2. AR(4) — 테스트 구간 1단계·1년앞 예측 추종\")\n",
    "plot_track(\"VAR\", \"fig3_var.png\", \"그림 3. VAR(4) — 테스트 구간 1단계·1년앞 예측 추종\")"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 18 · 그림 4: VAR vs BVAR 계수 =====\n",
    "# ================= 그림 4: VAR vs BVAR 계수 =================\n",
    "fig, ax = plt.subplots(1, 2, figsize=(12, 4.6))\n",
    "vv = Bvar[:-1].ravel(); bb = Bbvar[:-1].ravel()\n",
    "lim = max(abs(vv).max(), abs(bb).max())*1.1\n",
    "ax[0].plot([-lim, lim], [-lim, lim], color=\"grey\", ls=\"--\", lw=1)\n",
    "ax[0].axhline(0, color=\"#ccc\", lw=.6); ax[0].axvline(0, color=\"#ccc\", lw=.6)\n",
    "ax[0].scatter(vv, bb, s=30, color=NB, alpha=.7)\n",
    "ax[0].set_xlabel(\"VAR (OLS) 계수\"); ax[0].set_ylabel(\"BVAR (미네소타) 계수\")\n",
    "ax[0].set_title(\"(a) 2변수에선 VAR과 BVAR 거의 동일\\n(BVAR가 0쪽으로 살짝 수축)\")\n",
    "# 축약 요약: |계수| 평균\n",
    "ax[1].bar([\"VAR (OLS)\", \"BVAR\"], [np.abs(vv).mean(), np.abs(bb).mean()], color=[NR, NB])\n",
    "ax[1].set_ylabel(\"평균 |동학계수|\")\n",
    "ax[1].set_title(f\"(b) 평균 계수 크기\\nBVAR가 {(1-np.abs(bb).mean()/np.abs(vv).mean())*100:.0f}% 더 작음(수축)\")\n",
    "fig.suptitle(\"그림 4. VAR와 BVAR의 차이 = '수축(prior)' — 변수 적을 땐 미미\", fontweight=\"bold\")\n",
    "fig.tight_layout(rect=[0, 0, 1, 0.92]); fig.savefig(os.path.join(OUT, \"fig4_var_vs_bvar.png\"), bbox_inches=\"tight\"); plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 19 · 그림 5: RMSE 비교 =====\n",
    "# ================= 그림 5: RMSE 비교 =================\n",
    "fig, ax = plt.subplots(1, 2, figsize=(13, 4.3))\n",
    "models = [\"AR\", \"VAR\", \"BVAR\"]; x = np.arange(len(models)); w = .35\n",
    "for hi, h in enumerate((1, 4)):\n",
    "    avg = [np.mean(RM[mo][h]) for mo in models]\n",
    "    ax[hi].bar(x, avg, color=[NR, NB, NG])\n",
    "    ax[hi].bar_label(ax[hi].containers[0], fmt=\"%.2f\")\n",
    "    ax[hi].set_xticks(x); ax[hi].set_xticklabels(models)\n",
    "    ax[hi].set_title(f\"({'a' if h==1 else 'b'}) {'1단계' if h==1 else '1년앞(4Q)'} 예측 평균 RMSE\")\n",
    "    ax[hi].set_ylabel(\"RMSE (%p)\")\n",
    "fig.suptitle(\"그림 5. 테스트 구간 예측정확도 — AR vs VAR vs BVAR\", fontweight=\"bold\")\n",
    "fig.tight_layout(rect=[0, 0, 1, 0.93]); fig.savefig(os.path.join(OUT, \"fig5_rmse.png\"), bbox_inches=\"tight\"); plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 20 · 그림 6: 상관 군집(바텀업 설계) =====\n",
    "# ================= 그림 6: 상관 군집(바텀업 설계) =================\n",
    "C = allg.corr().values\n",
    "names = list(allg.columns)\n",
    "short = [s.replace(\" 제조업\", \"\").replace(\" 및 \", \"·\").replace(\"서비스업\", \"서비스\")[:10] for s in names]\n",
    "D = 1 - C; np.fill_diagonal(D, 0); D = (D + D.T)/2\n",
    "Z = linkage(squareform(D, checks=False), method=\"average\")\n",
    "fig, ax = plt.subplots(1, 2, figsize=(15, 7), gridspec_kw={\"width_ratios\": [1.1, 1]})\n",
    "dn = dendrogram(Z, labels=short, ax=ax[0], orientation=\"left\", color_threshold=0.72, leaf_font_size=7)\n",
    "ax[0].set_title(\"(a) 성장률 상관 기반 위계군집 (바텀업 병합)\")\n",
    "ax[0].set_xlabel(\"거리 = 1 - 상관\")\n",
    "order = dn[\"leaves\"]\n",
    "Cn = C[np.ix_(order, order)]\n",
    "im = ax[1].imshow(Cn, cmap=\"RdBu_r\", norm=TwoSlopeNorm(0, -1, 1), aspect=\"auto\")\n",
    "ax[1].set_xticks(range(len(order))); ax[1].set_xticklabels([short[i] for i in order], rotation=90, fontsize=5)\n",
    "ax[1].set_yticks(range(len(order))); ax[1].set_yticklabels([short[i] for i in order], fontsize=5)\n",
    "ax[1].set_title(\"(b) 군집순 상관행렬 (블록=함께 묶을 후보)\")\n",
    "fig.colorbar(im, ax=ax[1], shrink=.8, pad=.02)\n",
    "fig.suptitle(\"그림 6. 바텀업 설계: 상관 높은 산업을 블록으로 묶어 최종 BVAR로\", fontweight=\"bold\")\n",
    "fig.tight_layout(rect=[0, 0, 1, 0.95]); fig.savefig(os.path.join(OUT, \"fig6_cluster.png\"), bbox_inches=\"tight\"); plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 21 · 결과 JSON =====\n",
    "# ---------- 결과 JSON ----------\n",
    "results = {\n",
    "    \"pair\": [LA, LB], \"corr\": round(corrAB, 3), \"p\": P, \"lam\": LAM, \"test_n\": TESTN,\n",
    "    \"test_period\": [str(idx[test_start]), str(idx[-1])],\n",
    "    \"train_end\": str(idx[test_start-1]),\n",
    "    \"rmse\": {mo: {str(h): [round(v, 3) for v in RM[mo][h]] for h in (1, 4)} for mo in models},\n",
    "    \"rmse_avg\": {mo: {str(h): round(float(np.mean(RM[mo][h])), 3) for h in (1, 4)} for mo in models},\n",
    "    \"coef_abs_var\": round(float(np.abs(Bvar[:-1]).mean()), 4),\n",
    "    \"coef_abs_bvar\": round(float(np.abs(Bbvar[:-1]).mean()), 4),\n",
    "    \"top_pairs\": None,\n",
    "}\n",
    "json.dump(results, open(os.path.join(OUT, \"results.json\"), \"w\", encoding=\"utf-8\"), ensure_ascii=False, indent=2)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ===== 셀 22 · 결과 출력 =====\n",
    "print(\"=== 테스트 RMSE (낮을수록 좋음) ===\")\n",
    "print(f\"{'모형':6}{'1단계('+LA[:6]+')':>16}{'1단계('+LB[:4]+')':>14}{'1년('+LA[:6]+')':>14}{'1년('+LB[:4]+')':>12}\")\n",
    "for mo in models:\n",
    "    print(f\"{mo:6}{RM[mo][1][0]:>16.2f}{RM[mo][1][1]:>14.2f}{RM[mo][4][0]:>14.2f}{RM[mo][4][1]:>12.2f}\")\n",
    "print(f\"\\n평균 |동학계수|: VAR={np.abs(Bvar[:-1]).mean():.3f}  BVAR={np.abs(Bbvar[:-1]).mean():.3f}\")\n",
    "print(\"\\n저장: figures/ (fig1~6.png, results.json)\")"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "ch1_ar_var_bvar.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}