plotly + Dash:探索深圳市400例新冠肺炎个案可视化

笔记内容:

  • 数据获取
  • 用plotly(python) 实现400个病例来深时间,发病时间,入院时间概览
  • 用dash实现上述概览,并添加各个例性别,年龄,居住地情况筛选。

注意:

  • 数据来源为深圳市政府数据开放平台,注册即可获取。个案详情是一个详细表格,不是一坨通告,感恩。
  • 400例患者个案信息是2月14日及其之前发布的,现在已经不是400例了。

结果大概这样,这只是个很简陋的想法,不能说明任何流行病学上的问题。所以本质上只是我个人强行使用dash的练手项目。

数据获取

主要使用了深圳市政府数据开放平台公布数据中的个案详情作图
就新冠肺炎市政府公布了每日确诊病例,每个行政区每日上报的病例数目,以及在本笔记中用到的个案详情。需要先注册账号,然后才能获取。

选中后点击下载。(是有API的,但我没看明白=_=,既然有csv,我就直接下载了csv格式的数据)

可以预览一下:

用plotly实现400个病例来深时间,发病时间,入院时间概览

先将plotly部分的代码写好,做一个Figure对象出来。dash可以直接调用这个Figure。
先整理数据,很多来深时间为null的病例,为密切接触者,或者没有发病,筛查中核酸阳性,仍然按病例处理入院。整理成一个包含了400个病例来深时间,发病时间,入院时间,年龄,性别,居住地的dataframe.

import pandas as pd

case_report_file = 'C://Users//XXX//20200215_CoVtry//深圳市“新型肺炎”-每日新增确诊病例个案详情_2920001503668.csv'
case_report = pd.read_csv(case_report_file,sep=',',engine='python',encoding='utf-8')
case_report.index = case_report.blh.tolist()

# 把lssj, fbingsj, rysj 三个column提出来,即来深时间,发病时间,入院时间
time3 = case_report.loc[:,['lssj','fbingsj','rysj']]
time3.columns = ['sz_arrive','onset','hospitalized']
print('sz_arrive null: {0}'.format(time3['sz_arrive'].isnull().sum()),
      'onset null: {0}'.format(time3['onset'].isnull().sum()),
      'hospitalized: {0}'.format(time3['hospitalized'].isnull().sum()))
# sz_arrive null: 69 onset null: 10 hospitalized: 0
# 很多sz_arrive为null的病例,为密切接触者,或者没有发病,筛查中核酸阳性,仍然按病例处理入院。

time3 = time3.apply(lambda x:pd.to_datetime(x, errors='coerce'))
time3_sub = pd.concat([time3,case_report.loc[:,['xb','jzd','nl']]],sort=False,axis=1)
time3_sub.columns = ['sz_arrive', 'onset', 'hospitalized','gender','residence','age']


# 对居住和年龄段粗分类
def resi_class_assign(single_cell):
    # assign 居住地 as hubei_others, hubei_wuhan, shenzhen, others
    if '湖北' in single_cell:
        if '武汉' in single_cell:
            return 'hubei_wuhan'
        else:
            return 'hubei_others'
    elif '深圳' in single_cell:
        return 'shenzhen'
    
    else:
        return 'others'

def age_range_assign(single_cell):
    # assgin age(int) as <=20, 21-55, >=55
    if single_cell > 20:
        if single_cell > 55:
            return 'age >= 55'
        elif single_cell < 55:
            return 'age: 21-55'
    else:
        return 'age <=20'

time3_sub['residence_class'] = [resi_class_assign(str(i)) for i in time3_sub['residence'].tolist()]
time3_sub['age_range'] = [age_range_assign(int(i)) for i in time3_sub['age'].tolist()]

time3_sub.head() #如下所示:

然后画图:
将y轴当作1-400个病例的ID,x轴为时间。于是每个病例的来深时间,发病时间,入院时间,都可以通过<=3个点连成的一条线描述出来。

import plotly
from plotly.subplots import make_subplots
import plotly.graph_objects as go

trace_list = []
color_dict = dict(zip(time3.columns,plotly.colors.colorbrewer.Set1))

for i in time3.columns:
    tra = go.Scatter(x=time3[i].tolist(),
                     y=time3.index.tolist(),
                     mode='markers',
                     name=i,
                     marker=dict(color=color_dict[i],
                     size=4.5))
    trace_list.append(tra)

trace_line = []
for p in time3.index.tolist():
    trace_line.append(go.Scatter(x=time3.loc[p,:].tolist(),
                                 y=[p,p,p],
                                 mode='lines',
                                 name=p,
                                 line = dict(color='rgb(231,107,243)', width=2),
                                 connectgaps=True))


fig = go.Figure(data=trace_line+trace_list) 
fig.show()

上面是一个主图,给它加上副图,包含每个样本的性别,年龄段,居住地粗分类情况,都用不同的颜色来表示。

gender_color_dict = {'男': plotly.colors.sequential.Purpor[6], # dark purple
                     '女': plotly.colors.sequential.Burg[0]} # pink

residence_color_dict = dict(zip(['shenzhen','hubei_wuhan','hubei_others','others'],
                                plotly.colors.qualitative.T10))

age_color_dict = dict(zip(['age <=20','age: 21-55','age >= 55',None],
                          [plotly.colors.sequential.Agsunset[0],
                           plotly.colors.sequential.Agsunset[3],
                           plotly.colors.sequential.Agsunset[6],'black']))

def single_class_trace_fun(df_filter,cc):
    # cc: string in 'gender','age_range','residence_class','time3'
    cc_color= {'gender':gender_color_dict,
               'age_range':age_color_dict,
               'residence_class':residence_color_dict}
    single_class_trace = []
    for c in df_filter[cc].unique():
        single_class_df = df_filter.loc[df_filter[cc]==c,:]
        single_class_trace.append(go.Scatter(x=[cc]*len(single_class_df.index.tolist()),
                                             y=single_class_df.index.tolist(),
                                             mode='markers',
                                             name = c,
                                             marker=dict(color=cc_color[cc][c],
                                                         size=4.5)))
    return single_class_trace


# 把副图和主图合在一起
sub_traces = []
for i in ['gender','age_range','residence_class']:
    sub_traces += single_class_trace_fun(time3_sub,i)

fig_sub = make_subplots(rows=1, cols=2,column_widths=[0.2, 0.8],
                        shared_yaxes=True,
                        horizontal_spacing=0.01)
for i in sub_traces:
    fig_sub.add_trace(i,row=1,col=1)
for i in trace_line+trace_list:
    fig_sub.add_trace(i,row=1,col=2)

fig_sub.update_layout(height=1600, width=850)

fig_sub.show()

用dash实现上述概览,并添加各个例性别,年龄,居住地情况筛选

=_=这里用dash只是为自己练练手,写成一个app.py,然后在终端运行,Running on http://127.0.0.1:8050/...
在浏览器中访问http://127.0.0.1:8050/即可。
app.py代码在https://github.com/CS0000/shenzhen_400case_reports_overview

得到如文章开头所见的结果。

此外:

  1. dash官网及其教程:https://dash.plot.ly/
  2. 注意时间格式,以及时间空值NaT
  3. 有个全国的丁香园爬虫数据:
    是为人数记录,即每日累计病例,累计死亡等,不包含每个患者的情况。而深圳市政府公开数据包含除了总人数情况以外,还有每个病例的个案详情及每个行政区域的人数分布。
    丁香园爬虫及API (感恩造轮子的人):
    API获取:https://lab.isaaclin.cn/nCoV/ 这个接口为从丁香园上爬取的数据,包括全国和各省市的数据。原github项目为https://github.com/BlankerL/DXY-COVID-19-Crawler

请求并保存数据:

import requests
import picke
gd = requests.get('https://lab.isaaclin.cn/nCoV/api/area',params={'latest':0,'province':'广东省'})
gd.json() #看一下

with open('C://Users//XX//Desktop//gd_20200208.pickle','wb') as f:
    pickle.dump(gd,f) # 保存下来
  1. 还想做的:
  • 把各个病例的在外地活动时间段,出院时间加进来;
  • 给每个病例一个家族聚集的标签,同一个标签则为一家人(或一群朋友),
  • 做年龄的rangeslider,可以随意拖一个年龄区间,看这个年龄区间内的患者情况。
  • 优化筛选后的图,不是简单的扣掉不符合条件的病例。(go.Sactter(y=...))
  • ...再说吧
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容