지난 30년 인구 구조를 분석하고, 앞으로 10년의 지방소멸위험 추세선을 그리며, 도시의 지속가능한 미래 정책을 개발하는 파이썬 프로젝트
도시가 사라지는 속도,
데이터로 붙잡다
대한민국의 미래는 단순히 인구 수의 문제가 아니라 청년, 일자리, 교육, 교통, 돌봄, 산업이 함께 움직이는 지속가능성의 문제입니다. 공공데이터 API로 지역 인구 구조를 읽으면 소멸 위험 도시의 현재와 미래를 더 분명하게 볼 수 있습니다.
Q1. 지난 30년 동안 지역 인구는 얼마나 줄었는가?
Q2. 20~39세 여성 인구와 65세 이상 고령 인구의 비율은 어떻게 변했는가?
Q3. 앞으로 10년 뒤 소멸위험지수는 어느 단계가 될 가능성이 큰가?
| 데이터 | 활용 방법 |
|---|---|
| 행정안전부 주민등록 인구통계 / 주민등록 인구 OpenAPI | 시도·시군구·읍면동별 총인구, 남녀 인구, 연령대별 인구를 가져와 도시별 감소 추세를 분석합니다. |
| KOSIS 공유서비스 API | 인구총조사, 장래인구, 지역별 인구구조, 인구이동 통계를 불러와 장기 추세 분석에 활용합니다. |
| 지방소멸위험지수 | 일반적으로 20~39세 여성 인구 / 65세 이상 인구로 계산합니다. 0.5 미만이면 소멸위험지역, 0.2 미만이면 소멸고위험지역으로 해석합니다. |
# ============================================================
# 공공데이터 API 인증키 활용 프로젝트
# 주제: 지속가능 발전과 대한민국의 소멸 도시
# 분석: 지난 30년 + 앞으로 10년 추세선, 인사이트, 정책 개발
# ============================================================
import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
# ------------------------------------------------------------
# 1. 공공데이터 API 인증키 입력
# ------------------------------------------------------------
SERVICE_KEY = "여기에_공공데이터_API_인증키를_입력하세요"
# 예시 URL입니다.
# 실제 수업에서는 공공데이터포털 또는 KOSIS API 활용가이드의 URL로 바꾸세요.
# 예: 행정안전부 주민등록 인구통계, KOSIS 공유서비스, 지역별 주민등록인구 API 등
API_URL = "https://apis.data.go.kr/1741000/admmPpltnHhStus/selectAdmmPpltnHhStus"
# 분석할 도시명 예시
TARGET_CITY = "전라북도 임실군"
# ------------------------------------------------------------
# 2. 샘플 데이터 생성
# 실제 API 연동 전에도 수업 실습이 가능하도록 구성
# ------------------------------------------------------------
def make_sample_data():
years = np.arange(1996, 2026)
# 총인구: 30년 동안 점진적 감소를 가정한 예시
total_population = [
42000, 41200, 40500, 39800, 39100,
38400, 37700, 36900, 36100, 35200,
34300, 33500, 32700, 31900, 31200,
30500, 29800, 29100, 28500, 27900,
27300, 26800, 26300, 25800, 25300,
24800, 24400, 24000, 23600, 23200
]
# 20~39세 여성 인구: 청년층 유출로 감소
women_20_39 = [
4200, 4050, 3920, 3780, 3650,
3500, 3360, 3210, 3070, 2930,
2790, 2660, 2530, 2410, 2300,
2190, 2080, 1980, 1890, 1800,
1710, 1630, 1560, 1490, 1420,
1360, 1300, 1240, 1180, 1130
]
# 65세 이상 인구: 고령화로 증가
elderly_65 = [
6200, 6400, 6650, 6900, 7150,
7420, 7700, 7980, 8260, 8550,
8840, 9130, 9430, 9730, 10020,
10300, 10580, 10850, 11100, 11350,
11600, 11820, 12020, 12200, 12370,
12520, 12650, 12770, 12880, 12980
]
df = pd.DataFrame({
"year": years,
"city": TARGET_CITY,
"total_population": total_population,
"women_20_39": women_20_39,
"elderly_65": elderly_65
})
return df
# ------------------------------------------------------------
# 3. 공공데이터 API 호출 함수
# ------------------------------------------------------------
def fetch_public_data():
params = {
"serviceKey": SERVICE_KEY,
"pageNo": 1,
"numOfRows": 1000,
"type": "json"
}
try:
response = requests.get(API_URL, params=params, timeout=10)
response.raise_for_status()
data = response.json()
# 실제 API마다 JSON 구조가 다르므로 아래 부분은 활용가이드에 맞추어 수정합니다.
# 필요한 컬럼: year, city, total_population, women_20_39, elderly_65
rows = []
# 예시 파싱 구조
items = data.get("items", []) or data.get("response", {}).get("body", {}).get("items", [])
for item in items:
year = item.get("year") or item.get("stdYear") or item.get("baseYear")
city = item.get("city") or item.get("sggNm") or item.get("region")
total = item.get("total_population") or item.get("totPop") or item.get("population")
women = item.get("women_20_39") or item.get("female20to39")
elderly = item.get("elderly_65") or item.get("over65")
if year and city and total and women and elderly:
rows.append({
"year": int(year),
"city": str(city),
"total_population": int(str(total).replace(",", "")),
"women_20_39": int(str(women).replace(",", "")),
"elderly_65": int(str(elderly).replace(",", ""))
})
df = pd.DataFrame(rows)
if df.empty:
print("API 데이터 구조가 예시와 달라 샘플 데이터를 사용합니다.")
return make_sample_data()
return df
except Exception as e:
print("API 호출 실패 또는 파싱 실패:", e)
print("수업용 샘플 데이터를 사용합니다.")
return make_sample_data()
# ------------------------------------------------------------
# 4. 데이터 준비
# ------------------------------------------------------------
df = fetch_public_data()
df = df[df["city"].str.contains(TARGET_CITY.split()[-1], na=False) | (df["city"] == TARGET_CITY)]
if df.empty:
df = make_sample_data()
df = df.dropna().sort_values("year")
# 최근 30년 선택
current_year = df["year"].max()
df_30 = df[df["year"] >= current_year - 29].copy()
# 지방소멸위험지수 = 20~39세 여성 인구 / 65세 이상 인구
df_30["extinction_risk_index"] = df_30["women_20_39"] / df_30["elderly_65"]
# ------------------------------------------------------------
# 5. 소멸위험 단계 판정 함수
# ------------------------------------------------------------
def risk_level(index):
if index >= 1.5:
return "소멸위험 매우 낮음"
elif index >= 1.0:
return "소멸위험 보통"
elif index >= 0.5:
return "주의 단계"
elif index >= 0.2:
return "소멸위험 진입"
else:
return "소멸고위험"
df_30["risk_level"] = df_30["extinction_risk_index"].apply(risk_level)
print("최근 30년 분석 데이터")
print(df_30.tail())
# ------------------------------------------------------------
# 6. 10년 후 추세선 예측
# ------------------------------------------------------------
future_years = np.arange(df_30["year"].min(), current_year + 11)
future_X = pd.DataFrame({"year": future_years})
models = {}
predictions = {}
for col in ["total_population", "women_20_39", "elderly_65", "extinction_risk_index"]:
X = df_30[["year"]]
y = df_30[col]
model = LinearRegression()
model.fit(X, y)
models[col] = model
predictions[col] = model.predict(future_X)
future_index = predictions["extinction_risk_index"][-1]
future_total = predictions["total_population"][-1]
future_level = risk_level(future_index)
# ------------------------------------------------------------
# 7. 핵심 인사이트 출력
# ------------------------------------------------------------
first_pop = df_30.iloc[0]["total_population"]
last_pop = df_30.iloc[-1]["total_population"]
pop_change = (last_pop - first_pop) / first_pop * 100
first_index = df_30.iloc[0]["extinction_risk_index"]
last_index = df_30.iloc[-1]["extinction_risk_index"]
index_change = (last_index - first_index) / first_index * 100
print("\n[핵심 인사이트]")
print(f"분석 지역: {TARGET_CITY}")
print(f"지난 30년 총인구 변화율: {pop_change:.2f}%")
print(f"지난 30년 소멸위험지수 변화율: {index_change:.2f}%")
print(f"현재 소멸위험지수: {last_index:.3f} / 단계: {risk_level(last_index)}")
print(f"10년 후 예측 총인구: {future_total:,.0f}명")
print(f"10년 후 예측 소멸위험지수: {future_index:.3f} / 단계: {future_level}")
if future_index < 0.2:
print("판단: 10년 후 소멸고위험 단계가 예상되어 강력한 인구·일자리·생활SOC 정책이 필요합니다.")
elif future_index < 0.5:
print("판단: 소멸위험 진입 단계가 지속될 가능성이 있어 청년 정착 정책이 시급합니다.")
else:
print("판단: 아직 회복 가능성이 있으므로 선제 정책으로 위험을 낮출 수 있습니다.")
# ------------------------------------------------------------
# 8. 시각화
# ------------------------------------------------------------
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
plt.figure(figsize=(12, 7))
plt.plot(df_30["year"], df_30["total_population"], marker="o", label="실제 총인구")
plt.plot(future_years, predictions["total_population"], linestyle="--", label="총인구 추세선 및 10년 예측")
plt.axvline(current_year, linestyle=":", label="현재 기준 연도")
plt.title(f"{TARGET_CITY} 총인구 30년 추세와 10년 전망")
plt.xlabel("연도")
plt.ylabel("총인구")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig("population_30years_10future.png", dpi=200)
plt.show()
plt.figure(figsize=(12, 7))
plt.plot(df_30["year"], df_30["extinction_risk_index"], marker="o", label="실제 소멸위험지수")
plt.plot(future_years, predictions["extinction_risk_index"], linestyle="--", label="소멸위험지수 추세선 및 10년 예측")
plt.axhline(0.5, linestyle=":", label="소멸위험 진입 기준 0.5")
plt.axhline(0.2, linestyle=":", label="소멸고위험 기준 0.2")
plt.axvline(current_year, linestyle=":", label="현재 기준 연도")
plt.title(f"{TARGET_CITY} 지방소멸위험지수 30년 추세와 10년 전망")
plt.xlabel("연도")
plt.ylabel("소멸위험지수")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig("extinction_risk_index_trend.png", dpi=200)
plt.show()
# ------------------------------------------------------------
# 9. 정책 개발안
# ------------------------------------------------------------
policies = [
"청년 정착 패키지: 주거비, 지역 일자리, 창업 공간, 교통비를 묶어 지원",
"생활인구 확대: 워케이션, 체류형 관광, 관계인구 등록제를 통한 정기 방문 유도",
"교육 지속가능성: 작은 학교 특성화, 온라인 공동교육과정, 지역 대학 연계 강화",
"돌봄·의료 접근성 개선: 고령층 방문진료, 응급교통, 스마트 건강관리 확대",
"지역 산업 전환: 농생명, 재생에너지, 기후테크, 로컬푸드 산업 육성",
"공공데이터 대시보드: 인구, 일자리, 빈집, 교통, 의료 지표를 매년 공개"
]
print("\n[정책 개발안]")
for i, p in enumerate(policies, start=1):
print(f"{i}. {p}")
월세 지원, 지역기업 채용 연계, 창업 실험 공간, 교통비 지원을 하나로 묶어 청년이 실제로 살아볼 수 있는 조건을 만듭니다.
워케이션 센터, 체류형 관광, 귀촌 체험, 관계인구 멤버십을 운영해 정주인구 감소를 보완합니다.
지역 특화 교육과정, 온라인 공동교육과정, 대학·기업 멘토링을 통해 학생 수 감소에도 교육 품질을 유지합니다.
방문 진료, 응급 이동지원, 스마트 돌봄, 마을 단위 건강관리로 고령 인구가 안전하게 살 수 있는 도시를 만듭니다.
농생명, 재생에너지, 기후테크, 로컬푸드, 지역문화 콘텐츠를 연계하여 청년 일자리를 만듭니다.
인구, 출생, 이동, 빈집, 일자리, 의료, 교통 지표를 매년 공개하고 정책 효과를 데이터로 평가합니다.