笔记内容:
- 数据获取
- 用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
得到如文章开头所见的结果。
此外:
- dash官网及其教程:https://dash.plot.ly/
- 注意时间格式,以及时间空值
NaT
- 有个全国的丁香园爬虫数据:
是为人数记录,即每日累计病例,累计死亡等,不包含每个患者的情况。而深圳市政府公开数据包含除了总人数情况以外,还有每个病例的个案详情及每个行政区域的人数分布。
丁香园爬虫及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) # 保存下来
- 还想做的:
- 把各个病例的在外地活动时间段,出院时间加进来;
- 给每个病例一个家族聚集的标签,同一个标签则为一家人(或一群朋友),
- 做年龄的rangeslider,可以随意拖一个年龄区间,看这个年龄区间内的患者情况。
- 优化筛选后的图,不是简单的扣掉不符合条件的病例。(go.Sactter(y=...))
- ...再说吧