Data-Science-For-Beginners 课 07 作业实战:用 Pandas 完成 COVID-19 疫情建模与论文共现分析
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
本篇实战指南以本仓库 2-Working-With-Data/07-python/assignment.md 的课后作业为骨架,面向已经学完 Python 与 Pandas 课程 的读者。你将基于课内两个挑战(COVID-19 疫情传播建模、COVID-19 学术论文文本分析)中已写好的代码继续深化,完成多国R_t对比、死亡/康复与感染的相关性、病程时长推断、死亡率时序变化,以及药物共现矩阵、热力图与正则提取剂量的实战任务。读完本文,你将掌握用 Pandas + Matplotlib 处理时间序列与半结构化文本的完整套路,并学会把课内示例代码改造成可复用的分析管线。
作业背景与完成标准
本作业要求你在课内两个挑战的基础上"接着写",而不是从零开始。挑战代码分别在 notebook-covidspread.ipynb(疫情建模)与 notebook-papers.ipynb(论文分析)中。评分标准(Rubric)分三档:
| 等级 | 要求 |
|---|---|
| 优秀(Exemplary) | 所有任务完成,有图形化展示与文字解释,且至少完成两个加分目标之一 |
| 合格(Adequate) | 完成超过 5 项任务,但未尝试加分目标,或结果不清晰 |
| 需改进(Needs Improvement) | 完成少于 5 项(但多于 3 项),且可视化无法说明问题 |
从评分标准可以看出:任务数量、可视化质量、解释说明缺一不可,加分目标是拉开差距的关键。作业原始数据说明与评分细则详见 translations/bg/2-Working-With-Data/07-python/assignment.md(保加利亚语译本,内容与英文原版一致)。
第一部分:COVID-19 疫情传播建模
准备数据与课内代码回顾
疫情数据来自约翰霍普金斯大学 CSSE 的时间序列数据集,本仓库在 data/COVID/ 下提供了三份快照(仓库内共 266 行、150 列,覆盖 188 个国家/地区,日期列从 2020-01-22 起):
- time_series_covid19_confirmed_global.csv:累计确诊
- time_series_covid19_recovered_global.csv:累计康复
- time_series_covid19_deaths_global.csv:累计死亡
notebook 中默认从网络读取,离线时可改用本地快照,只需替换base_url(见 notebook-covidspread.ipynb 第 3 个代码单元):
base_url = "../../data/COVID/" # 离线模式:改用仓库本地快照 # base_url = "https://raw.githubusercontent.com/..." # 在线模式(默认) infected = pd.read_csv(base_url + "time_series_covid19_confirmed_global.csv") recovered = pd.read_csv(base_url + "time_series_covid19_recovered_global.csv") deaths = pd.read_csv(base_url + "time_series_covid19_deaths_global.csv")课内已经完成的预处理链路包括:按Country/Region用groupby().sum()合并省份行 → 用drop(columns=['Lat','Long','Province/State'])去掉元数据列 → 用mkframe(country)把三份累计数据拼成按日期索引的 DataFrame → 用diff()求每日新增、rolling(7).mean()平滑周波动。此外,人口数据来自 data/UID_ISO_FIPS_LookUp_Table.csv,用于计算每百万人感染率pinfected。
R_t的核心公式(8 天滑动窗口)在 notebook 中实现为:
df['Rt'] = df['ninfected'].rolling(8).apply(lambda x: x[4:].sum()/x[:4].sum())其数学含义是:第t天的基本再生数近似等于窗口内后半段新增感染数之和除以前半段之和,即
R_t = (I_{t-7}+I_{t-6}+I_{t-5}+I_{t-4}) / (I_{t-3}+I_{t-2}+I_{t-1}+I_t)
其中I_t为第t天新增感染人数。R_t > 1表示疫情仍在扩散,R_t < 1表示传播在收敛。画图前需把除零产生的inf替换为NaN再用fillna(method='pad')前向填充,否则曲线会出现断口:
ax = df[df.index < "2020-05-01"]['Rt'].replace(np.inf, np.nan).fillna(method='pad').plot(figsize=(10,3)) ax.set_ylim([0,6]) # 限制纵轴以看清早期波动 ax.axhline(1, linestyle='--', color='red') # R_t=1 参考线 plt.show()任务一:多国 R_t 对比图
把课内的单国逻辑封装成函数,返回某国的R_t序列,再对 5~6 个国家统一绘图:
def compute_rt(country, window=8, half=4): df = mkframe(country) df['ninfected'] = df['infected'].diff() rt = df['ninfected'].rolling(window).apply( lambda x: x[half:].sum()/x[:half].sum(), raw=True) return rt.replace(np.inf, np.nan).fillna(method='pad') countries = ['US', 'United Kingdom', 'Italy', 'Brazil', 'India', 'Japan'] rt_series = {c: compute_rt(c) for c in countries} # 方案 A:单图叠加比较 ax = pd.DataFrame(rt_series).plot(figsize=(12, 4)) ax.set_ylabel('R_t'); ax.axhline(1, linestyle='--', color='red') # 方案 B:并排多子图(适合各国波峰错峰明显的场景) fig, axes = plt.subplots(2, 3, figsize=(12, 6), sharey=True) for ax, c in zip(axes.flat, countries): rt_series[c].plot(ax=ax, title=c) ax.axhline(1, linestyle='--', color='red', lw=0.8) plt.tight_layout()实现要点:raw=True让apply直接传入 ndarray 而非 Series,可显著加速滑动窗口计算;各国疫情阶段不同,叠加图适合看"谁先起峰、谁先收敛",子图适合逐国观察R_t是否持续高于 1。
任务二:死亡数、康复数与感染数的相关性
用mkframe拿到累计值后,直接调用 Pandas 内置的相关系数,并结合散点图观察滞后关系:
df = mkframe('US') corr = df[['infected', 'recovered', 'deaths']].corr() print(corr) # 平滑后更能反映趋势相关性 smooth = df[['infected', 'recovered', 'deaths']].rolling(7).mean() smooth.plot.scatter(x='infected', y='deaths')可以预期:感染数与死亡数高度正相关,但死亡往往滞后于感染若干天(存在病程时滞)。一个值得探索的分析是:计算"每日新增死亡"与"每日新增感染"在不同时间滞后 k 天下的相关系数,找出相关系数最大时的 k,这为任务三的病程推断提供定量依据:
new_inf = df['infected'].diff().rolling(7).mean() new_deaths = df['deaths'].diff().rolling(7).mean() lags = {} for k in range(0, 40): lags[k] = new_deaths.corr(new_inf.shift(k)) best_k = max(lags, key=lags.get)任务三:推断典型病程时长
思路是"以视觉方式关联感染率与死亡率曲线并寻找异常"。把两条曲线放到同一张图上,观察死亡曲线相对感染曲线的时间偏移:
df['ninfected'] = df['infected'].diff().rolling(7).mean() df['ndeaths'] = df['deaths'].diff().rolling(7).mean() # 归一化到 [0,1] 便于叠加比较形状 ax = (df['ninfected']/df['ninfected'].max()).plot(label='new infected (norm)') (df['ndeaths']/df['ndeaths'].max()).shift(0).plot(ax=ax, label='new deaths (norm)') ax.legend()实际操作上可以遍历滞后天数 k,把ndeaths.shift(k)与ninfected叠加,找到两条曲线"峰对峰"最吻合的 k,即为粗略病程时长。注意:不同国家因检测策略、死亡报告口径不同,推断出的滞后可能不一致,这正是作业要求"多看几个国家"的原因——异常点(如某国死亡率曲线突然偏离)往往对应数据质量或政策干预(如封城、检测量变化)事件。
任务四:死亡率及其随时间的变化
基本定义:fatality rate = deaths / infected * 100。作业的提示非常关键:要考虑病程天数,先平移一条时间序列再做计算。原因在于当天确诊的人不会当天死亡,直接用同日累计值会低估早期真实病死率:
df = mkframe('US') df['fatality'] = df['deaths'] / df['infected'] * 100 df['fatality'].plot() # 朴素计算,早期明显偏低 # 平移改进:假设病程 D 天,将感染序列后移 D 天再相除 D = 14 # 可由任务三得到的滞后天数替换 df['fatality_shifted'] = df['deaths'].shift(-D) / df['infected'] * 100 df[['fatality', 'fatality_shifted']].plot()同时建议平滑(rolling(7).mean())去除报告导致的周波动,并观察死亡率随时间是否趋稳——这能反映医疗资源挤兑、治疗手段改进或检测范围扩大等动态因素。
第二部分:COVID-19 论文分析
数据集说明与课内代码回顾
本挑战使用 CORD-19 论文数据集(仓库不随附,需自行下载metadata.csv,大小约 1 GB)。课内 notebook notebook-papers.ipynb 已完成如下分析链路:
- 读取
metadata.csv,把publish_time转成datetime并画直方图; - 手工维护药物清单
medications(hydroxychloroquine、chloroquine、tocilizumab、remdesivir、azithromycin、lopinavir、ritonavir、dexamethasone、heparin、favipiravir、methylprednisolone)与诊断清单diagnosis(covid、sars、pneumonia、infection、diabetes、coronavirus、death); - 用
df[m] = df['abstract'].apply(lambda x: str(x).lower().count(' '+m))逐词计数,注意词首加空格,避免chloroquine被hydroxychloroquine内的子串污染; - 按年-月分组
groupby([index.year, index.month]).sum()得到治疗策略月度趋势; - 用
np.zeros((len(medications), len(diagnosis)))构建"药物×诊断"共现矩阵,逐篇摘要累加; - 用
plt.imshow(..., cmap='hot')画热力图,并用 Plotly 的go.Sankey画桑基图(notebook 中封装为sankey(cat1, cat2, m, treshold=0, h1=[], h2=[])函数)。
任务一:构建药物共现矩阵
把课内"药物×诊断"的代码改造成"药物×药物",遍历每篇摘要,只要某两种药物在同一摘要中出现就计数一次:
meds = medications # 沿用课内 11 种药物清单 coocc = np.zeros((len(meds), len(meds)), dtype=int) for a in df['abstract']: x = str(a).lower() present = [m for m in meds if ' '+m in x] for i in range(len(present)): for j in range(i+1, len(present)): coocc[meds.index(present[i]), meds.index(present[j])] += 1 coocc[meds.index(present[j]), meds.index(present[i])] += 1 # 对称 cooc_df = pd.DataFrame(coocc, index=meds, columns=meds)出于效率考虑,也可以先构造布尔矩阵再与自身转置做矩阵乘法:present = pd.DataFrame({m: df['abstract'].str.contains(' '+m, case=False) for m in meds}),然后coocc = present.T @ present(注意此时对角线为各药物总出现次数)。观察重点:哪些药物常在同一篇论文里成对出现(例如 chloroquine 与 azithromycin、lopinavir 与 ritonavir 常作为联合用药方案被共同研究)。
任务二:热力图可视化共现矩阵
用 Matplotlib 画出共现矩阵热力图:
fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(coocc, interpolation='nearest', cmap='hot') ax.set_xticks(range(len(meds))); ax.set_xticklabels(meds, rotation=90) ax.set_yticks(range(len(meds))); ax.set_yticklabels(meds) plt.colorbar(im, ax=ax) plt.show()热力图中亮色格(高共现次数)即联合用药研究热点。若矩阵数值跨度大,可先取np.log1p(coocc)再画,压低少数极高值对色标的压缩效应;也可叠加数值标注ax.text(j, i, coocc[i,j])提升可读性。注意课内药物×诊断热力图样式(covidtreat.png 为治疗策略堆叠面积图,热力图绘制逻辑见 notebook 第 26 个代码单元)可直接迁移复用。
加分目标一:chord 弦图可视化共现
作业推荐的第三方库是chord(PyPI 包名)。用法示意:
from chord import Chord # 传入对称矩阵与标签列表(矩阵元素为整数频次) Chord(coocc.tolist(), meds).to_html() # 生成可交互 HTML若coocc数值过大,可先二值化或按阈值截断,只保留共现次数高于阈值的药物对,否则弦图会过于拥挤。按任务说明,此目标与另一加分目标完成其一即可达到"优秀"档。
加分目标二:用正则表达式提取药物剂量
从"take 400mg of chloroquine daily"这类句子中抽取剂量(如400mg),并用 DataFrame 汇总"每种药物出现过的剂量及次数"。关键技巧:在药物名周围限定上下文窗口,只统计与药物名文本距离很近的数值:
import re from collections import defaultdict def extract_doses(abstracts, medicine, window=30): doses = defaultdict(int) pat = re.compile(r'(\d+(?:\.\d+)?\s*(?:mg|g|mcg|microgram|gram|milligram))', re.I) for a in abstracts: a = str(a).lower() for mm in re.finditer(re.escape(medicine), a): start = max(0, mm.start() - window) end = min(len(a), mm.end() + window) ctx = a[start:end] for dm in pat.finditer(ctx): doses[dm.group(1).lower()] += 1 return doses rows = [] for med in medications: for dose, cnt in extract_doses(df['abstract'], med).items(): rows.append({'medication': med, 'dose': dose, 'count': cnt}) dose_df = pd.DataFrame(rows).sort_values(['medication', 'count'], ascending=[True, False])要点:re.escape(medicine)避免药物名中的特殊字符被当作正则元字符;窗口大小(此处 30 字符)决定"文本邻近"的判定范围;剂量模式\d+(?:\.\d+)?\s*(?:mg|g|...)覆盖整数/小数与常见单位。可进一步做单位归一化(如把0.4 g与400 mg视为同一剂量),并观察同一药物不同剂量随时间/国别的分布差异。
仓库内可深入阅读的资料
- 课程正文:2-Working-With-Data/07-python/README.md(Series/DataFrame 核心操作、
apply/groupby/rolling用法) - 疫情建模完整代码:notebook-covidspread.ipynb
- 论文分析完整代码:notebook-papers.ipynb
- 疫情本地数据:data/COVID/(三份累计时间序列 CSV)
- 人口数据:data/UID_ISO_FIPS_LookUp_Table.csv
- 作业英文原版:2-Working-With-Data/07-python/assignment.md
- 疫情趋势图:covidspread.png;治疗策略趋势图:covidtreat.png
- 课程配套 R 语言版本:2-Working-With-Data/07-python/R/(供对照学习)
完成建议
按"先复现、后改造、再独立"的顺序推进:先在两个 notebook 中逐单元运行确认基线输出,再基于本文给出的函数模板完成四项建模任务与两项论文分析任务,最后任选一个加分目标冲刺"优秀"档。可视化务必配上文字结论(例如"某国R_t在何时跌破 1""某两种药物共现次数显著高于其他组合"),这既是评分标准(Exemplary 要求"graphically illustrated and explained")的要求,也是数据科学报告的基本素养。
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考