SEIR_Prediction / app.py
Dexwel25's picture
Update app.py
55b1c44 verified
Raw
History Blame Contribute Delete
12.1 kB
import numpy as np
import pandas as pd
import gradio as gr
import plotly.graph_objects as go
from scipy.integrate import odeint
PRESETS = {
"Malaria (Nigeria)": {"beta": 0.30, "gamma": 0.10, "sigma": 0.14, "model": "SEIR"},
"Malaria (Ghana)": {"beta": 0.25, "gamma": 0.10, "sigma": 0.14, "model": "SEIR"},
"Dengue Fever": {"beta": 0.40, "gamma": 0.14, "sigma": 0.20, "model": "SEIR"},
"Cholera": {"beta": 0.50, "gamma": 0.25, "sigma": 0.50, "model": "SEIR"},
"Lassa Fever": {"beta": 0.20, "gamma": 0.18, "sigma": 0.17, "model": "SEIR"},
"Measles": {"beta": 1.40, "gamma": 0.14, "sigma": 0.20, "model": "SEIR"},
"Mpox": {"beta": 0.18, "gamma": 0.10, "sigma": 0.13, "model": "SEIR"},
}
DISEASE_INFO = {
"Malaria (Nigeria)": "R0 approx 3.0 - Nigeria has the world's highest malaria burden. Peaks in rainy season.",
"Malaria (Ghana)": "R0 approx 2.5 - High seasonal transmission in northern and coastal regions.",
"Dengue Fever": "R0 approx 2.9 - Re-emerging in West Africa via Aedes aegypti mosquitoes.",
"Cholera": "R0 approx 2.0 - Frequent outbreaks during floods and poor-sanitation events.",
"Lassa Fever": "R0 approx 1.1 - Endemic in Nigeria; spread via rodent contact.",
"Measles": "R0 approx 10-18 - Highly contagious; leading cause of child mortality in West Africa.",
"Mpox": "R0 approx 1.8 - Endemic in parts of Nigeria; human-to-human spread increasing.",
}
POPULATIONS = {
"Nigeria - National (220M)": 220000000,
"Nigeria - Lagos (15M)": 15000000,
"Nigeria - Kano (13M)": 13000000,
"Nigeria - Rivers (7M)": 7000000,
"Ghana - National (33M)": 33000000,
"Ghana - Greater Accra (5M)": 5000000,
"Ghana - Ashanti (5M)": 5000000,
}
def sir_ode(y, t, beta, gamma, N):
S, I, R = y
n = beta * S * I / N
return [-n, n - gamma * I, gamma * I]
def seir_ode(y, t, beta, gamma, sigma, N):
S, E, I, R = y
n = beta * S * I / N
return [-n, n - sigma * E, sigma * E - gamma * I, gamma * I]
def fig_to_html(fig):
return fig.to_html(include_plotlyjs="cdn", full_html=False,
config={"displayModeBar": False})
def _card(label, value, sub):
return (
"<div style='background:white;border-radius:10px;padding:14px 16px;"
"border:1px solid #e2e8f0;box-shadow:0 1px 3px rgba(0,0,0,0.06);'>"
f"<div style='font-size:11px;color:#64748b;font-weight:600;text-transform:uppercase;'>{label}</div>"
f"<div style='font-size:24px;font-weight:800;color:#0f172a;margin-top:4px;'>{value}</div>"
f"<div style='font-size:12px;color:#94a3b8;margin-top:2px;'>{sub}</div>"
"</div>"
)
def run_simulation(disease, population_label, model_type,
beta, gamma, sigma, days,
i0_pct, vax_pct,
add_intervention, interv_day, interv_reduction):
N = POPULATIONS[population_label]
t = np.linspace(0, int(days), int(days) * 4)
I0 = max(1, int(N * i0_pct / 100))
R_init = int(N * vax_pct / 100)
S0 = max(0, N - I0 - R_init)
def solve(b, t_arr, y0):
if model_type == "SIR":
return odeint(sir_ode, y0[:3], t_arr, args=(b, gamma, N))
return odeint(seir_ode, y0, t_arr, args=(b, gamma, sigma, N))
y0 = [S0, I0, R_init] if model_type == "SIR" else [S0, 0, I0, R_init]
sol = solve(beta, t, y0)
if model_type == "SIR":
S, I, R = sol[:, 0], sol[:, 1], sol[:, 2]
E = None
else:
S, E, I, R = sol[:, 0], sol[:, 1], sol[:, 2], sol[:, 3]
I_int = None
if add_intervention and int(interv_day) < int(days):
t1 = t[t <= interv_day]
t2 = t[t > interv_day] - interv_day
beta2 = beta * (1 - interv_reduction / 100)
sol1 = solve(beta, t1, y0)
sol2 = solve(beta2, t2, sol1[-1])
full = np.concatenate([sol1, sol2], axis=0)
I_int = full[:, 1] if model_type == "SIR" else full[:, 2]
R0 = beta / gamma
peak_I = int(I.max())
peak_day = int(t[np.argmax(I)])
total_inf = int(R[-1] - R_init)
herd = max(0.0, (1 - 1 / R0) * 100) if R0 > 1 else 0.0
inf_p = round(1 / gamma, 1)
inc_p = round(1 / sigma, 1) if model_type == "SEIR" else "N/A"
epidemic = "Epidemic will grow" if R0 > 1 else "Epidemic will die out"
cards = "".join([
_card("Basic R0", f"{R0:.2f}", epidemic),
_card("Peak Infectious", f"{peak_I:,}", f"on day {peak_day}"),
_card("Total Infected", f"{total_inf:,}", f"{total_inf/N*100:.1f}% of population"),
_card("Herd Immunity", f"{herd:.1f}%", "vaccination needed"),
_card("Infectious Period",f"{inf_p}d", f"Incubation: {inc_p}{'d' if inc_p != 'N/A' else ''}"),
])
metrics_html = (
"<div style='display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));"
f"gap:12px;margin-bottom:12px;'>{cards}</div>"
"<div style='background:#eff6ff;border-left:4px solid #3b82f6;border-radius:0 8px 8px 0;"
f"padding:10px 14px;font-size:13px;color:#1e40af;'>{DISEASE_INFO[disease]}</div>"
)
COLORS = {"S": "#94a3b8", "E": "#f59e0b", "I": "#ef4444", "R": "#22c55e"}
fig1 = go.Figure()
curves = [("Susceptible", S, COLORS["S"]),
("Infectious", I, COLORS["I"]),
("Recovered", R, COLORS["R"])]
if E is not None:
curves.insert(1, ("Exposed", E, COLORS["E"]))
for name, arr, color in curves:
fig1.add_trace(go.Scatter(
x=t, y=arr / N * 100, name=name,
line=dict(color=color, width=2.5),
hovertemplate=f"<b>{name}</b><br>Day %{{x:.0f}}<br>%{{y:.2f}}%<extra></extra>",
))
if I_int is not None:
fig1.add_trace(go.Scatter(
x=t, y=I_int / N * 100,
name="Infectious (+ intervention)",
line=dict(color="#7c3aed", width=2, dash="dash"),
))
fig1.add_vline(x=interv_day, line_dash="dot", line_color="#7c3aed",
annotation_text=f"Intervention day {int(interv_day)}",
annotation_position="top right", annotation_font_size=11)
fig1.add_vline(x=peak_day, line_dash="dot", line_color="#ef4444",
annotation_text=f"Peak day {peak_day}",
annotation_position="top left", annotation_font_size=11)
fig1.update_layout(
title=f"{model_type} Epidemic Curve | {disease} | {population_label}",
xaxis_title="Days", yaxis_title="% of Population",
height=430, plot_bgcolor="white", paper_bgcolor="white",
hovermode="x unified", font=dict(size=13),
legend=dict(orientation="h", y=-0.25, x=0),
margin=dict(l=10, r=10, t=50, b=90),
)
fig1.update_xaxes(showgrid=True, gridcolor="#f1f5f9", linecolor="#e2e8f0")
fig1.update_yaxes(showgrid=True, gridcolor="#f1f5f9", linecolor="#e2e8f0", range=[0, 105])
beta_range = np.round(np.arange(0.05, 1.55, 0.05), 2)
peaks = []
for b in beta_range:
s = solve(b, t, y0)
col = 1 if model_type == "SIR" else 2
peaks.append(s[:, col].max() / N * 100)
r0_range = beta_range / gamma
bar_colors = ["#ef4444" if r > 1 else "#22c55e" for r in r0_range]
fig2 = go.Figure(go.Bar(
x=[f"{r:.1f}" for r in r0_range], y=peaks,
marker_color=bar_colors,
hovertemplate="R0=%{x}<br>Peak infected: %{y:.1f}%<extra></extra>",
))
closest_idx = int(np.argmin(np.abs(r0_range - R0)))
fig2.add_vline(x=f"{r0_range[closest_idx]:.1f}",
line_dash="dash", line_color="#0f172a", line_width=2,
annotation_text=f"Current R0 = {R0:.2f}",
annotation_font_size=11, annotation_position="top right")
fig2.update_layout(
title="Peak Infected (%) across R0 values | Red=epidemic grows Green=dies out",
xaxis_title="R0 (beta / gamma)", yaxis_title="Peak Infected (%)",
height=320, plot_bgcolor="white", paper_bgcolor="white",
showlegend=False, font=dict(size=12),
margin=dict(l=10, r=10, t=50, b=50),
)
fig2.update_xaxes(tickangle=-45, tickfont=dict(size=9),
showgrid=True, gridcolor="#f1f5f9")
fig2.update_yaxes(showgrid=True, gridcolor="#f1f5f9")
step = max(1, len(t) // 100)
rowdict = {
"Day": t[::step].astype(int),
"Susceptible %": (S[::step] / N * 100).round(2),
"Infectious %": (I[::step] / N * 100).round(2),
"Recovered %": (R[::step] / N * 100).round(2),
}
if E is not None:
rowdict["Exposed %"] = (E[::step] / N * 100).round(2)
df_out = pd.DataFrame(rowdict)
return metrics_html, fig_to_html(fig1), fig_to_html(fig2), df_out
def load_preset(disease):
p = PRESETS[disease]
return p["beta"], p["gamma"], p["sigma"], p["model"]
with gr.Blocks(title="Disease Modelling - Nigeria & Ghana", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"# Infectious Disease Modelling - Nigeria & Ghana\n"
"SIR / SEIR compartmental model. Adjust parameters then click **Run Simulation**."
)
with gr.Row():
with gr.Column(scale=1, min_width=270):
gr.Markdown("### Settings")
disease_dd = gr.Dropdown(list(PRESETS.keys()),
value="Malaria (Nigeria)", label="Disease Preset")
pop_dd = gr.Dropdown(list(POPULATIONS.keys()),
value="Nigeria - National (220M)", label="Population / Location")
model_dd = gr.Radio(["SIR", "SEIR"], value="SEIR", label="Model type",
info="SEIR adds an Exposed (latent) compartment")
gr.Markdown("### Parameters")
beta_sl = gr.Slider(0.01, 2.00, value=0.30, step=0.01, label="Beta - transmission rate")
gamma_sl = gr.Slider(0.01, 1.00, value=0.10, step=0.01, label="Gamma - recovery rate (1/gamma = infectious days)")
sigma_sl = gr.Slider(0.01, 1.00, value=0.14, step=0.01, label="Sigma - incubation rate (SEIR only)")
gr.Markdown("### Simulation")
days_sl = gr.Slider(30, 730, value=180, step=10, label="Days to simulate")
i0_sl = gr.Slider(0.001, 2.0, value=0.01, step=0.001, label="Initial infected (%)")
vax_sl = gr.Slider(0, 80, value=0, step=1, label="Pre-existing immunity / vaccinated (%)")
gr.Markdown("### Intervention")
interv_cb = gr.Checkbox(value=False, label="Add intervention (bed nets, treatment, quarantine)")
interv_day = gr.Slider(1, 180, value=30, step=1, label="Intervention start day")
interv_red = gr.Slider(10, 90, value=50, step=5, label="Transmission reduction (%)")
run_btn = gr.Button("Run Simulation", variant="primary", size="lg")
with gr.Column(scale=2):
metrics_out = gr.HTML(label="Key Metrics")
curve_out = gr.HTML(label="Epidemic Curve")
sens_out = gr.HTML(label="R0 Sensitivity")
table_out = gr.Dataframe(label="Simulation Data (% of population)", interactive=False)
inputs = [disease_dd, pop_dd, model_dd,
beta_sl, gamma_sl, sigma_sl, days_sl,
i0_sl, vax_sl,
interv_cb, interv_day, interv_red]
outputs = [metrics_out, curve_out, sens_out, table_out]
disease_dd.change(fn=load_preset, inputs=[disease_dd],
outputs=[beta_sl, gamma_sl, sigma_sl, model_dd])
run_btn.click(fn=run_simulation, inputs=inputs, outputs=outputs)
demo.load(fn=run_simulation, inputs=inputs, outputs=outputs)
gr.Markdown(
"---\n"
"S = Susceptible | E = Exposed | I = Infectious | R = Recovered\n\n"
"R0 = beta / gamma. R0 > 1 means epidemic grows. R0 < 1 means it dies out.\n\n"
"Sources: WHO AFRO - NCDC Nigeria - Ghana Health Service"
)
demo.launch()