Data-Science-For-Beginners 第 7 课作业实战:用 Python 完成 COVID-19 疫情传播建模与论文药物共现分析
2026/9/20 7:55:13 网站建设 项目流程

Data-Science-For-Beginners 第 7 课作业实战:用 Python 完成 COVID-19 疫情传播建模与论文药物共现分析

【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners

本篇文章完整拆解《Data-Science-For-Beginners》课程第 7 课(Python 与 Pandas 数据处理)的课后作业,该作业要求读者在两个官方挑战 Notebook——notebook-covidspread.ipynb(疫情传播建模)与 notebook-papers.ipynb(论文文本分析)——的基础上继续深化,完成 6 项核心任务与 2 项拓展目标。读完本文,你将掌握用 Pandas 处理时间序列(滚动窗口、差分、平移对齐)、计算再生数 R_t、构建药物共现矩阵并用 heatmap 可视化、以及用正则表达式从医学摘要中抽取药物剂量的完整实战方案。作业原文见 assignment.md(英文) 与 希腊语译文版(即本任务依据的关联文档)。

作业背景:在挑战代码之上做深度数据分析

课程第 7 课的核心主张是:数据库查询虽然高效,但遇到“需要洞察”的场景——分布、相关性、趋势、文本中的隐含信息——用 Python 直接操作数据往往更灵活。课内配套的 README.md 介绍了 Series、DataFrame、groupbyapplyresample等 Pandas 核心概念,而作业则要求你把它们真正用起来。

作业明确分成两个部分:

  1. COVID-19 疫情传播建模(COVID-19 Spread Modelling):基于约翰霍普金斯大学 CSSE 提供的全球疫情时间序列,完成 4 项任务;
  2. COVID-19 论文分析(COVID-19 Papers Analysis):基于 CORD-19 论文数据集(含摘要的metadata.csv),完成 2 项任务 + 2 项拓展目标。

作业原文给出的完整任务清单如下:

  • 第一部分
    • 为 5~6 个不同国家绘制R图,画在同一张图上对比,或用多个图并排展示;
    • 观察死亡数、康复数与感染数的相关性;
    • 通过可视化关联感染率与死亡率、寻找异常点,推断典型病程持续多久(可能需要对比不同国家);
    • 计算致死率(fatality rate)及其随时间的变化(提示:可考虑按病程天数平移某条时间序列后再计算)。
  • 第二部分
    • 构建不同药物的共现矩阵,观察哪些药物经常在同一篇摘要中被共同提及(可参考“药物×诊断”共现矩阵的构建代码进行修改);
    • 用 heatmap 可视化该矩阵;
    • 拓展目标一:用 chord diagram(弦图)可视化药物共现,官方提示可参考chord库;
    • 拓展目标二:用正则表达式抽取不同药物的剂量(例如从take 400mg of chloroquine daily中抽取400mg),并构建展示“不同药物对应不同剂量”的 DataFrame(提示:考虑药物名附近文本距离内的数值)。

环境准备与数据获取

两个 Notebook 都基于 Python 数据科学生态,核心依赖与导入方式在 notebook-covidspread.ipynb 中有明确示例:

import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams["figure.figsize"] = (10, 3) # 放大默认画布,便于观察趋势

疫情数据的两种获取方式

Notebook 默认从约翰霍普金斯大学 CSSE 的 GitHub 仓库在线读取 CSV;如果网络不可用,仓库本地data/目录下已内置了一份副本,可直接切换数据源:

base_url = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/" # 在线加载 # base_url = "data/COVID/" # 本地加载:仓库 data 目录下的副本 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") countries = pd.read_csv(base_url + "../UID_ISO_FIPS_LookUp_Table.csv")

本地副本对应的文件为 time_series_covid19_confirmed_global.csv、time_series_covid19_deaths_global.csv、time_series_covid19_recovered_global.csv,人口数据为 UID_ISO_FIPS_LookUp_Table.csv。从表头可以看到数据的宽表结构:每一行是一个国家/省份,日期列(1/22/201/23/20……)依次排开。

论文数据的获取

CORD-19 数据集未包含在仓库内,需要自行下载metadata.csv(约 1GB,Notebook 提示下载可能耗时约 5 分钟)。官方 Notebook 给出的加载方式是:

df = pd.read_csv("https://datascience4beginners.blob.core.windows.net/cord/metadata.csv.zip", compression='zip') # df = pd.read_csv("metadata.csv") # 若已下载本地文件

疫情数据的预处理(第一部分的地基)

Notebook 已经演示了疫情数据从“宽表”到“可分析 DataFrame”的完整预处理链路,作业第一部分的所有任务都建立在这套代码之上:

# 1) 按国家聚合,把省份细分的行累加为整国数据 infected = infected.groupby('Country/Region').sum() recovered = recovered.groupby('Country/Region').sum() deaths = deaths.groupby('Country/Region').sum() # 2) 去掉非日期的元数据列 infected.drop(columns=['Lat', 'Long', 'Province/State'], inplace=True) recovered.drop(columns=['Lat', 'Long', 'Province/State'], inplace=True) deaths.drop(columns=['Lat', 'Long', 'Province/State'], inplace=True) # 3) 封装“按国家构建时间序列帧”的函数,日期列转 datetime def mkframe(country): df = pd.DataFrame({'infected': infected.loc[country], 'recovered': recovered.loc[country], 'deaths': deaths.loc[country]}) df.index = pd.to_datetime(df.index) return df df = mkframe('US')

关键点在于:groupby('Country/Region').sum()让 DataFrame 以国家为索引,之后用.loc[country]即可取到单国数据;diff()用于把累计感染数转换为每日新增感染数,rolling(window=7).mean()则用于抹平数据中的周度波动(报告周期导致的锯齿):

df['ninfected'] = df['infected'].diff() # 每日新增 df['ninfav'] = df['ninfected'].rolling(7).mean() # 7 日均线,消除周度波动

如果要在不同国家之间比较疫情规模,Notebook 还给出了人口归一化的做法——从 UID_ISO_FIPS_LookUp_Table.csv 中按“国家 + 无省份细分”取出人口数,换算成感染占比:

pop = countries[(countries['Country_Region'] == 'US') & countries['Province_State'].isna()]['Population'].iloc[0] df['pinfected'] = df['infected'] * 100 / pop

第一部分:COVID-19 疫情传播建模

这一部分的 R_t 计算是 Notebook 的核心铺垫。Notebook 用 8 天滚动窗口估计随时间变化的再生数 R_t:

df['Rt'] = df['ninfected'].rolling(8).apply(lambda x: x[4:].sum() / x[:4].sum()) df['Rt'].plot()

实现细节说明:8 天窗口内的 8 个值为从旧到新的每日新增感染数,x[:4]是窗口前 4 天之和,x[4:]是后 4 天之和。值得注意,Notebook 中公式文本写作 R_t=(I_{t-7}+…+I_{t-4})/(I_{t-3}+…+I_t),与代码方向互为倒数;两种写法语义上都刻画“当前新增相对前几天”的放大倍数,实际计算以代码为准。当 R_t>1 时说明疫情仍在扩张,R_t<1 则意味着传播在收敛。由于 R_t 是比率,天然不受国家人口规模影响,可以直接跨国家比较。

绘制前建议先清理inf/NaN——窗口不足 8 天时结果为 NaN,除零会产生inf。Notebook 的标准清理手法是:

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]) # 只看 0~6 区间,便于观察 ax.axhline(1, linestyle='--', color='red') # 参考线 R_t=1 plt.show()

兼容性提示:较新版本 Pandas 已弃用fillna(method='pad'),可等价替换为.fillna(method='ffill')或直接.ffill()

任务 1:为 5~6 个国家绘制 R_t 对比图

把上面的 R_t 计算封装成函数,然后对多国循环取值、绘制在同一坐标系内即可完成对比:

def compute_rt(country): d = mkframe(country) d['ninfected'] = d['infected'].diff() rt = d['ninfected'].rolling(8).apply(lambda x: x[4:].sum() / x[:4].sum()) return rt.replace(np.inf, np.nan).ffill() countries_list = ['US', 'Brazil', 'India', 'Russia', 'UK', 'Germany'] plt.figure(figsize=(12, 6)) for c in countries_list: compute_rt(c).plot(label=c) plt.ylim(0, 6) plt.axhline(1, linestyle='--', color='red') plt.legend() plt.show()

也可以用plt.subplots(2, 3)并排画出多个子图,逐个观察各国 R_t 的峰谷形态。R_t 高于 1 的时段越长,说明该国疫情扩张持续越久。

任务 2:死亡数、康复数与感染数的相关性

Notebook 已经构造了同时包含infectedrecovereddeaths三列的时间序列帧,直接计算相关系数矩阵即可:

df = mkframe('US') print(df[['infected', 'recovered', 'deaths']].corr())

更直观的做法是用散点图看两两关系(df.plot.scatter(x='infected', y='deaths'))。由于康复与死亡都滞后于感染,先验上它们与感染数应呈强正相关;如果你发现相关性偏弱,多半是时间滞后导致的,可以顺带对比不同滞后天数下的相关系数——这正好为任务 3 和任务 4 埋下伏笔。

任务 3:推断典型病程

任务 3 的思路是“用平移对齐两条时间序列”:死亡/康复曲线整体滞后于感染曲线,滞后天数约等于从感染到结局的平均病程。Notebook 的作业提示也明确建议“按病程天数平移一条时间序列后再计算”。可行的做法是扫描不同的平移天数 d,计算“感染曲线”与“前移 d 天的死亡曲线”的相关系数,取相关系数最大的 d 作为病程估计:

def best_lag(df, col='deaths', max_lag=40): res = [] for d in range(0, max_lag): corr = df[col].shift(-d).corr(df['infected']) res.append((d, corr)) return max(res, key=lambda t: t[1]) print(best_lag(mkframe('US')))

这里的逻辑是:今天的死亡数对应约 d 天前的感染数,因此把死亡序列向前(shift(-d))移动 d 天后,两条曲线应最大程度重合。不同国家的报告节奏、医疗条件不同,最佳滞后天数会有差异,任务要求你“多对比几个国家并寻找异常点”——比如数据上报集中导致的陡峰、或者疫情暴发早期的高致死率时段,都会在曲线对位上留下痕迹。

任务 4:致死率及其随时间的变化

致死率最基本的定义是死亡数除以确诊数:

df['fatality'] = df['deaths'] / df['infected'] df['fatality'].plot()

但正如作业提示所说,这个指标存在时间错位问题:分母用的是“今天”的累计确诊,而分子对应的是“若干天前”确诊的那批病人的结局,因此早期病例尚未结束病程时,致死率会被系统性低估。更严谨的做法是先按任务 3 得到的滞后天数平移:

lag = best_lag(mkframe('US'))[0] df['fatality_lagged'] = df['deaths'] / df['infected'].shift(lag) df['fatality_lagged'].plot()

把两种定义画在同一张图上对比,通常能看到:平移后的致死率更平稳,且能更早反映真实水平的变化趋势。这正是作业想要你体会的“数据对齐”思想。

第二部分:COVID-19 论文分析

这一部分处理的是非结构化文本数据,核心方法论是“先从摘要中抽取结构化计数,再做矩阵运算与可视化”。Notebook 的做法是手动维护两份实体清单,然后用“带前导空格的子串匹配”统计每个词在摘要中出现的次数:

medications = ['hydroxychloroquine', 'chloroquine', 'tocilizumab', 'remdesivir', 'azithromycin', 'lopinavir', 'ritonavir', 'dexamethasone', 'heparin', 'favipiravir', 'methylprednisolone'] for m in medications: df[m] = df['abstract'].apply(lambda x: str(x).lower().count(' ' + m))

易错点(Notebook 专门提示):匹配时一定要在词首加空格(' ' + m),否则chloroquine会被包含它的hydroxychloroquine错误命中;同时用str(x)强制转字符串以避免缺失值报错。

有了逐篇摘要的计数列,就能按时间聚合观察治疗策略的演变(月度groupby+ 求和),Notebook 还演示了用plot.area()堆叠面积图和归一化百分比图来展示不同药物的相对热度变化。下图是论文分析部分的典型输出——不同药物随发表时间变化的堆叠面积图:

任务 1:构建药物共现矩阵

共现矩阵的本质是一个二维计数数组:对每一篇摘要,凡是同时出现两种药物,就在对应格子上加 1。Notebook 里“药物×诊断”矩阵的构建代码是:

m = np.zeros((len(medications), len(diagnosis))) for a in df['abstract']: x = str(a).lower() for i, d in enumerate(diagnosis): if ' ' + d in x: for j, me in enumerate(medications): if ' ' + me in x: m[j, i] += 1

作业要求你照此模式改造为“药物×药物”矩阵,从而回答“哪些药物经常被一起研究”:

n = len(medications) m = np.zeros((n, n)) for a in df['abstract']: x = str(a).lower() present = [i for i, med in enumerate(medications) if ' ' + med in x] for i in present: for j in present: m[i, j] += 1

注意对角线上的值表示该药物被提及的总篇数(同一药物与自己共现),分析共现强度时通常只看非对角元素;如需消除规模差异,可进一步把计数归一化为 Jaccard 相似度或除以对角线得到条件概率。

任务 2:用 heatmap 可视化共现矩阵

Notebook 对“药物×诊断”矩阵的 heatmap 画法可直接复用,只需把行标签与列标签都换成药物名:

plt.figure(figsize=(10, 8)) plt.imshow(m, interpolation='nearest', cmap='hot') ax = plt.gca() ax.set_yticks(range(len(medications))) ax.set_yticklabels(medications) ax.set_xticks(range(len(medications))) ax.set_xticklabels(medications, rotation=90) plt.colorbar() plt.show()

矩阵中颜色越亮的格子对应的药物对共现越频繁。结合任务 1 的计数,你应当能识别出类似“羟氯喹 + 阿奇霉素”“洛匹那韦 + 利托那韦”这类常被组合研究的方案。

拓展目标一:用 chord diagram 可视化药物共现

作业给出的拓展思路是使用chord库绘制弦图,把共现矩阵变成更美观的关系图:

# pip install chord from chord import Chord # 构造 chord 库需要的矩阵与标签(API 细节以该库文档为准) Chord(m, medications).show()

弦图把每种药物作为圆周上的一个节点,节点之间的弦越粗表示共现越强,适合一眼看清“谁和谁绑定出现”。如果矩阵不对称,通常需要先对称化(如取m + m.T)再传给弦图库。

拓展目标二:用正则表达式抽取药物剂量

最后一项拓展目标是把“剂量”从自然语言中抽出来,例如从take 400mg of chloroquine daily中抽到400mg。作业提示的关键约束是:只取药物名附近文本距离内的数值,避免抽到与用药无关的数字。实现思路是“先定位药物名,再在它前后开一个窗口,在窗口内用正则匹配剂量单位”:

import re dosage_pattern = re.compile(r'(\d+(?:\.\d+)?)\s*(mg|mcg|µg|g|iu|ml)\b', re.IGNORECASE) def extract_dosages(abstract, meds, window=50): rows = [] x = str(abstract).lower() for med in meds: for match in re.finditer(r'\b' + re.escape(med) + r'\b', x): start = max(0, match.start() - window) end = min(len(x), match.end() + window) context = x[start:end] for dose, unit in dosage_pattern.findall(context): rows.append((med, f'{dose}{unit.lower()}')) return rows records = [] for a in df['abstract']: records.extend(extract_dosages(a, medications)) dosage_df = pd.DataFrame(records, columns=['medication', 'dosage']) dosage_df.value_counts().head(20)

window参数控制“药物名前后多少字符内才算关联数值”,\b词边界避免400mg内部或词缀造成的误匹配。最终dosage_df就是一个“药物 × 剂量”的 DataFrame,可以通过value_counts统计最常见剂量,或按药物分组查看剂量分布。

评分标准(Rubric)

作业原文附带了明确的评分表,可以作为自检清单:

等级要求
Excellent(优秀)所有任务全部完成,有图形化展示与文字解释,且至少完成两个拓展目标中的一个
Adequate(合格)完成任务超过 5 项,未尝试拓展目标,或结果不够清晰
Needs Improvement(待改进)完成任务少于 5 项(但多于 3 项),且可视化未能有效支撑结论

按此标准规划工作量:第一部分 4 项 + 第二部分 2 项为必做底线;想拿“优秀”必须再攻坚任一拓展目标(弦图或剂量抽取)。评分同时强调“可视化必须能证明观点”——不是画了图就算完成,图中必须能读出任务要求回答的问题。

总结与延伸

这份作业完整覆盖了数据科学实战的典型链路:宽表整形 → 时间序列特征工程(差分、滚动窗口、平移对齐)→ 统计度量(相关性、比率)→ 文本结构化抽取 → 矩阵构建 → 可视化验证。你既可以用 notebook-covidspread.ipynb 的疫情数据练手 Pandas 时间序列操作,也可以在 notebook-papers.ipynb 的论文文本上体会“非结构化数据如何变成结构化洞察”。Pandas 基础概念(Series、DataFrame、groupbyapplyresample)可回看 notebook.ipynb 与课程 README 巩固。

完成作业后,还可以继续沿着 Notebook 的参考文献方向探索:例如把“手动词表”换成真正的 NLP 实体抽取(如 NLTK 或云端文本分析服务),或对 R_t 估计引入更精细的滑动 SIR 模型——这些进阶方向都能让同一份数据产生更深的结论。

【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询