01 데이터 입수 — BOKDPM 관측 20변수와 시나리오 입력¶

목적. 3장의 계산에 필요한 입력을 불러오고, 고정해 둔 스냅샷이 기록된 파일과 같은지 검증한다.

2026-10 정비판. 이 패키지는 기준선을 정비한 판이다. 정상상태(장기 수렴값) 13개를 현재 여건에 맞게 바꾸어 BOKDPM을 다시 추정했으므로, 사후 표본·평활값·전망값 스냅샷은 2026-10 재추정 결과이다. 관측 20변수 자료파일은 이전 판과 같다. 이전 판(2026-10-08) 패키지는 포털의 보관본으로 남아 있다.

입력 내용 출처
BOKDPM_data_2025.m BOKDPM 관측 20변수, 2000Q1–2025Q4(104분기). 추정에 실제로 쓴 Dynare 자료파일(유가 = 두바이유) ECOS·FRED(원천 계열은 2절)
bokdpm_posterior_draws.csv.gz 메트로폴리스–헤이스팅스 사후 표본(2블록 × 21,000개, 번인 제외) 본 연구 Dynare 재추정(2026-10)
bokdpm_smoothed_2000_2025.csv 내생변수 70개의 칼만 평활값 본 연구 Dynare 재추정(2026-10)
bokdpm_forecast_2026_2035.csv 내생변수 70개의 40분기 기준선 전망 본 연구 Dynare 전망(2026-10)
bokdpm_posterior_mode.csv 사후최빈값(추정 실행·전망 실행) 본 연구 Dynare 결과(2026-10)
ecos_labor_force.csv 경제활동인구·취업자, 1999Q3–2026Q2 ECOS 901Y027
keis_labor_supply_2024_2034.csv 경제활동인구·취업자 증감 전망(2024–2029, 2029–2034) 고용노동부·한국고용정보원 「2024~2034 중장기 인력수급 전망」(2026-02)
kosis_생산연령인구.csv 생산연령인구(15–64세) 추계 저위·중위·고위 KOSIS 장래인구추계(DT_1BPA003)
cpi_kor_1999_2000.csv 소비자물가지수 1999–2000 원본 BOKDPM 워크북(ECOS 소비자물가지수)

원천 경로·계열 코드·입수일은 data/inputs/PROVENANCE.md에 정리하였다.

실행 방식. 기본 실행은 스냅샷의 SHA-256과 형태를 검증한다. 환경변수 ONLINE=1을 주면 4절에서 ECOS·FRED로 관측 20변수를 다시 받아 스냅샷과 비교한다(인증키 API_KEY_BOK·API_KEY_FRED 필요).

산출물. data/interim/obs20_2000_2025.csv(관측 20변수 표). ONLINE=1이면 online_obs20.csv·online_compare.csv를 추가로 만든다.

In [1]:
import os, time
import numpy as np, pandas as pd
import config as C
import bokdpm_macro as B
pd.set_option("display.float_format", lambda v: f"{v:,.4f}")
pd.set_option("display.width", 160)
env = C.setup()
Python 3.12.10 · pandas 2.2.3 · numpy 2.4.6 · scipy 1.18.0 · matplotlib 3.11.0
  실행 방식   ONLINE=0 (ECOS·FRED 재수집) · FULL=0 (Dynare 재추정)
  ECOS 키     없음 · FRED 키 없음  (ONLINE=0이면 쓰지 않는다)
  Octave      있음 · Dynare 있음  (FULL=0이면 쓰지 않는다)
  BOKDPM_data_2025.m               있음      23,440 bytes
  bokdpm_forecast_2026_2035.csv    있음      56,135 bytes
  bokdpm_posterior_draws.csv.gz    있음   5,426,716 bytes
  bokdpm_posterior_mode.csv        있음       2,909 bytes
  bokdpm_smoothed_2000_2025.csv    있음     142,828 bytes
  cpi_kor_1999_2000.csv            있음         146 bytes
  ecos_labor_force.csv             있음       2,620 bytes
  keis_labor_supply_2024_2034.csv  있음         353 bytes
  kosis_생산연령인구.csv                 있음         735 bytes

1. 스냅샷 무결성¶

입력 파일 9개의 SHA-256을 config.py에 기록한 값과 대조한다. 하나라도 다르면 이후 계산이 기록된 산출물과 달라질 수 있으므로 여기서 멈춘다.

In [2]:
rows = []
for name, expect in C.INPUT_SHA256.items():
    p = os.path.join(C.INPUTS, name)
    h = B.sha256(p)
    rows.append({"파일": name, "크기(bytes)": os.path.getsize(p), "SHA-256(앞 16자리)": h[:16], "기록값과 일치": h == expect})
chk = pd.DataFrame(rows).set_index("파일")
assert chk["기록값과 일치"].all(), "스냅샷 파일이 기록된 SHA-256과 다르다 — data/inputs/를 원래 상태로 되돌린다"
chk
Out[2]:
크기(bytes) SHA-256(앞 16자리) 기록값과 일치
파일
BOKDPM_data_2025.m 23440 79c7586814e92f71 True
bokdpm_forecast_2026_2035.csv 56135 9e2a288dace40f09 True
bokdpm_posterior_draws.csv.gz 5426716 30b3d2d62740f235 True
bokdpm_posterior_mode.csv 2909 79d9f736deb6bdc2 True
bokdpm_smoothed_2000_2025.csv 142828 a6355022bba39ead True
cpi_kor_1999_2000.csv 146 b64596b28c504fe0 True
ecos_labor_force.csv 2620 b647956255276a61 True
keis_labor_supply_2024_2034.csv 353 a3d0d4e92f5ac218 True
kosis_생산연령인구.csv 735 a51389439be67d17 True

2. 관측 20변수 — 재추정에 쓴 자료파일¶

관측변수는 한국 14개, 미국 6개(유가 포함), 합계 20개이다. 지수형 변수는 100×자연로그, 금리·비율은 원계열을 쓰며, 원천 계열과 변환은 아래 표와 같다.

세 가지 사항을 밝혀 둔다.

  • 유가(LOIL_us)는 두바이유(FRED POILDUBUSDM) 분기 평균의 100×자연로그이다. 국내 도입 원유의 대부분이 중동산이므로 두바이유를 쓴다. 2026-10 정비에서 수집 노트북(bokdpm_pipeline/01_데이터입수.ipynb)·자료 원천 기록(used_data_sources.json)·관측자료 파일을 모두 이 자료파일과 같은 두바이유 기준으로 정리하였다(이전에는 수집 코드와 문서가 서부텍사스유로 적혀 있었다).
  • 미국 대출태도(BLT_us)의 원천은 확정되지 않았다. FRED 선임대출담당자 서베이(DRTSCILM)와의 상관은 0.90이며, 단순 조합·시차·평활로도 재현되지 않는다. 자료파일 값을 그대로 고정한다.
  • ECOS가 제공하지 않는 구간(국내 대출태도 2000–2001년, 정부지출 증감률 2000년)은 원본 BOKDPM 자료의 값이다.
In [3]:
obs = B.read_dynare_data(C.DATAFILE)
assert obs.shape == (C.PUBLISHED["관측자료_분기"], C.PUBLISHED["관측변수"]), obs.shape
assert list(obs.columns) == B.VAROBS and obs.notna().all().all()
print(f"관측자료: {obs.shape[0]}분기({obs.index[0]}–{obs.index[-1]}) × {obs.shape[1]}변수, 결측 없음")
src = pd.DataFrame({"원천·변환": B.SOURCES}).loc[B.VAROBS]
src["2000Q1"], src["2025Q4"] = obs.iloc[0], obs.iloc[-1]
src
관측자료: 104분기(2000Q1–2025Q4) × 20변수, 결측 없음
Out[3]:
원천·변환 2000Q1 2025Q4
UNR_kor ECOS 901Y027/I61BC 실업률 4.8000 2.9000
RS_kor ECOS 722Y001/0101000 콜금리(월→분기 평균) 4.9167 2.5000
LCPI_kor ECOS 901Y009/0 소비자물가지수(월→분기 평균), 100·ln 413.8175 476.5559
LGDP_kor ECOS 200Y108/10101 실질GDP(계절조정), 100·ln 1,206.7517 1,286.5561
Y_kor ECOS 200Y108/10101 실질GDP의 HP 순환성분(λ=1600) -0.8543 0.1015
BLT_kor ECOS 514Y001/AA 대출태도(2002Q1~), 2000–2001은 원본 B... 27.8021 -21.0000
GE_kor ECOS 901Y013/B01 통합재정 총지출(누계→분기 흐름), 4분기 로그차분,... -10.0017 -0.7459
CA_kor ECOS 301Y013/000000 경상수지 ÷ 명목GDP × 100 -0.2477 8.7614
LS_kor ECOS 731Y004/0000001 원/달러 환율(월→분기 평균), 100·ln 702.1739 727.3069
LDEF_kor ECOS 200Y107/10101 명목GDP ÷ 실질GDP, 100·ln 412.5399 475.0039
RS_us FRED FEDFUNDS(월→분기 평균) 5.6767 3.8967
LCPI_us FRED CPIAUCSL(월→분기 평균), 100·ln 513.6386 578.5507
LGDP_us FRED GDPC1, 100·ln 953.8071 1,008.8129
Y_us FRED GDPC1·GDPPOT, 100·ln(실질/잠재) 1.6680 1.0217
BLT_us 원천 미확정 합성 대출태도 지수(2026-08-01 자료파일 값 고정; 참고 계열 ... 5.6000 -7.1000
CD_kor ECOS 817Y002/010502000 CD(91일)(일→분기 평균) 7.1337 2.6988
CB_kor ECOS 817Y002/010300000 회사채(3년, AA−)(일→분기 평균) 10.0836 3.2923
LHOUSE_kor ECOS 901Y062/P63A 주택매매가격지수(월→분기 평균), 100·ln 372.5750 460.0315
LOIL_us FRED POILDUBUSDM 두바이유(월→분기 평균), 100·ln 319.4262 415.2531
TB_kor ECOS 817Y002/010200000 국고채(3년)(일→분기 평균) 9.1082 2.8437

추정에 쓴 자료인지 확인¶

관측방정식에 측정오차가 없으므로, 추정 결과의 칼만 평활값은 관측변수에서 자료와 같아야 한다. 평활값(bokdpm_smoothed_2000_2025.csv)과 자료파일의 차이가 수치 오차 범위(10⁻⁶ 미만)이면 이 자료파일이 추정에 쓰인 것이다.

In [4]:
sm = B.read_exact(C.SMOOTHED, index_col=0)
gap = pd.Series({v: float((sm[v] - obs[v]).abs().max()) for v in B.VAROBS}, name="최대절대차")
print(f"평활값 − 자료파일: 최대 {gap.max():.2e} ({gap.idxmax()})")
assert gap.max() < 1e-6, "평활값이 자료파일과 다르다 — 추정에 다른 자료가 쓰였다"
obs.to_csv(os.path.join(C.INTERIM, "obs20_2000_2025.csv"), encoding="utf-8-sig")
평활값 − 자료파일: 최대 2.88e-09 (BLT_us)

3. 시나리오·외생 구성 입력¶

  • 경제활동인구는 산업 외생 '취업자 수'(= 경제활동인구 × (1 − 실업률/100))를 만드는 데 쓴다. 외생 6종 가운데 BOKDPM 밖에서 가져오는 유일한 계열이다. 관측 구간(2026Q2까지) 이후는 중장기 인력수급 전망(2024–2034)의 기간별 증가율로 잇는다(02 6절). 이전 판은 최근 20분기 로그 선형추세(연 0.86%)로 연장하여, 생산연령인구 감소가 반영되지 않았다.
  • 생산연령인구 추계(통계청 장래인구추계, 2022년 기준)의 저위·중위·고위 경로는 시나리오의 인구 레버이다. 2026-10 정비부터 이 레버가 경제활동인구에 곱해져 외생 '취업자 수'에 전달된다.
  • 1999–2000년 소비자물가지수는 2000년 실질금리(회사채 수익률 − 전년동기 물가상승률)를 계산하는 데만 쓴다. 관측자료가 2000Q1에 시작하므로 2000년의 전년동기 상승률을 구하려면 1999년 지수가 필요하다.
In [5]:
lab = pd.read_csv(C.LABOR, index_col=0, encoding="utf-8-sig")
pop = pd.read_csv(C.POP, encoding="utf-8-sig")
cpi = pd.read_csv(C.CPI, encoding="utf-8-sig")
keis = pd.read_csv(C.KEIS, encoding="utf-8-sig")
infl = B.inflation_2000(cpi)
print(f"경제활동인구·취업자: {lab.index[0]}–{lab.index[-1]} ({len(lab)}분기), 2025Q4 취업자 {lab.loc['2025Q4', '취업자']:,.0f}천 명")
print("2000년 전년동기 CPI 상승률(%, 소수 둘째 자리):", infl)
g = B.keis_growth(lab, keis)
print(f"인력수급 전망 경제활동인구 증가율(연, %): 2024–2029 {g[2029]:+.3f} · 2029–2034 {g[2030]:+.3f}")
display(keis.set_index("구간"))
p = pop.set_index("연도").loc[[2025, 2030, 2035]]
p.columns = ["중위(천 명)", "고위(천 명)", "저위(천 명)"]
p.astype(int)
경제활동인구·취업자: 1999Q3–2026Q2 (108분기), 2025Q4 취업자 28,792천 명
2000년 전년동기 CPI 상승률(%, 소수 둘째 자리): [1.91, 1.57, 2.94, 2.51]
인력수급 전망 경제활동인구 증가율(연, %): 2024–2029 +0.234 · 2029–2034 -0.142
경제활동인구_증감_천명 취업자_증감_천명 출처
구간
2024-2029 346 367 고용노동부·한국고용정보원, 「2024~2034 중장기 인력수급 전망」(2026-02...
2029-2034 -210 -303 고용노동부·한국고용정보원, 「2024~2034 중장기 인력수급 전망」(2026-02...
Out[5]:
중위(천 명) 고위(천 명) 저위(천 명)
연도
2025 35912 36039 35785
2030 34166 34568 33758
2035 31878 32532 31219

4. (선택) ECOS·FRED 재수집과 스냅샷 비교 — ONLINE=1¶

bokdpm_pipeline/01_데이터입수.ipynb의 수집·변환 절차를 그대로 따르며, 유가는 추정 자료와 같은 두바이유로 받는다. HP 필터는 원본과 같이 수집 시점까지의 전 표본에 적용하므로, 새 분기가 공표되면 표본 끝 부근의 산출갭(Y_kor)이 조금씩 달라진다. 미국 국민계정의 연간 개정도 LGDP_us·Y_us의 차이로 나타난다. 이러한 차이는 자료 개정으로 보고 허용한다. 원천이 확정되지 않은 BLT_us와 ECOS 미제공 구간은 스냅샷 값을 쓰므로 비교 대상이 아니다(표의 마지막 열).

경제활동인구·취업자(ECOS 901Y027)와 1999–2000년 소비자물가(ECOS 901Y009)도 다시 받아 비교한다.

In [6]:
if C.ONLINE:
    assert C.API_KEY_BOK and C.API_KEY_FRED, "ONLINE=1에는 환경변수 API_KEY_BOK·API_KEY_FRED가 필요하다"
    new, drtscilm = B.refetch_observables(C.API_KEY_BOK, C.API_KEY_FRED, obs)
    cmp = B.compare_observables(new, obs)
    new.to_csv(os.path.join(C.INTERIM, "online_obs20.csv"), encoding="utf-8-sig")
    cmp.to_csv(os.path.join(C.INTERIM, "online_compare.csv"), encoding="utf-8-sig")
    print(f"재수집 시각 {time.strftime('%Y-%m-%d %H:%M')} · 동일 {int((cmp['판정'] == '동일').sum())}개, 그 밖 {int((cmp['판정'] != '동일').sum())}개")
    print(f"BLT_us 참고: FRED DRTSCILM과의 상관 {np.corrcoef(drtscilm, obs['BLT_us'])[0, 1]:.3f}, 최대 차이 {np.abs(drtscilm - obs['BLT_us']).max():.1f}")
    lab_new = B.refetch_labor(C.API_KEY_BOK)
    print(f"경제활동인구·취업자: 최신 {lab_new.index[-1]}, 스냅샷 구간 최대 차이 {(lab_new.reindex(lab.index) - lab).abs().max().max():.1f}천 명")
    infl_new = B.refetch_inflation_2000(C.API_KEY_BOK)
    print(f"2000년 CPI 상승률: ECOS 재계산 {infl_new} · 스냅샷 {infl} → {'일치' if infl_new == infl else '불일치'}")
    display(cmp)
else:
    print("ONLINE=1이 아니므로 재수집을 건너뛴다. 이 실행은 스냅샷의 무결성과 추정 자료와의 일치만 확인하였다.")
ONLINE=1이 아니므로 재수집을 건너뛴다. 이 실행은 스냅샷의 무결성과 추정 자료와의 일치만 확인하였다.

5. 산출물¶

다음 노트북(02_분석.ipynb)은 스냅샷 입력을 직접 읽는다. data/interim/에는 관측 20변수 표(와 ONLINE=1이면 재수집 비교표)가 남는다.

In [7]:
for f in sorted(os.listdir(C.INTERIM)):
    p = os.path.join(C.INTERIM, f)
    if os.path.isfile(p):
        print(f"{f:<28} {os.path.getsize(p):>10,d} bytes")
obs20_2000_2025.csv              19,663 bytes