PythonER随机网格&数据可视化实现"以SEIR传播动力学模型实验为例"

SEIR传播动力学模型

  1. 什么是SEIR传播动力学模型
    经典SEIR模型将人群分为S (Susceptible,易感者),I (Infected,感染者),E (Exposed,潜伏者)和R (Recovered,康复人群)。

  2. 本文根据SEIR传播动力学模型,利用Python实现生成ER随机网格、并在网格间随机结边、模拟SEIR型传染病传播

  3. 该实验可分为两个步骤进行:
    一、生成一个ER随机网络,生成网络节点的度分布图、画出节点分布图
    二、编写程序:编程SEIR传播动力学模型。随机选择一个传播节点,基于SEIR传播动力学模型,统计每个时间步长对应的S,E,I,R四个状态节点数,作图
    *注意:为防止概率传播误差,宜进行100次独立实验,取平均值

  4. 关键词解析:
    • 网络是由节点和边组成。节点代表个体,边代表节点之间的关系,比如两个节点是朋友,那么他们之间有连边,网络可以用一个邻接矩阵表示,矩阵中的0表示没有接触,即没有连边,1表示有接触,即存在连边。下面是一个简单的例子:
    PythonER随机网格&数据可视化实现
    • 度(degree): 邻居节点的个数,如节点a的度为4,对应于邻接矩阵中a行或列1的个数。

  5. 生成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()

获得的四个文件如下:
PythonER随机网格&数据可视化实现
PythonER随机网格&数据可视化实现
PythonER随机网格&数据可视化实现
PythonER随机网格&数据可视化实现

  1. 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天实验期):

PythonER随机网格&数据可视化实现
屏幕窗口效果图应为:
PythonER随机网格&数据可视化实现

由本次实验结果图可知,病毒传播会在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天实验期

你可能感兴趣的:(Python)