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를 추가로 만든다.
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에 기록한 값과 대조한다. 하나라도 다르면 이후 계산이 기록된 산출물과 달라질 수 있으므로 여기서 멈춘다.
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
| 크기(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)는 두바이유(FREDPOILDUBUSDM) 분기 평균의 100×자연로그이다. 국내 도입 원유의 대부분이 중동산이므로 두바이유를 쓴다. 2026-10 정비에서 수집 노트북(bokdpm_pipeline/01_데이터입수.ipynb)·자료 원천 기록(used_data_sources.json)·관측자료 파일을 모두 이 자료파일과 같은 두바이유 기준으로 정리하였다(이전에는 수집 코드와 문서가 서부텍사스유로 적혀 있었다). - 미국 대출태도(
BLT_us)의 원천은 확정되지 않았다. FRED 선임대출담당자 서베이(DRTSCILM)와의 상관은 0.90이며, 단순 조합·시차·평활로도 재현되지 않는다. 자료파일 값을 그대로 고정한다. - ECOS가 제공하지 않는 구간(국내 대출태도 2000–2001년, 정부지출 증감률 2000년)은 원본 BOKDPM 자료의 값이다.
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변수, 결측 없음
| 원천·변환 | 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⁻⁶ 미만)이면 이 자료파일이 추정에 쓰인 것이다.
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년 지수가 필요하다.
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... |
| 중위(천 명) | 고위(천 명) | 저위(천 명) | |
|---|---|---|---|
| 연도 | |||
| 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)도 다시 받아 비교한다.
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이면 재수집 비교표)가 남는다.
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