本文主要简要分享一下我从1月底开始的一个用shiny制作的关注日本疫情动态的仪表盘应用项目。

可能有的读者对这篇文章有既视感,是因为在5月份的时候,我将当时访问量仅有 600 万的文章第一个版本投稿到了Y叔叔(生信专业R包开发大牛)的公众账号上(原创:用shiny和echarts4r制作一个COVID-19的dashboard)。而随着日本第二波疫情的扩大,4个月也已经转眼即逝,网站访问量再次翻倍突破了 1300 万。因此在统计之都编辑的帮助下,再次对文章做了细节优化,作为一份shiny应用开发经验分享给大家。

Figure1

项目本身为日文版https://covid-2019.live/,5月份左右逐步翻译成中文版英文版。项目中的代码和数据集全部开源,详见 https://github.com/swsoyee/2019-ncov-japan,只要把整个仓库克隆(下载)到本地,安装所需的软件包即可本地启动。

项目开发是利用工作之余的时间,我本人不是R语言和shiny应用开发专家,个人精力也有限,项目在功能上已经不会有很大改动了,代码质量和项目结构可能极其恐怖且不规范,能用就行,希望各位读者吐槽之余建言献策,帮助继续维护项目。

文笔不好请大家多多包涵。正文夹杂了不少冗长的个人对这个项目的定位以及思考过程,进行了引用标注,各位读者可以选择跳着看。

Figure2

这个仪表盘随着日本疫情的发展经过了数次迭代,GitHub的提交次数高达8000次,除了数据更新类的提交外,有数千次是属于功能改善的提交。

Figure:NHK

在仅依靠口口相传下,浏览数从2月中旬刚上线的日均3000飙到最高时日均10万,截至发文之前,取得了总浏览量超过1300万的成绩。期间,项目不仅在eRum2020::CovidR比赛中荣获优秀奖,也有幸被日本NHK(福冈)电视台在2个节目中进行了介绍,还受到了日本诺贝尔奖获得者山中伸弥教授的认可,被引用到他的新冠肺炎信息网站中

由于个人时间和精力的关系,这个仪表盘应用完全不能说有多完美,仍有许多需要改善的烂尾部分,不过在项目进行的过程中也积攒了不少可供分享的个人经验和相关技术,希望能够帮助各位读者在用shiny打造同类产品的路上,梳理出来一个路径,少走一些弯路。

项目规划

我在开发这个项目时,首先对项目做了一些定位思考,归结起来为以下几点:

  1. 做一个能够实时快速反映各地数据的仪表盘;
  2. 在尽可能多维度展示数据上下功夫;
  3. 注重动态互动,处理好疫情时间轴。
  • 1月底,国内已经到了确诊人数快速增加的阶段,而那时日本才刚刚开始(在开始着手开发时,日本全国的确诊人数还在个位数)。国内疫情刚开始的时候,我和周围的朋友都十分关注哪个省份出现了疫情,具体确诊人数在多少,丁香园的疫情信息页面刚刚上线。因此在疫情初期,就很需要一个能够实时快速展现各地区数据的页面了,而在那时日本还没有出现此类应用。
  • 如果仅仅提供单纯的数据,会显得内容十分地薄弱。而日本的互联网大厂届时肯定会竞相开发自己的疫情信息页面(就如在丁香园之后,BAT等各个大厂都开发了自己产品线上的页面)。此时,一般用户肯定更倾向于使用有企业或者官方作为准确性担保的页面,个人项目最终会沦落为只被打开一次就不再点击的个人小作品。因此,页面需要更多能够帮助人们在各方面清晰了解到疫情的综合类应用。如果定位在信息收集类或者内容提供类的网站,百分百会被互联网大厂给挤兑掉。因此,如果能在数据展示方面下一些功夫,提供一些稍有学术性质但普通用户又不难理解的内容,或许能有一席之地。此时,就能体现出仪表盘类应用的优势了。
  • 在疫情结束之后,对于一般人来说,也就几乎不会再访问了。而如果是一个能够动态展示过去数据的疫情网站,或许还能够发挥余热,对未来的学术性研究起到一定的协助作用。因此,一定要处理好时间轴这一项,使项目能够苟延残喘。

数据源

为了满足项目的需求,就需要开始选择仪表盘最为重要的部分——数据源。

  1. 以媒体发布的新闻或数值作为主要实时数据源(半自动);
  2. 各级政府和厚生劳动省的数据在其官方上公开的(手动);
  3. 其他志愿者汇总整理的数据集(自动)。
  • 首先,霍普金斯大学的数据中没有细致到日本各个省份的数据,所以只能自己手动收集数据(1月末的时候)。日本官方发布疫情数据的机构为各个自治体(省份)和相当于国内卫生部的厚生劳动省。自治体和厚劳省发布的数据并不及时,而各个媒体报道的消息最快。因此,只能自己关注各大媒体的消息,打造实时数据集。在2月初时发现一家媒体JX通讯社上线了自家的疫情页面,其数据更新速度是所有日媒中最快的,在后期,实时数据部分的更新源头就依赖他家了。
  • 其次,在对项目的定位上,应用需要提供多维度的数据可视化。很多没办法实时获取到的数据如检测人数、出院数,新冠咨询热线拨打次数等,仅在各级政府和厚劳省的页面进行汇总。
  • 最后,一些数字外的信息,如患者的个人活动轨迹、症状等详细信息都只能在各级政府的网站上获取到,且没有统一格式的PDF文档,好在日本企业SIGNATE(日本版 Kaggle)和一些网上的志愿者在进行数据整理,因此,我们利用这一部分数据来进行更丰富的可视化。

主要框架

当然逃不过最为基础的shiny,仪表盘框架为shinydashboardPlus,其实还有一些升级版如bs4Dash等,但当我注意到的时候已经晚了,重构自己网站的开销又太大,所以目前选用的主体框架是前者。关于shiny应用开发,可以关注 RinteRface 组织,他们提供了很多有意思的包可以你的应用更为酷炫。另外, Github上的awesome-shiny-extensions库罗列了很多有意思的shiny插件包,十分推荐感兴趣的读者前去浏览浏览。

主要的绘图包为注重互动性的 echarts4r(个人认为ggplot2更倾向于学术,而plotly的流畅度有点挫),表格展示为常用的DT,增强交互元件功能为shinyWidgets,悬浮提示为shinyBS,加载动画显示用shinycssloaders,应用的多语言化shiny.i18n,小图展示为sparkline等包。数据处理方面统一采用data.tableshiny应用的性能优化方面使用profvis。具体用法不一一介绍,大家只要根据名字去 Github 上搜即可。

应用逻辑

对于一些老手来说,下面可能是一些初级问题,作为非应用开发专业出身的前生信专业的渣渣来说,走过不少坑,并且还在持续踩坑中,将踩坑经验分享出来,希望给大家带来帮助。

  1. 展示类应用中应尽力避免在应用中进行数据处理的各项操作,将数据处理放到本地进行,shiny 直接读取处理后的数据即可。
  2. 在用户打开应用首页时,尽量避免读取全部数据,为非首页所需要的数据做一个触发器,如点击标签切换才加载本地数据。

第二点不怎么需要说明,主要提一下我在第一点踩过的坑。

搜集的数据往往不能够满足可视化所需要的结构,我们常常需要对data.framedata.table进行一些预处理,如添加内容、修改或者统计数据。起初,我所有的数据变换都是在应用中完成的,这导致网站用户量稍微大一点点,服务器就繁忙到所有人都访问不了。后来,我发现先写独立的数据处理脚本将原始数据处理成展示所需要的结构,然后直接读取展示用的表格就行了。这个独立的脚本可以使用git pre-commit hook在你每次提交代码的时候进行调用,或者是使用Github Action来完成数据的预处理。经过这次优化后,网站的403次数明显下降了,美滋滋。另外,可以利用代码效率检查包profvis来判断一下采用哪种方式能使得你的用户体验更为友好。

主要功能

废话了这么多,终于到仪表盘的功能介绍了。项目本身代码量较大,这篇文章不着重介绍代码,而仅仅采用流水方式走一遍。

1. 首页

以横向布局为主,分为4层结构。

Figure1

1.1 总计数值栏面板

和大多数仪表盘一样,最重要的四项总数指标放在首位。左边使用了shinydashboardPlus::widgetUserBox()对该仪表盘的数据对象做一个简要的介绍页面,而右边的shinydashboard::valueBox()在原版的基础上搭配shiny::tagList()和各种shiny::tags$X()函数进行内容扩展,这样能够融入更多的信息量。下方有该网站的多语言版本的点击跳转切换按钮。

Figure3

1.2 全国数据总览面板

1.2.1 疫情地图

这部分主要有4个标签,其中第一个标签的疫情地图为左地图右表格的结构,为整个应用中最为重要的部分。也是用户关注最多的部分。

Figure4

其中,左侧地图有简易和详细两种模式。简易模式为静态地图,详细模式则是可以根据时间播放的动态地图。项目初期为了显得更高大上,只提供了动态地图展示。但在运营过程中,数据量的增加和访问流量的暴涨都对网站的用户体验造成了极大影响,因此不得不增加了简易模式并作为默认选项。简易模式目前分别有现在患者数累计患者数重症患者数3个选项供用户自行选择查看。地图下方有现存患者数的进度条,为用户提供了一个简单直观的疫情发展情况的指标。

Figure5

在动态疫情图中,将可以看到从第一例开始的确诊累计的和每日各个自治体的增加情况,推荐各位可以前往站点进行实际操作体验一下。此外还有一些简单的播放设定供用户自行调节。可能今后将会添加现存患者的动态地图,来展现一个从无到有从有到无的疫情发展过程(天坑)。

Figure6

右边主要由确诊检测康复和死亡三个表格组成,对于一些因为表格宽度不够的列或者是指标不太重要的列可以利用DT的自带插件功能,做一个显示隐藏按钮(如图左上的列显示)。

此外,为了在有限空间内尽量增加数据的丰富度,可以利用sparkline包添加一些小的图表(如确诊趋势)在内。最初这个表格是在shiny启动时生成的,但其生成开销较大,很容易造成多用户访问时页面崩溃,或者是页面渲染时间过长。所以推荐将此类处理和shiny应用本身进行分离。

此外还有一个比较不容易注意到的点就是在使用DT进行表格展示时候,可能还需要考虑在手机上的用户体验,因此一般不太建议把表格横向做得很长,以3到4列不需要用户左右滑动的用户体验为最佳。不过实际开发当中要同时满足桌面版和手机版的最佳体验,用DT是非常困难的一件事(或者是我没有找到很好的办法),因此也只能折中选其一,以桌面版用户体验为优先了。

Figure7

使用DT的按钮插件还可以在检测表格中选择追加显示列。

Figure8

恢复死亡表完成度较低(一个人开发工作量实在是太大了),不过同样可以利用sparkline作出扇形图用以展现康复百分比等信息。右上角还有各种设定可供用户自行调整显示方式等。

1.2.2 多维度比较

此部分主要用来展示通过实时新闻报道收集的数据与厚劳省官方发布的数据的比较,目的在于清晰地让用户了解到本网站数据集的可信度。

在这个页面中同时用简单的条形图展示检测人数确诊康复重症死亡等各项数值随时间变化情况。雷达图用来反映每个省份在这一个月内的各项指标变化情况。该功能处于初期版,今后预计加入时间选项和选取更为科学的指标来反映省份的疫情情况。 在选用echarts4r进行可视化时,多个图表的悬浮提示能够非常简单地进行联动,而如果用ggplot2或者plotly时候为了达成相同效果可能就要费一番功夫了。

Figure9

1.2.3 确诊热图

目前提供了2张热图,同样完成度还很低,但能够让用户迅速直观地掌握到全国各地区的每日增长和增速情况。此外还可以考虑制作成3d形式的热图或者添加更多信息到这图表中(如各地区政府的疫情应对政策和大事件等)。配合右边的空位和鼠标点击事件、悬浮操作可以制作出具有庞大信息量的可视化页面。

Figure10

Figure11

1.2.4 确诊矩形树图

在很多疫情仪表盘类应用中,通常采用地图的形式展示每个区域的确诊人数情况(可用leaflet实现)。但现实情况是每个国家、地区所发表的确诊患者信息的详细程度各有不同,比如国内包括香港地区在内的仪表盘可以精确到小区和楼栋,而日本一般情况下只能获取到地区级范围的信息。如果使用地图来展示的话,患者坐标定位就会是一个极大的难点。因此在这里我使用了矩形树图来进行可视化。

Figure12

点击每一个矩形整个图形即可平滑地动态扩大、缩小和移动,展现次级分类数据。

Figure13

矩形树图在echarts4r中的完成度还不算高,还有很多原版Echart中提供的设置选项无法使用。不过还是要感谢echarts4r的作者John Coene将百度团队的Echart引入到了R中,使得制作精美且交互性出色的图表不再困难,也期待百度和John带给我们更多新的绘图功能(有感而发非广告)。顺带吐槽一下John还会中文,强无敌。

1.2.5 详细数值栏

在全国数据总览的shinydashboardPlus::boxPlus()中的底部,使用shinydashboardPlus::DescriptionBlock()制作了一排用于展示主要数据的底栏。一开始出于全面展示日本境内的疫情状况(包括医疗资源应对等)的目的,同时将公主号游轮的数据纳入了统计范围当中了(根据WHO的标准,公主号游轮单独进行了统计而非日本的数据)。因此在这里为了说明这一情况,把日本国内的计数和游轮进行分开统计以免造成误解。此外使用shinyBS::bsTooltip()进行一些额外信息的加注。

Figure14

1.3 数值趋势面板

这一部分主要是使用较为常见的柱状图、线形图等来描述疫情的发展趋势。大致以2:1结构进行面板划分,左侧为主要柱状图,右侧为放置信息加注的空间。

1.3.1 确诊趋势

页面左侧为最基础的每日确诊图柱状图,右侧为各省份的每日增长情况。为了和上一个面板有所功能上的区分,在这里追加了可以对地区进行叠加显示的地区选择选项,方便用户自行选择某一地区的数据变化情况(如关西地区、关东地区等)。由于检测数在节假日时会显著下降,为了处理这种周期性震荡,不仅在柱状图上添加了移动平均线,还在右侧添加了日历图,来方便大众自行对数据进行解读。该功能上线后随即带来了网站访问数的第一个峰值(自夸一下在我添加了移动平均线后,不少同类应用包括日媒统计网站也跟着追加了移动平均线w)。

Figure15

出于项目的定位考虑,还引入了带有一点学术性质的单双轴对数确诊图。一般的大企业所开发的疫情页面可能出于更为简单明了地将数据呈现给一般用户的考虑,基本不会在页面中追加此类图表。而对较为学术型用户来说,会参考对数图来对疫情走势进行一些判断。因此实时更新的对数图就能作为页面的一个亮点内容,从而减少用户打开一次就不再访问的一击脱离现象的发生,维持网站一定量活跃度。功能上线后网站也到达了它的第二个流量顶峰。

Figure16

该区块同样也提供了一些简单的设定如显示或隐藏某个省份的数据、选择是否使用对数方式展现数据,或计算间隔等。

Figure17

接下来是PCR检测人数、康复和新冠咨询热线的接线情况等。在echarts4r下支持图例点击显示/隐藏,同时在切换的过程中会有流程的默认过渡动画效果。个人认为这一点比plotly更为出色。

Figure18

Figure19

Figure20

1.4 其他数据

左侧为确诊患者的性别年龄金字塔图,用户可自行选择想了解的都道府县的现状,或者是搭配确诊日期范围来定制展示图表的时间范围。这部分数据来源于其他志愿者的贡献,但由于数据集更新的不稳定性已经无法持续更新了。

Figure21

右侧为关于确诊患者去向的桑基(Sankey)图,用于显示每日确诊患者的流向(症状的有无轻重、是否在住院、出院和死亡等信息)。但由于厚生劳动省所发表的数据格式有过多次变更,标准更改前后数据一致性无法做到统一,这个桑基图也只能无奈地停更了。不过作为一种可以参考的数据可视化方式仍然保留在了页面当中。

下方左侧则是确诊患者的新闻来源,前期为各大媒体的速报新闻,现在已经稳定在使用NHK的链接作为数据引用出处。其目的在于提供网站所收集数据可靠性的一个佐证。

右侧为一个跳转到聚集性感染网络图(后述)的按钮。至于为何要在这里加一个按钮,是出于移动端页面的考虑。在移动端中,如果启用了侧边栏,shinydashboard默认会将侧边栏进行隐藏,一般用户很少发现页面还能提供额外的功能。而聚集性感染网络图是其他同类网站所没有的主打亮点功能,为了让新用户能够了解到这一功能,因此在这里添加了一个跳转到别的子页面的按钮就显得有所必要了。

至于为何不直接把网络图放到首页,究其原因有以下三点:

  1. 极度影响到落地页的渲染速度;
  2. 网络图功能页面复杂度较高,页面布局占用率过大,动态网络图的绘图计算量也大;
  3. 虽然酷炫,但使用率不高。当感染人数到达一定程度后,就很少还有人去关注感染网络本身,对疫情数字麻木后估计只看个数值就关闭页面了。

因此综合考虑,与其把网络本身放到首页,不如仅仅添加一个跳转按钮更为友好。

2. 其他功能页面

剩下的一些页面由于完成度都不太高,就简要带过了。

感染路线子页面中,可以看到确诊的病例是如何传染开来构成一张整体的网络的。鼠标悬浮于节点可以高亮显示患者简要信息,点击则会在右侧面板中显示患者完整信息。此外还可以通过右侧的搜索框对患者进行搜索。不过由于通过第三方收集的数据集结构常常发生变化,4月5日以后的数据已经无法更新。只能等待数据集稳定后再对此功能进行重构(天坑)。

Figure22

接下来是各省份的单独页面。在整个日本疫情应对情况中,各个省份独自发布的数据在时间上会比厚劳省更为提前。因此和首页使用厚劳省的数据不同,这里直接使用各省份所发布的数据进行可视化。不过由于完成所有47个都道府县的独立页面的工程量实在太大,而且各个省级机构数据发布格式也不是长期统一稳定,需要经常进行格式匹配的修缮工作,只能暂时放弃继续开发。

Figure23

Figure24

Figure25

案例地图中可以查看每一个患者的行动轨迹。由于完全没有现成数据集,不仅需要自己从各级政府所发布的PDF报告中提取内容写成json文件,还要搜索关于每个病例的新闻报道中的消息(政府官方发布的消息非常有限,而在新闻媒体中常常能够获取不少额外信息),工程量实在太大,因此在项目初期这个开发计划早已作废……

Figure26

最后是利用谷歌所发布的社区流动报告进行一个简要的可视化。用于了解各地区在国家进行紧急事态宣言后人们的出行情况。完成度较低因此不做详细介绍。

Figure27

结语

感谢大家看了那么长唠唠叨叨的一堆废话,如果能为大家在写shiny应用时提供到一点帮助或者灵感的话,那么本文的任务也就达成了。同时欢迎各位前往项目的Github: swsoyee/2019-ncov-japan 加Star(骗赞)、提Issue或PR,或者在评论区参与讨论shiny应用的开发。

致谢

本文主要是介绍了项目的shiny前端和数据展示部分,后端部分由技术大牛我的前辈 @Bob-FU 帮忙打理和运维。在他不断地对项目的后端进行的优化下,可以自豪地说我们的应用在速度方面达到了一般shiny应用难以达到的高度。也正是有他的支持,我们的应用才能承受得起上千万浏览量的冲击。

同时我还要感谢 RStudio 公司给我们项目提供专业版的服务器支持。仅凭借免费版的服务器,我们的网站可能会在处理同等流量上面临更为困难的技术挑战。

最后,感谢所有在这个项目上为我提供过帮助或者认可的个人和组织(没有你们的支持,项目早就停更了,毕竟每天维护还是很累的w)。

发表/查看评论