什么是SEIR传播动力学模型?
经典SEIR模型将人群分为S (Susceptible,易感者),I (Infected,感染者),E (Exposed,潜伏者)和R (Recovered,康复人群)。
本文根据SEIR传播动力学模型,利用Python实现生成ER随机网格、并在网格间随机结边、模拟SEIR型传染病传播
该实验可分为两个步骤进行:
一、生成一个ER随机网络,生成网络节点的度分布图、画出节点分布图
二、编写程序:编程SEIR传播动力学模型。随机选择一个传播节点,基于SEIR传播动力学模型,统计每个时间步长对应的S,E,I,R四个状态节点数,作图
*注意:为防止概率传播误差,宜进行100次独立实验,取平均值
关键词解析:
• 网络是由节点和边组成。节点代表个体,边代表节点之间的关系,比如两个节点是朋友,那么他们之间有连边,网络可以用一个邻接矩阵表示,矩阵中的0表示没有接触,即没有连边,1表示有接触,即存在连边。下面是一个简单的例子:
• 度(degree): 邻居节点的个数,如节点a的度为4,对应于邻接矩阵中a行或列1的个数。
生成ER随机网络—— 利用 numpy 函数
初始: 网络总结点数 N, 没有边;
网络构建过程:每对节点以概率 p 被选择,进行连边,不允许重复连边。代码如下:
def create_ER(N, p): #利用numpy生成N*N[0,1]的随机二维数组,再用np(where)条件赋值
ERmap = np.random.rand(N, N)
ERmap = np.where(ERmap>p, 0, 1)
for i in range(N):
ERmap[i, i] = 0
ERmap[i, :] = ERmap[:, i] #ER邻边连接随机网格,应具有对称性、对角线为0
return ERmap
这里继续将ER随机网格打印为文件,代码如下:
def save_ER_to_file(ERmap): #将ER邻边连接随机网格写入txt文档
file = open('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/ER随机网格.txt', 'w+')
for i in range(len(ERmap)): #打开文档,若无将自动创建
a = ERmap[i]
for j in range(len(ERmap)):
s = str(a[j]) #先取第一行,再取各列
file.write(s)
file.write(' ')
file.write('\n')
file.close()
接着将ER随机网格转为ER随机分布图,代码如下:
def showGraph(ERmap): #将生成的ER随机网格连接生成分布图
G = nx.Graph()
for i in range(len(ERmap)):
for j in range(len(ERmap)):
if ERmap[i][j] == 1:
G.add_edge(i, j) #将值为1的点相连接
nx.draw(G)
plt.savefig('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/ER网格图.png')
plt.show() #保存图片、显示图片
现在已经成功生成了ER随机分布图,我们接下来完成度分布概率图(并顺手把概率分布存入txt文件):
def calculateDegreeDistribution(ERmap):
avedegree = 0.0 #计算度分布
identify = 0.0 #若算法正确,度概率分布总和应为0
p_degree = np.zeros((len(ERmap)), dtype=float)
#statistic下标为度值
#(1)先计数该度值的量
#(2)再除以总节点数N得到比例
degree = np.zeros(len(ERmap), dtype=int)
#degree用于存放各个节点的度之和
for i in range(len(ERmap)):
for j in range(len(ERmap)):
degree[i] = degree[i] + ERmap[i][j]
#汇总各个节点的度之和
for i in range(len(ERmap)):
avedegree += degree[i]
#汇总每个节点的度之和
print('该模型平均度为\t' + str(avedegree /len(ERmap)))
# 计算平均度
for i in range(len(ERmap)):
p_degree[degree[i]] = p_degree[degree[i]] + 1
#先计数该度值的量
for i in range(len(ERmap)): #再除以总节点数N得到比例
p_degree[i] = p_degree[i] / len(ERmap)
identify = identify + p_degree[i]
#将所有比例相加,应为1
identify = int(identify)
plt.figure(figsize=(10, 4), dpi=120)
#绘制度分布图
plt.xlabel('$Degree$', fontsize=21)
#横坐标标注——Degrees
plt.ylabel('$P$', fontsize=26)
#纵坐标标注——P
plt.plot(list(range(len(ERmap))),list(p_degree),'-*',markersize=15,label='度',color = "#ff9c00")
#自变量为list(range(N)),因变量为list(p_degree)
#图形标注选用星星*与线条-,大小为15,标注图例为度,颜色是水果橘
plt.xlim([0, 12]) #给x轴设限制值,这里是我为了美观加入的,也可以不写
plt.ylim([-0.05, 0.5]) #给y轴设限制值,也是为了美观加入的,不在意可以跳过
plt.xticks(fontsize=20) #设置x轴的字体大小为21
plt.yticks(fontsize=20) #设置y轴的字体大小为21
plt.legend(fontsize=21, numpoints=1, fancybox=True, prop={'family': 'SimHei', 'size': 15}, ncol=1)
plt.savefig('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/度分布图.pdf')
plt.show() #展示图片
print('算法正常运行则概率之和应为1 当前概率之和=\t' + str(identify))
#用于测试算法是否正确
f = open('度分布.txt', 'w+')
#将度分布写入文件名为201838000553_张浩然_度分布.txt中
#若磁盘中无此文件将自动新建
for i in range(len(ERmap)):
f.write(str(i)) #先打印度值、再打印度的比例
f.write(' ')
s = str(p_degree[i]) #p_degree[i]为float格式,进行转化才能书写
f.write(s) #再打印度的比例
f.write('\n') #换行
f.close()
SEIR传播模型。根据上面生成的网络,从网络中随机选择一个节点作为传播者(即感染I状态),t=0。如果一个 S 状态节点 与一个 I 状态节点接触,那么下一时间步(t +1)这个S状态的节点被感染的概率为a,进入潜伏(E)状态。每个E状态节点,下一时间步以概率b变为I状态,以概率 c变为R状态。每个I状态节点下一时间步以概率d变为R状态。
因此,在t时刻,如果一个S状态节点有 m 个I状态邻居, 那么下个时间步 t + 1,该节点被感染为E状态的概率为 1−(1−a)^m。
SEIR传播及绘图代码如下:
def spread(ERmap, S_to_E, E_to_I, to_R, degree):
post_degree = np.array(degree)
for i in range(degree.size):
if degree[i] == 1: #若节点状态为1,即"易感者"
lines = 0 #计算节点附近的邻边数
for j in range(degree.size):
if ERmap[i, j] == 1 and degree[j] == 3:
lines = lines + 1
oops = 1 - (1 - S_to_E) ** lines
p = random.random() #当有n条邻边时,被感染概率为1-(1-w)^n
if p < oops:
post_degree[i] = 2 #被感染,更新状态为E
elif degree[i]==2: #若节点状态为2,转为I概率为E_to_I
p = random.random()
if p< E_to_I:
post_degree[i]=3 #若转换为I,更新状态为I
elif degree[i] == 3: #若节点状态为3,转为R概率为I_to_R
p = random.random()
if p < to_R:
post_degree[i] = 4
return post_degree #导出传播后节点状态
def epedemic_Simulation(N,p,S_to_E, E_to_I, to_R, t):
ERmap = create_ER(N, p) #生成ER网格
save_ER_to_file(ERmap) #存储ER网格
calculateDegreeDistribution(ERmap)
#计算度分布、存储度分布概率结果,并显示度分布图
showGraph(ERmap) #显示随机ER网格图
#重复实验次数 Rp 为 100
Rp = 100 #概率传播有一定误差,这里重复实验100次,若电脑运行速度更快可以提高
# 建立四个数组,准备存放不同t时间的节点数
S = np.array([1 for i in range(t)])
E = np.array([1 for i in range(t)])
I = np.array([1 for i in range(t)])
R = np.array([1 for i in range(t)])
for a in range(Rp): #重复实验次数Rp次,利用for套内循环
degrees = np.array([1 for i in range(N)])
#生成N个节点数,默认为1,即"易感者"
iNum = random.randint(0, N - 1)
degrees[iNum] = 3 #随机抽取一位"发病者"
Ss = []
Ee = []
Ii = []
Rr = []
for i in range(t):
if i !=0:
degrees = spread(ERmap,S_to_E,E_to_I, to_R, degrees)
Ss.append(np.where(np.array(degrees) == 1, 1, 0).sum())
Ee.append(np.where(np.array(degrees) == 2 , 1, 0).sum())
Ii.append(np.where(np.array(degrees) == 3, 1, 0).sum())
Rr.append(np.where(np.array(degrees) == 4, 1, 0).sum())
else:
Ss.append(np.where(np.array(degrees) == 1, 1, 0).sum())
Ee.append(np.where(np.array(degrees) == 2, 1, 0).sum())
Ii.append(np.where(np.array(degrees) == 3, 1, 0).sum())
Rr.append(np.where(np.array(degrees) == 4, 1, 0).sum())
S = S + Ss #将每次重复实验数据求和
E = E + Ee
I = I + Ii
R = R + Rr
print(S)
for i in range(t):
S[i] /= Rp #求Rp次实验后的平均值
E[i] /= Rp
I[i] /= Rp
R[i] /= Rp
print(S) #检验实验是否正常
#plt作图
plt.plot(S ,color = 'darkblue',label = 'Susceptible',marker = '.')
plt.plot(E ,color = 'orange',label = 'Exposed',marker = '.')
plt.plot(I , color = 'red',label = 'Infection',marker = '.')
plt.plot(R , color = 'green',label = 'Recovery',marker = '.')
plt.title('SEIR Model')
plt.legend()
plt.xlabel('Day')
plt.ylabel('Number')
plt.rcParams['savefig.dpi'] = 300 # 图片像素
plt.rcParams['figure.dpi'] = 100 # 分辨率
plt.savefig('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/SEIR曲线图.png')
#存储图片
plt.show()
epedemic_Simulation(1000,0.006,0.2,0.5,0.2,100)
#人数为1000,邻边结边率为0.006,感染率0.2,发病率0.5,康复率0.2,100天实验期
SEIR效果图(1000单位,邻边结边率为0.006,感染率0.2,发病率0.5,康复率0.2,100天实验期):
由本次实验结果图可知,病毒传播会在31天(一月左右达到高峰期),之后疫情会在50天左右彻底结束。
全部完整代码如下:
import numpy as np #调用numpy多维数组包——用来建立ER网格
import matplotlib.pyplot as plt #调用matplotlib画图包——生成SEIR模型图
import random #调用random随机包——随机感染
import networkx as nx #调用networkx图形包——绘制ER随机分布图
def create_ER(N, p): #利用numpy生成N*N[0,1]的随机二维数组,再用np(where)条件赋值
ERmap = np.random.rand(N, N)
ERmap = np.where(ERmap>p, 0, 1)
for i in range(N):
ERmap[i, i] = 0
ERmap[i, :] = ERmap[:, i] #ER邻边连接随机网格,应具有对称性、对角线为0
return ERmap
def save_ER_to_file(ERmap): #将ER邻边连接随机网格写入txt文档
file = open('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/ER随机网格.txt', 'w+')
for i in range(len(ERmap)): #打开文档,若无将自动创建
a = ERmap[i]
for j in range(len(ERmap)):
s = str(a[j]) #先取第一行,再取各列
file.write(s)
file.write(' ')
file.write('\n')
file.close()
def showGraph(ERmap): #将生成的ER随机网格连接生成分布图
G = nx.Graph()
for i in range(len(ERmap)):
for j in range(len(ERmap)):
if ERmap[i][j] == 1:
G.add_edge(i, j) #将值为1的点相连接
nx.draw(G)
plt.savefig('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/ER网格图.png')
plt.show() #保存图片、显示图片
def calculateDegreeDistribution(ERmap):
avedegree = 0.0 #计算度分布
identify = 0.0 #若算法正确,度概率分布总和应为0
p_degree = np.zeros((len(ERmap)), dtype=float)
#statistic下标为度值
#(1)先计数该度值的量
#(2)再除以总节点数N得到比例
degree = np.zeros(len(ERmap), dtype=int)
#degree用于存放各个节点的度之和
for i in range(len(ERmap)):
for j in range(len(ERmap)):
degree[i] = degree[i] + ERmap[i][j]
#汇总各个节点的度之和
for i in range(len(ERmap)):
avedegree += degree[i]
#汇总每个节点的度之和
print('该模型平均度为\t' + str(avedegree /len(ERmap)))
# 计算平均度
for i in range(len(ERmap)):
p_degree[degree[i]] = p_degree[degree[i]] + 1
#先计数该度值的量
for i in range(len(ERmap)): #再除以总节点数N得到比例
p_degree[i] = p_degree[i] / len(ERmap)
identify = identify + p_degree[i]
#将所有比例相加,应为1
identify = int(identify)
plt.figure(figsize=(10, 4), dpi=120)
#绘制度分布图
plt.xlabel('$Degree$', fontsize=21)
#横坐标标注——Degrees
plt.ylabel('$P$', fontsize=26)
#纵坐标标注——P
plt.plot(list(range(len(ERmap))),list(p_degree),'-*',markersize=15,label='度',color = "#ff9c00")
#自变量为list(range(N)),因变量为list(p_degree)
#图形标注选用星星*与线条-,大小为15,标注图例为度,颜色是水果橘
plt.xlim([0, 12]) #给x轴设限制值,这里是我为了美观加入的,也可以不写
plt.ylim([-0.05, 0.5]) #给y轴设限制值,也是为了美观加入的,不在意可以跳过
plt.xticks(fontsize=20) #设置x轴的字体大小为21
plt.yticks(fontsize=20) #设置y轴的字体大小为21
plt.legend(fontsize=21, numpoints=1, fancybox=True, prop={'family': 'SimHei', 'size': 15}, ncol=1)
plt.savefig('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/度分布图.pdf')
plt.show() #展示图片
print('算法正常运行则概率之和应为1 当前概率之和=\t' + str(identify))
#用于测试算法是否正确
f = open('度分布.txt', 'w+')
#将度分布写入文件名为201838000553_张浩然_度分布.txt中
#若磁盘中无此文件将自动新建
for i in range(len(ERmap)):
f.write(str(i)) #先打印度值、再打印度的比例
f.write(' ')
s = str(p_degree[i]) #p_degree[i]为float格式,进行转化才能书写
f.write(s) #再打印度的比例
f.write('\n') #换行
f.close()
# 这里正式模拟SEIR传播,本程序将通过每个节点的不同值来表示不同状态
# 1. S(Susceptible) 为 "易感者", # 2. E(Explosed) 为 "潜伏者",无感染力
# 3. I(Infected) 为 "发病者",有感染力 # 4. R(Recovered) 为 "康复者",无感染力,不会再被感染
def spread(ERmap, S_to_E, E_to_I, to_R, degree):
post_degree = np.array(degree)
for i in range(degree.size):
if degree[i] == 1: #若节点状态为1,即"易感者"
lines = 0 #计算节点附近的邻边数
for j in range(degree.size):
if ERmap[i, j] == 1 and degree[j] == 3:
lines = lines + 1
oops = 1 - (1 - S_to_E) ** lines
p = random.random() #当有n条邻边时,被感染概率为1-(1-w)^n
if p < oops:
post_degree[i] = 2 #被感染,更新状态为E
elif degree[i]==2: #若节点状态为2,转为I概率为E_to_I
p = random.random()
if p< E_to_I:
post_degree[i]=3 #若转换为I,更新状态为I
elif degree[i] == 3: #若节点状态为3,转为R概率为I_to_R
p = random.random()
if p < to_R:
post_degree[i] = 4
return post_degree #导出传播后节点状态
def epedemic_Simulation(N,p,S_to_E, E_to_I, to_R, t):
ERmap = create_ER(N, p) #生成ER网格
save_ER_to_file(ERmap) #存储ER网格
calculateDegreeDistribution(ERmap)
#计算度分布、存储度分布概率结果,并显示度分布图
showGraph(ERmap) #显示随机ER网格图
#重复实验次数 Rp 为 100
Rp = 100 #概率传播有一定误差,这里重复实验100次,若电脑运行速度更快可以提高
# 建立四个数组,准备存放不同t时间的节点数
S = np.array([1 for i in range(t)])
E = np.array([1 for i in range(t)])
I = np.array([1 for i in range(t)])
R = np.array([1 for i in range(t)])
for a in range(Rp): #重复实验次数Rp次,利用for套内循环
degrees = np.array([1 for i in range(N)])
#生成N个节点数,默认为1,即"易感者"
iNum = random.randint(0, N - 1)
degrees[iNum] = 3 #随机抽取一位"发病者"
Ss = []
Ee = []
Ii = []
Rr = []
for i in range(t):
if i !=0:
degrees = spread(ERmap,S_to_E,E_to_I, to_R, degrees)
Ss.append(np.where(np.array(degrees) == 1, 1, 0).sum())
Ee.append(np.where(np.array(degrees) == 2 , 1, 0).sum())
Ii.append(np.where(np.array(degrees) == 3, 1, 0).sum())
Rr.append(np.where(np.array(degrees) == 4, 1, 0).sum())
else:
Ss.append(np.where(np.array(degrees) == 1, 1, 0).sum())
Ee.append(np.where(np.array(degrees) == 2, 1, 0).sum())
Ii.append(np.where(np.array(degrees) == 3, 1, 0).sum())
Rr.append(np.where(np.array(degrees) == 4, 1, 0).sum())
S = S + Ss #将每次重复实验数据求和
E = E + Ee
I = I + Ii
R = R + Rr
print(S)
for i in range(t):
S[i] /= Rp #求Rp次实验后的平均值
E[i] /= Rp
I[i] /= Rp
R[i] /= Rp
print(S) #检验实验是否正常
#plt作图
plt.plot(S ,color = 'darkblue',label = 'Susceptible',marker = '.')
plt.plot(E ,color = 'orange',label = 'Exposed',marker = '.')
plt.plot(I , color = 'red',label = 'Infection',marker = '.')
plt.plot(R , color = 'green',label = 'Recovery',marker = '.')
plt.title('SEIR Model')
plt.legend()
plt.xlabel('Day')
plt.ylabel('Number')
plt.rcParams['savefig.dpi'] = 300 # 图片像素
plt.rcParams['figure.dpi'] = 100 # 分辨率
plt.savefig('/Users/apple/Documents/Class Folders/2019-2020 2/Python programming /Codes/Python/'
'SEIR 传播模型/SEIR曲线图.png')
#存储图片
plt.show()
epedemic_Simulation(1000,0.006,0.2,0.5,0.2,100)
#人数为1000,邻边结边率为0.006,感染率0.2,发病率0.5,康复率0.2,100天实验期