import numpy as np
import pandas as pd
import datetime
import math
import random
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from scipy.integrate import odeint
from geopy.geocoders import Nominatim
import folium
from folium.plugins import HeatMapWithTime
import os
import webbrowser
import time
# 폰트 설정 (한글, 마이너스 부호)
plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False
def plot_real_data_graph(df_covid):
df_covid = pd.read_csv('./코로나_확진자_수_통계.csv', encoding='utf-8')
df_covid.columns = df_covid.columns.str.strip()
df = df_covid
x = df['연도'].astype(str).to_numpy()
y = df['확진자'].to_numpy()
plt.figure(figsize=(10, 5))
plt.plot(x, y, marker='o', linestyle='-', color = 'red', label = '코로나 확진률(누적)')
plt.xlabel('연도')
plt.ylabel('확진자')
plt.title('현실 데이터 코로나 확진자 수')
plt.legend()
plt.grid()
plt.show()
# SIRV 모델
def sirv_model_unique(y, t, N, beta_func, gamma, reinfection_rate, vaccination_schedule):
U, S, I, R, C = y
beta = beta_func(t)
vac_effect = sum(rate for start_day, rate in vaccination_schedule if t >= start_day)
new_infections = beta * S * I / N
first_infections = new_infections * (U / S if S > 0 else 0)
dUdt = -first_infections
dSdt = -new_infections + reinfection_rate * R - vac_effect * S
dIdt = new_infections - gamma * I
dRdt = gamma * I - reinfection_rate * R + vac_effect * S
dCdt = first_infections
return dUdt, dSdt, dIdt, dRdt, dCdt
# R0 시간별 변동
def beta_time_dependent(t):
gamma = 1/14
if t <= 365:
R0 = 2.5
elif t <= 600:
R0 = 1.2
elif t <= 800:
R0 = 4.0
elif t <= 1000:
R0 = 1.5
elif t <= 1200:
R0 = 5.0
else:
R0 = 1.0
return R0 * gamma
# 시뮬레이션
def run_sirv_simulation():
N = 50_000_000
I0, R0, C0 = 10, 0, 10
S0 = N - I0 - R0
U0 = N - I0
gamma = 1/14
reinfection_rate = 1/180
vaccination_schedule = [(500, 0.002), (900, 0.003)]
start_date = pd.to_datetime('2020-01-01')
end_date = pd.to_datetime('2024-06-30')
total_days = (end_date - start_date).days
t = np.linspace(0, total_days, total_days + 1)
y0 = U0, S0, I0, R0, C0
ret = odeint(sirv_model_unique, y0, t,
args=(N, beta_time_dependent, gamma, reinfection_rate, vaccination_schedule))
U, S, I, R, C = ret.T
dates = start_date + pd.to_timedelta(t, unit='D')
return dates, U, S, I, R, C, C, N
# 🔵 감염 그래프 출력
def plot_sirv_graph(dates, U, S, I, R, C, N):
U_pct = (U / N) * 100
R_pct = (R / N) * 100
I_pct = (I / N) * 100
C_pct = (C / N) * 100
plt.figure(figsize=(14, 7))
plt.plot(np.array(dates), np.array(I_pct), 'r', linewidth=2, label='현재 감염자 (I%)')
plt.plot(np.array(dates), np.array(R_pct), 'g', linewidth=2, label='회복자 (R%)')
plt.plot(np.array(dates), np.array(C_pct), 'm--', linewidth=2, label='감염 경험자 (C%)')
plt.plot(np.array(dates), np.array(U_pct), 'c--', linewidth=2, label='아직 안 걸린 사람 (U%)')
plt.xlabel('날짜')
plt.ylabel('인구 대비 비율 (%)')
plt.title('현실 반영 코로나 SIRV 시뮬레이션 결과')
plt.legend()
plt.grid()
plt.gca().xaxis.set_major_locator(mdates.MonthLocator(bymonth=[1, 6]))
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
def plot_sirv_graph_overlay(dates, U, S, I, R, C, N, df_covid):
U_pct = (U / N) * 100
R_pct = (R / N) * 100
I_pct = (I / N) * 100
C_pct = (C / N) * 100
covid_dates = pd.to_datetime(df_covid['연도'].astype(str) + '-01-01')
covid_confirmed = df_covid['확진자'].to_numpy()
plt.figure(figsize=(14, 7))
ax1 = plt.gca()
ax1.plot(np.array(dates), np.array(I_pct), 'r', linewidth=2, label='시뮬레이션 현재 감염자 (I%)')
ax2 = ax1.twinx()
ax2.plot(np.array(covid_dates), np.array(covid_confirmed), 'b--o', label='현실 누적 확진자')
ax1.set_xlabel('날짜 / 연도')
ax1.set_ylabel('인구 대비 감염자 비율 (%)')
ax2.set_ylabel('확진자 수')
ax1.xaxis.set_major_locator(mdates.YearLocator())
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.setp(ax1.get_xticklabels(), rotation=45, ha='right')
ax1.legend(loc='upper left')
ax2.legend(loc='upper right')
plt.title('시뮬레이션 감염자와 현실 누적 확진자 비교')
plt.grid(True)
plt.tight_layout()
plt.show()
# 🗺️ 지도 확산 시각화
def create_heatmap_simulation(dates, I_vals, C_vals, country_name="South Korea"):
geolocator = Nominatim(user_agent="covid_sim")
location = geolocator.geocode(country_name)
if location is None:
raise ValueError(f"{country_name} 좌표를 찾을 수 없습니다.")
center_lat, center_lon = location.latitude, location.longitude
step = 7
heat_data = []
time_index = []
for idx in range(0, len(dates), step):
current_I = I_vals[idx]
current_C = C_vals[idx]
date = dates[idx]
num_points = max(int(current_I / 20000), 5)
spread_radius = max(10, math.sqrt(current_C) / 50)
points = [
random_point_in_circle(center_lat, center_lon, spread_radius)
for _ in range(num_points)
]
heat_data.append(points)
time_index.append(date.strftime("%Y-%m-%d"))
m = folium.Map(location=[center_lat, center_lon], zoom_start=5, tiles='cartodbpositron')
HeatMapWithTime(
data=heat_data,
index=time_index,
auto_play=True,
max_opacity=0.8,
radius=10,
gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 0.8: 'red'}
).add_to(m)
output_file = "covid_heatmap_simulation.html"
m.save(output_file)
file_path = os.path.abspath(output_file)
print(f"Heatmap 시뮬레이션 저장 완료: {file_path}")
webbrowser.open(f"file://{file_path}")
# 🔁 원형 반경 내 랜덤 포인트
def random_point_in_circle(lat_center, lon_center, radius_km):
r = radius_km * math.sqrt(random.random())
theta = random.uniform(0, 2 * math.pi)
delta_lat = r * math.cos(theta) / 111
delta_lon = r * math.sin(theta) / (111 * math.cos(math.radians(lat_center)))
lat = lat_center + delta_lat
lon = lon_center + delta_lon
return [lat, lon]
# ▶️ 메인 실행
if __name__ == "__main__":
df_covid = pd.read_csv('./코로나_확진자_수_통계.csv', encoding='utf-8')
df_covid.columns = df_covid.columns.str.strip()
dates, U, S, I, R, C, _, N = run_sirv_simulation()
create_heatmap_simulation(dates, I, C, country_name="South Korea")
time.sleep(5)
plot_sirv_graph(dates, U, S, I, R, C, N)
plot_real_data_graph(df_covid)
plot_sirv_graph_overlay(dates, U, S, I, R, C, N, df_covid)



