天使伊人 發表於 2022-2-18 14:01:55

R语言模拟疫情传播图RVirusBroadcast展示疫情数据

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>前言</li><li>效果展示</li><li>小结</li><li>参考</li><ul class="second_class_ul"><li>附录:RVirusBroadcast代码</li></ul></ul></div><p class="maodian"></p><h2>前言</h2>
<p>前几天微博的一个热搜主题是**&ldquo;计算机仿真程序告诉你为什么现在还没到出门的时候!!!&rdquo;**,该视频用模拟的疫情数据告诉大家&ldquo;不要随便出门(宅在家)&rdquo;对战胜疫情很重要,生动形象,广受好评。</p>
<p>所用的程序叫VirusBroadcast,源码已经公开,是用Java写的。鉴于画图是R语言的优势,所以笔者在读过源码后,写了一个VirusBroadcast程序的R语言版本,暂且叫做RVirusBroadcast。与VirusBroadcast相比,RVirusBroadcast所用的模型和逻辑大体不变,只是在少许细节上做了修改。<br />(为了防止上面的超链接被过滤掉而打不开,文末也放上了明文链接)</p>
<p class="maodian"></p><h2>效果展示</h2>
<p>下面两段视频是RVirusBroadcast用模拟的数据展示的效果,由于笔者的电脑性能实在一般,所以暂时只模拟了30天的数据。请再次注意下面两段视频的数据是模拟生成的,纯属虚构,不具有现实意义,仅供电脑模拟实验所用。</p>
<p>其他条件不变,当人们随意移动时,病毒传播迅速,疫情很难控制</p>
<p style="text-align:center"><img alt="随意移动" src="https://img.jbzj.com/file_images/article/202202/2022021811444952.gif" /></p>
<p>其他条件不变,当人们控制自己的移动时,病毒传播缓慢,疫情逐渐得到控制</p>
<p style="text-align:center"><img alt="控制移动" src="https://img.jbzj.com/file_images/article/202202/2022021811445153.gif" /></p>
<p class="maodian"></p><h2>小结</h2>
<p>诚如VirusBroadcast的作者所说,现在的模型是一个很简单的模型,所用的数据也是模拟生成的,还需优化改进。朋友们如果有兴趣,可以自行查阅复制下文中的R代码,自由修改。</p>
<p class="maodian"></p><h2>参考</h2>
<p> &ldquo;计算机仿真程序告诉你为什么现在还没到出门的时候&rdquo; 原视频地址:<br />https://www.bilibili.com/video/av86478875?spm_id_from=333.788.b_765f64657363.1</p>
<p class="maodian"></p><h3>附录:RVirusBroadcast代码</h3>
<div class="jb51code"><pre class="brush:ruby;">###name:RVirusBroadcast
###author:hxj7(hxj5hxj5@126.com)
###version:202002010
###note:本程序是"VirusBroadcast (in Java)"的R版本
###      VirusBroadcast (in Java) 项目链接:
###      https://github.com/KikiLetGo/VirusBroadcast/tree/master/src
library(tibble)
library(dplyr)
########## 模拟参数 ##########
ORIGINAL_COUNT &lt;- 50   # 初始感染数量
BROAD_RATE &lt;- 0.8      # 传播率
SHADOW_TIME &lt;- 140       # 潜伏时间,14天为140
HOSPITAL_RECEIVE_TIME &lt;- 10   # 医院收治响应时间
BED_COUNT &lt;- 1000      # 医院床位
MOVE_WISH_MU &lt;- -0.99   # 流动意向平均值,建议调整范围:[-0.99,0.99];
                     #   -0.99 人群流动最慢速率,甚至完全控制疫情传播;
                     #   0.99为人群流动最快速率, 可导致全城感染
CITY_PERSON_SIZE &lt;- 5000    # 城市总人口数量
FATALITY_RATE &lt;- 0.02       # 病死率,根据2月6日数据估算(病死数/确诊数)为0.02
SHADOW_TIME_SIGMA &lt;- 25   # 潜伏时间方差
CURED_TIME &lt;- 50            # 治愈时间均值,从入院开始计时
CURED_SIGMA &lt;- 10         # 治愈时间标准差
DIE_TIME &lt;- 300             # 死亡时间均值,30天,从发病(确诊)时开始计时
DIE_SIGMA &lt;- 50             # 死亡时间标准差
CITY_WIDTH &lt;- 700         # 城市大小即窗口边界,限制不允许出城
CITY_HEIGHT &lt;- 800
MAX_TRY &lt;- 300             # 最大模拟次数,300代表30天
########## 生成人群点,用不同颜色代表不同健康状态。 ##########
# 用正态分布刻画人群点的分布
CITY_CENTERX &lt;- 400         # x轴的mu值
CITY_CENTERY &lt;- 400
PERSON_DIST_X_SIGMA &lt;- 100# x轴的sigma值
PERSON_DIST_Y_SIGMA &lt;- 100
# 市民状态应该需要细分,虽然有的状态暂未纳入模拟,但是细分状态应该保留
STATE_NORMAL &lt;- 0            # 正常人,未感染的健康人
STATE_SUSPECTED &lt;- STATE_NORMAL + 1   # 有暴露感染风险
STATE_SHADOW &lt;- STATE_SUSPECTED + 1   # 潜伏期
STATE_CONFIRMED &lt;- STATE_SHADOW + 1   # 发病且已确诊为感染病人
STATE_FREEZE &lt;- STATE_CONFIRMED + 1   # 隔离治疗,禁止位移
STATE_DEATH &lt;- STATE_FREEZE + 1    # 病死者
STATE_CURED &lt;- STATE_DEATH + 1   # 治愈数量用于计算治愈出院后归还床位数量,该状态是否存续待定
worldtime &lt;- 0
NTRY_PER_DAY &lt;- 10   # 一天模拟几次
getday &lt;- function(t) (t - 1) %/% NTRY_PER_DAY + 1
# 生成人群数据
format_coord &lt;- function(coord, boundary) {
    if (coord &lt; 0) return(runif(1, 0, 10))
    else if(coord &gt; boundary) return(runif(1, boundary - 10, boundary))
    else return(coord)
}
set.seed(123)
people &lt;- tibble(
    id = 1:CITY_PERSON_SIZE,
    x = sapply(rnorm(CITY_PERSON_SIZE, CITY_CENTERX, PERSON_DIST_X_SIGMA),
             format_coord, boundary = CITY_WIDTH),    # (x, y) 为人群点坐标
    y = sapply(rnorm(CITY_PERSON_SIZE, CITY_CENTERY, PERSON_DIST_Y_SIGMA),
             format_coord, boundary = CITY_HEIGHT),
    state = STATE_NORMAL,    # 健康状态
    infected_time = 0,   # 感染时刻
    confirmed_time = 0,    # 确诊时刻
    freeze_time = 0,       # 隔离时刻
    cured_moment = 0,      # 痊愈时刻,为0代表不确定
    die_moment = 0         # 死亡时刻,为0代表未确定,-1代表不会病死
) %&gt;%
    mutate(tx = rnorm(CITY_PERSON_SIZE, x, PERSON_DIST_X_SIGMA),# target x
         ty = rnorm(CITY_PERSON_SIZE, y, PERSON_DIST_Y_SIGMA),
         has_target = T, is_arrived = F)

# 随机选择初始感染者
peop_id &lt;- sample(people$id, ORIGINAL_COUNT)
people$state &lt;- STATE_SHADOW
people$infected_time &lt;- worldtime
people$confirmed_time &lt;- worldtime +
    max(rnorm(length(peop_id), SHADOW_TIME / 2, SHADOW_TIME_SIGMA), 0)

########## 生成床位点 ##########
HOSPITAL_X &lt;- 720   # 第一张床位的x坐标
HOSPITAL_Y &lt;- 80    # 第一张床位的y坐标
NBED_PER_COLUMN &lt;- 100   # 医院每一列有多少张床位
BED_ROW_SPACE &lt;- 6       # 一行中床位的间距
BED_COLUMN_SPACE &lt;- 6    # 一列中床位的间距
bed_ncolumn &lt;- ceiling(BED_COUNT / NBED_PER_COLUMN)
hosp_beds &lt;- tibble(id = 1, x = 0, y = 0, is_empty = T, state = STATE_NORMAL) %&gt;%
    slice(-1)
if (BED_COUNT &gt; 0) {
    hosp_beds &lt;- tibble(
      id = 1:BED_COUNT,
      x = HOSPITAL_X + rep(((1:bed_ncolumn) - 1) * BED_ROW_SPACE,
                         each = NBED_PER_COLUMN),
      y = HOSPITAL_Y + 10 - BED_COLUMN_SPACE +
      rep((1:NBED_PER_COLUMN) * BED_COLUMN_SPACE, bed_ncolumn),
      is_empty = T,
      person_id = 0       # 占用床位的患者的序号,床位为空时为0
    )
}

########## 准备画图的数据 ##########
npeople_total &lt;- CITY_PERSON_SIZE
npeople_shadow &lt;- ORIGINAL_COUNT
npeople_confirmed &lt;- npeople_freeze &lt;- npeople_cured &lt;- npeople_death &lt;- 0
nbed_need &lt;- 0

########## 画出初始数据 ##########
# 设置画图参数
person_color &lt;- data.frame(   # 不同健康状态的颜色不同
    label = c("健康", "潜伏", "确诊", "隔离", "治愈", "死亡"),
    state = c(STATE_NORMAL, STATE_SHADOW, STATE_CONFIRMED, STATE_FREEZE,
            STATE_CURED, STATE_DEATH),
    color = c(
      "lightgreen",   # 健康
      "#EEEE00",      # 潜伏期
      "red",          # 确诊
      "#FFC0CB",      # 隔离
      "green",      # 治愈
      "black"         # 死亡
    ), stringsAsFactors = F
)
bed_color &lt;- data.frame(
    is_empty = c(T, F), color = c("#F8F8FF", "#FFC0CB"), stringsAsFactors = F
)
x11(width = 5, height = 7, xpos = 0, ypos = 0, title = "人群变化模拟")
window_hist &lt;- dev.cur()
x11(width = 7, height = 7, xpos = 460, ypos = 0, title = "疫情传播模拟")
window_scatter &lt;- dev.cur()
max_plot_x &lt;- ifelse(BED_COUNT &gt; 0, max(hosp_beds$x), CITY_WIDTH) + 10

# 疫情传播模拟散点图
dev.set(window_scatter)
plot(x = people$x, y = people$y, cex = .8, pch = 20, xlab = NA, ylab = NA,
       xlim = c(5, max_plot_x), xaxt = "n", yaxt = "n", bty = "n", main = "疫情传播模拟",
       sub = paste0("世界时间第 ", getday(worldtime), " 天"),
       col = (people %&gt;% left_join(person_color, by = "state") %&gt;%
            select(color))$color)
points(x = hosp_beds$x, y = hosp_beds$y, cex = .8, pch = 20,
         col = (hosp_beds %&gt;% left_join(bed_color, by = "is_empty") %&gt;%
            select(color))$color)
rect(HOSPITAL_X - BED_ROW_SPACE / 2, HOSPITAL_Y + 10 - BED_COLUMN_SPACE,
       max(hosp_beds$x) + BED_ROW_SPACE / 2, max(hosp_beds$y + BED_COLUMN_SPACE))
legend(x = 150, y = -30, legend = person_color$label, col = person_color$color,
         pch = 20, horiz = T, bty = "n", xpd = T)

# 人群变化模拟条形图
dev.set(window_hist)
bp_data &lt;- c(npeople_death, npeople_cured, nbed_need, npeople_freeze,
               npeople_confirmed, npeople_shadow)
bp_color &lt;- c("black", "green", "#FFE4E1", "#FFC0CB", "red", "#EEEE00")
bp_labels &lt;- c("死亡", "治愈", "不足\n床位", "隔离", "累计\n确诊", "潜伏")
bp &lt;- barplot(bp_data, horiz = T, border = NA, col = bp_color,
                xlim = c(0, CITY_PERSON_SIZE * 1), main = "人群变化模拟",
                sub = paste0("世界时间第 ", getday(worldtime), " 天"))
abline(v = BED_COUNT, col = "gray", lty = 3)
abline(v = CITY_PERSON_SIZE, col = "gray", lty = 1)
text(x = -350, y = bp, labels = bp_labels, xpd = T)
text(x = bp_data + CITY_PERSON_SIZE / 15, y = bp, xpd = T,
   labels = ifelse(bp_data &gt; 0, bp_data, ""))
legend(x = 300, y = -.6, legend = c("总床位数", "城市总人口"), col = "gray",
       lty = c(3, 1), bty = "n", horiz = T, xpd = T)
Sys.sleep(5)# 手动调整窗口大小

########## 更新人群数据 ##########
# 市民流动意愿以及移动位置参数174MOVE_WISH_SIGMA &lt;- 1
MOVE_DIST_SIGMA &lt;- 50
SAFE_DIST &lt;- 2   # 安全距离
worldtime &lt;- worldtime + 1
get_min_dist &lt;- function(person, peop) {# 一个人和一群人之间的最小距离
    min(sqrt((person["x"] - peop$x) ^ 2 + (person["y"] - peop$y) ^ 2))
}
for (i in 1:MAX_TRY) {
    # 如果已经隔离或者死亡了,就不需要处理了
    #
    # 处理已经确诊的感染者(即患者)
    peop_id &lt;- people$id[people$state == STATE_CONFIRMED &amp;
                                 people$die_moment == 0]
    if ((npeop &lt;- length(peop_id)) &gt; 0) {
      people$die_moment &lt;- ifelse(
      runif(npeop, 0, 1) &lt; FATALITY_RATE,   # 用均匀分布模拟确诊患者是否会死亡
      people$confirmed_time + max(rnorm(npeop, DIE_TIME, DIE_SIGMA), 0),# 发病后确定死亡时刻
          -1                                    # 逃过了死神的魔爪
      )
    }
    # 如果患者已经确诊,且(世界时刻-确诊时刻)大于医院响应时间,
    # 即医院准备好病床了,可以抬走了
      peop_id &lt;- people$id[people$state == STATE_CONFIRMED &amp;
                  worldtime - people$confirmed_time &gt;= HOSPITAL_RECEIVE_TIME]
    if ((npeop &lt;- length(peop_id)) &gt; 0) {
      if ((nbed_empty &lt;- sum(hosp_beds$is_empty)) &gt; 0) {# 有空余床位
      nbed_use &lt;- min(npeop, nbed_empty)
      bed_id &lt;- hosp_beds$id
       # 更新患者信息
      peop_id2 &lt;- sample(peop_id, nbed_use)   # 这里是随机选择,理论上应该按症状轻重
          people$x &lt;- hosp_beds$x
      people$y &lt;- hosp_beds$y
      people$state &lt;- STATE_FREEZE
      people$freeze_time &lt;- worldtime
       # 更新床位信息
      hosp_beds$is_empty &lt;- F
      hosp_beds$person_id &lt;- peop_id2
      }
    }
    # TODO 需要确定一个变量用于治愈时长。
    # 为了说明问题,暂时用一个正态分布模拟治愈时长并且假定治愈的人不会再被感染
    peop_id &lt;- people$id[people$state == STATE_FREEZE &amp;
                           people$cured_moment == 0]
    if ((npeop &lt;- length(peop_id)) &gt; 0) { # 正态分布模拟治愈时间
      people$cured_moment &lt;- people$freeze_time +
      max(rnorm(npeop, CURED_TIME, CURED_SIGMA), 0)
    }
    peop_id &lt;- people$id[people$state == STATE_FREEZE &amp; people$cured_moment &gt; 0 &amp;
                           worldtime &gt;= people$cured_moment]
    if ((npeop &lt;- length(peop_id)) &gt; 0) {# 归还床位
      people$state &lt;- STATE_CURED
      hosp_beds$is_empty[! hosp_beds$is_empty &amp; hosp_beds$person_id %in% peop_id] &lt;- T
      people$x &lt;- sapply(rnorm(npeop, CITY_CENTERX, PERSON_DIST_X_SIGMA),
               format_coord, boundary = CITY_WIDTH)    # (x, y) 为人群点坐标
      people$y &lt;- sapply(rnorm(npeop, CITY_CENTERY, PERSON_DIST_Y_SIGMA),
               format_coord, boundary = CITY_HEIGHT)
      people$tx &lt;- rnorm(npeop, people$x, PERSON_DIST_X_SIGMA)
      people$ty &lt;- rnorm(npeop, people$y, PERSON_DIST_Y_SIGMA)
      people$has_target &lt;- T
      people$is_arrived &lt;- F
    }
    # 处理病死者
    peop_id &lt;- people$id[people$state %in% c(STATE_CONFIRMED, STATE_FREEZE) &amp;
      worldtime &gt;= people$die_moment &amp; people$die_moment &gt; 0]
    if (length(peop_id) &gt; 0) {# 归还床位
      people$state &lt;- STATE_DEATH
      hosp_beds$is_empty[! hosp_beds$is_empty &amp; hosp_beds$person_id %in% peop_id] &lt;- T
    }
    # 处理发病的潜伏期感染者
    peop_id &lt;- people$id[people$state == STATE_SHADOW &amp;
                        worldtime &gt;= people$confirmed_time]
    if ((npeop &lt;- length(peop_id)) &gt; 0) {
      people$state &lt;- STATE_CONFIRMED   # 潜伏者发病
    }
    # 处理未隔离者的移动问题
    peop_id &lt;- people$id[
      ! people$state %in% c(STATE_FREEZE, STATE_DEATH) &amp;
      rnorm(CITY_PERSON_SIZE, MOVE_WISH_MU, MOVE_WISH_SIGMA) &gt; 0] # 流动意愿
    if ((npeop &lt;- length(peop_id)) &gt; 0) {# 正态分布模拟要移动到的目标点
      pp_id &lt;- peop_id[! people$has_target | people$is_arrived]
      if ((npp &lt;- length(pp_id)) &gt; 0) {
      people$tx &lt;- rnorm(npp, people$tx, PERSON_DIST_X_SIGMA)
      people$ty &lt;- rnorm(npp, people$ty, PERSON_DIST_Y_SIGMA)
      people$has_target &lt;- T
      people$is_arrived &lt;- F
      }
      # 计算运动位移262      dx &lt;- people$tx - people$x
      dy &lt;- people$ty - people$y
      move_dist &lt;- sqrt(dx ^ 2 + dy ^ 2)
      people$is_arrived &lt;- T# 判断是否到达目标点266    pp_id &lt;- peop_id
      if ((npp &lt;- length(pp_id)) &gt; 0) {
      udx &lt;- sign(dx)# x轴运动方向269      udy &lt;- sign(dy)
      # 是否到了边界
      pid_x &lt;- (1:npp) + udx &lt; 0 | people$x + udx &gt; CITY_WIDTH]
      pid_y &lt;- (1:npp) + udy &lt; 0 | people$y + udy &gt; CITY_HEIGHT]
      # 更新到了边界的点的信息
      people$x] &lt;- people$x] - udx
      people$y] &lt;- people$y] - udy
      people$has_target, pp_id))] &lt;- F
      # 更新没有到边界的点的信息278      people$x] &lt;- people$x] +
          udx[! pp_id %in% pid_x]
      people$y] &lt;- people$y] +
          udy[! pp_id %in% pid_y]
      }
    }
    # 处理健康人被感染的问题
    # 通过一个随机幸运值和安全距离决定感染其他人286    normal_peop_id &lt;- people$id
    other_peop_id &lt;- people$id[! people$state %in% c(STATE_NORMAL, STATE_CURED)]
    if (length(normal_peop_id) &gt; 0) {
      normal_other_dist &lt;- apply(people, 1, get_min_dist,
                               peop = people)
      normal2other_id &lt;- normal_peop_id[normal_other_dist &lt; SAFE_DIST &amp;
                        runif(length(normal_peop_id), 0, 1) &lt; BROAD_RATE]
      if ((n2other &lt;- length(normal2other_id)) &gt; 0) {
      people$state &lt;- STATE_SHADOW
      people$infected_time &lt;- worldtime
      people$confirmed_time &lt;- worldtime +
          max(rnorm(n2other, SHADOW_TIME / 2, SHADOW_TIME_SIGMA), 0)
      }
    }
    # 画出更新后的数据
      npeople_confirmed &lt;- sum(people$state &gt;= STATE_CONFIRMED)
    npeople_death &lt;- sum(people$state == STATE_DEATH)
    npeople_freeze &lt;- sum(people$state == STATE_FREEZE)
    npeople_shadow &lt;- sum(people$state == STATE_SHADOW)
   npeople_cured &lt;- sum(people$state == STATE_CURED)
    nbed_need &lt;- npeople_confirmed - npeople_cured - npeople_death - BED_COUNT
    nbed_need &lt;- ifelse(nbed_need &gt; 0, nbed_need, 0)# 不足病床数
    # 疫情传播模拟散点图
    dev.set(window_scatter)
    plot(x = people$x, y = people$y, cex = .8, pch = 20, xlab = NA, ylab = NA,
         xlim = c(5, max_plot_x), xaxt = "n", yaxt = "n", bty = "n", main = "疫情传播模拟",
         sub = paste0("世界时间第 ", getday(worldtime), " 天"),
         col = (people %&gt;% left_join(person_color, by = "state") %&gt;%
                select(color))$color)
    points(x = hosp_beds$x, y = hosp_beds$y, cex = .8, pch = 20,
         col = (hosp_beds %&gt;% left_join(bed_color, by = "is_empty") %&gt;%
                  select(color))$color)
    rect(HOSPITAL_X - BED_ROW_SPACE / 2, HOSPITAL_Y + 10 - BED_COLUMN_SPACE,
         max(hosp_beds$x) + BED_ROW_SPACE / 2, max(hosp_beds$y + BED_COLUMN_SPACE))
    legend(x = 150, y = -30, legend = person_color$label, col = person_color$color,
         pch = 20, horiz = T, bty = "n", xpd = T)
    # 人群变化模拟条形图
    dev.set(window_hist)
    bp_data &lt;- c(npeople_death, npeople_cured, nbed_need, npeople_freeze,
               npeople_confirmed, npeople_shadow)
    bp &lt;- barplot(bp_data, horiz = T, border = NA, col = bp_color,
                  xlim = c(0, CITY_PERSON_SIZE * 1), main = "人群变化模拟",
                  sub = paste0("世界时间第 ", getday(worldtime), " 天"))
    abline(v = BED_COUNT, col = "gray", lty = 3)
    abline(v = CITY_PERSON_SIZE, col = "gray", lty = 1)
    text(x = -350, y = bp, labels = bp_labels, xpd = T)
    text(x = bp_data + CITY_PERSON_SIZE / 15, y = bp, xpd = T,
         labels = ifelse(bp_data &gt; 0, bp_data, ""))
   legend(x = 300, y = -.6, legend = c("总床位数", "城市总人口"), col = "gray",
         lty = c(3, 1), bty = "n", horiz = T, xpd = T)
# 更新世界时间
    worldtime &lt;- worldtime + 1
}
</pre></div>
<p>以上就是R语言模拟疫情传播图告诉你为什么还没到出门的时候的详细内容,更多关于R语言模拟疫情传播图的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>R语言数据可视化绘图bar&nbsp;chart条形图实现示例</li><li>R语言绘制Bubble&nbsp;Matrix气泡矩阵图</li><li>R语言数据可视化绘图Lollipop chart棒棒糖图</li><li>R语言数可视化Split violin plot小提琴图绘制方法</li><li>R语言UpSet包实现集合可视化示例详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: R语言模拟疫情传播图RVirusBroadcast展示疫情数据