The SIR model without vital dynamics: The dynamics of an epidemic, for example the flu, are often much faster than the dynamics of birth and death, therefore, birth and death are often omitted in simple compartmental models. The SIR system without so-called vital dynamics (birth and death, sometimes called demography) described above can be expressed by the following set of ordinary differential equations:
At time \(t\) the SIR model is characterised by \(S(t)\) for the number of susceptible in the population, \(I(t)\) for the number of infected, and \(R(t)\) for the number of recovered or deceased (or immune) individuals out of the population \(N = S(t)+I(t)+R(t)\).
People move through these stages as follows:
Code
import attrimport pandas as pdimport numpy as npfrom scipy.integrate import solve_ivpimport plotly.express as px@attr.sclass SIR(object): N = attr.ib(converter=int) I = attr.ib(converter=float) beta = attr.ib(converter=float) gamma = attr.ib(converter=float) days = attr.ib(converter=int, default=200) S = attr.ib(init=False, converter=float) R = attr.ib(init=False, converter=float, default=0.0)def __attrs_post_init__(self):self.S =self.N -self.I -self.Rself.beta =round(self.beta, 2)self.gamma =round(self.gamma, 2)@staticmethoddef ode(t, y, beta, gamma, N): S, I, R = y new_cases = beta*S*I/N removed_cases = gamma*I S =-new_cases I = new_cases - removed_cases R = removed_cases y = (S,I,R)return ydef solve(self): y = (self.S, self.I, self.R) days =self.days +1 sol = solve_ivp(SIR.ode, [0, days], y,t_eval=np.arange(0, days), args=(self.beta, self.gamma, self.N)) df = pd.DataFrame(sol.y.T, index=pd.Index(sol.t,name="Time"), columns=["Susceptible","Infected","Removed"]).reset_index() df["beta"] =self.beta df["gamma"] =self.gamma df = df.melt(id_vars=["Time", "beta", "gamma"], value_vars=["Susceptible", "Infected", "Removed"], value_name="People", var_name="Category")return (df, sol)def plot(self): df, sol =self.solve() fig = px.line(df, x=df.Time, y=df.People, color=df.Category, title="test",# title=dict(# text=self.__repr__(),# y=0.98,# x=0.5,# xanchor="center",# yanchor="top") ) fig.for_each_trace(lambda t: t.update(hovertemplate ="%{y:.0f}")) fig.update_layout(hovermode="x unified", margin=dict(l=20, r=20, t=30, b=20), legend=dict(orientation="h", title=""))return figm = SIR(5000,6,0.3,0.1)m.plot().show()