Data-Science-For-Beginners 实战作业解析:用 Pandas 完成 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 课《Working with Data: Python and the Pandas Library》的结课作业(对应仓库 作业原文)展开,系统讲解两大实战任务:COVID-19 疫情传播建模(多国有效再生数 Rt曲线对比、感染/死亡/康复相关性分析、病死率演化计算)与COVID-19 科学论文文本挖掘(药物共现矩阵构建、热力图可视化、弦图与剂量提取两个延伸目标)。读完本文,你将掌握用 Pandas/NumPy/Matplotlib 处理时间序列与自由文本数据、实现滑动窗口统计、共现计数与正则提取的完整可复用方案,并能直接运行到仓库提供的 notebook 与本地数据上完成作业验收。
作业全景:两大实战任务与验收标准
作业要求在不脱离课程两个挑战(notebook-covidspread.ipynb 与 notebook-papers.ipynb)现有代码的基础上做深度延展,共分两大部分、八个小项:
- 第一部分 · COVID-19 传播建模(4 项任务):多国 Rt曲线对比、死亡/康复与感染相关性、病程时长推断、病死率时间演化;
- 第二部分 · COVID-19 论文分析(2 项核心任务 + 2 项延伸目标):药物共现矩阵与热力图、弦图可视化(延伸)、正则提取药物剂量(延伸)。
官方评分标准(Rubric)分为三档:Exemplary(示范级)——所有任务完成并有图形化说明与解释,且至少完成一个延伸目标;Adequate(合格级)——完成 5 项以上任务,未尝试延伸目标或结果不清晰;Needs Improvement(待改进)——完成少于 5 项(但多于 3 项)任务,且可视化无法帮助论证观点。这提示我们:每一项任务不仅要"跑出结果",更要"画成图、讲清楚"。
第一部分:COVID-19 传播建模
1.1 数据源与预处理
课程 notebook 默认从约翰霍普金斯大学 CSSE 的在线仓库读取时间序列数据;仓库同时在 data/COVID/ 目录下提供了三份离线副本(推荐优先使用,避免网络不可用):
- time_series_covid19_confirmed_global.csv(确诊)
- time_series_covid19_recovered_global.csv(康复)
- time_series_covid19_deaths_global.csv(死亡)
三份文件结构一致:前四列为Province/State, Country/Region, Lat, Long元数据,其余每一列对应一个日期(如1/22/20),值为该日期的累计数。人口数据来自 data/UID_ISO_FIPS_LookUp_Table.csv 的Population字段,用于计算感染占比。
做分析前需要完成四步预处理(对应 notebook 第 9~19 号单元):
- 按国家聚合:用
groupby('Country/Region').sum()把中国、澳大利亚等按省份拆分的行合并为整国数据; - 丢弃元数据列:
drop(columns=['Lat','Long','Province/State']),保留纯日期序列; - 构造时间索引:用
pd.to_datetime把字符串列名转为DatetimeIndex; - 封装为国家视图函数(notebook 的
mkframe):
import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams["figure.figsize"] = (10, 3) 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在此基础上派生三个关键列:ninfected = infected.diff()(每日新增)、ninfav = ninfected.rolling(window=7).mean()(7 日滑动平均,消除周报波动)、pinfected = infected * 100 / pop(感染占比,用于跨国公平对比)。
1.2 任务一:多国 Rt曲线对比
课程 notebook 给出了 Rt(有效再生数)的滑动窗口估计法:取 8 天窗口,用前 4 天新增感染之和除以后 4 天之和,公式为
Rt= (It-7+ It-6+ It-5+ It-4) / (It-3+ It-2+ It-1+ It)
其 Pandas 实现为rolling(8).apply(lambda x: x[4:].sum() / x[:4].sum())。当 Rt> 1 时疫情趋向扩散,因此参考线 1 是关键判据。注意数据里会出现inf(除以 0)与NaN(窗口不足),作图前须清理:
df['Rt'] = df['ninfected'].rolling(8).apply(lambda x: x[4:].sum() / x[:4].sum()) Rt = df['Rt'].replace(np.inf, np.nan).fillna(method='pad') ax = Rt[df.index < "2020-05-01"].plot() ax.set_ylim([0, 6]) # 只看 R<0..6 区间,突出差异 ax.axhline(1, linestyle='--', color='red') # R=1 为疫情消长分界线 plt.show()作业要求的提升点是多国对比。参考实现是把 5~6 个国家的 Rt曲线画在同一坐标系(共用axhline(1)参考线便于横向比较),或使用plt.subplots并排多图:
countries = ['US', 'Italy', 'Spain', 'Germany', 'Brazil', 'India'] fig, ax = plt.subplots(1, len(countries), figsize=(18, 3)) for i, c in enumerate(countries): r = compute_rt(mkframe(c)) # 复用上面的 Rt 计算 r[r.index < "2020-06-01"].plot(ax=ax[i], title=c, ylim=(0, 6)) ax[i].axhline(1, linestyle='--', color='red') plt.tight_layout(); plt.show()输出后应能直观读出:不同国家 Rt首次跌破 1 的时间点不同,反映各国防控节奏与强度的差异。这是整份作业中最能体现"可视化论证"价值的任务。
1.3 任务二:死亡/康复与感染病例的相关性
notebook 已演示用df[['infected','recovered','deaths']].plot()观察三者同轴趋势。作业要求进一步量化相关性,可从两个层面展开:
- 数值相关:用 Pandas 的
corr()计算列间 Pearson 相关系数,并检验"死亡滞后于感染若干天后相关系数最大"这一假设; - 可视化:将三者归一化(除以各自峰值或取对数)后叠加绘图,观察滞后效应。死亡与感染之间通常存在稳定的时间滞后(约为一个病程周期),这正是任务三的线索。
df = mkframe('US') df[['infected', 'recovered', 'deaths']].plot() plt.show() # 定量相关性(对累计数或每日新增均可) print(df[['infected', 'recovered', 'deaths']].corr()) # 平移 14 天后死亡与感染的相关性(示意,需在任务三确定最优滞后) lag = 14 print(np.corrcoef(df['ninfected'][lag:], df['deaths'].diff()[lag:])[0, 1])1.4 任务三:通过视觉相关推断典型病程时长
"病程多久"是个开放式推断题。思路是:如果从感染到死亡平均经历 D 天,那么把死亡曲线向左平移 D 天(或把感染曲线向右平移 D 天)后,两条曲线应出现最明显的形态吻合(峰值对齐、拐点对齐)。作业提示可多试几个国家寻找证据,因为各国数据质量与报告口径不同,可能只有部分国家能清晰呈现滞后。
df = mkframe('US') deaths_daily = df['deaths'].diff().rolling(7).mean() for lag in [7, 14, 21, 28]: plt.plot(df['ninfav'].index[:-lag] if lag else df['ninfav'].index, df['ninfav'].values[:-lag] if lag else df['ninfav'].values, label=f'infected (shift -{lag}d)' if lag else 'infected') plt.plot(deaths_daily.index, deaths_daily.values * 20, label='deaths ×20') # 缩放便于同轴比较 plt.legend(); plt.show()更严谨的做法是穷举滞后天数,对每个lag计算感染新增与死亡新增的相关系数,取相关系数最大(或 RMSE 最小)的lag作为病程估计。这个"平移 + 相关"的组合拳也是任务四的直接前置。
1.5 任务四:病死率及其随时间的变化
病死率(fatality rate)的朴素定义是deaths / infected,但它天然存在系统性低估:今天死亡的人对应的是若干天前的感染者,直接用同日累计数计算会偏低且随时间失真。作业提示的解法正是用任务三推断出的病程天数 D 做时间序列位移:把死亡时间序列相对于感染序列平移 D 天后再计算比率。
D = 14 # 由任务三推断的病程天数(示例值,需自行论证) # 朴素病死率 naive_cfr = df['deaths'] / df['infected'] # 校正病死率:死亡(第 t 天) 对应 感染(第 t-D 天) adjusted_cfr = df['deaths'].iloc[D:] / df['infected'].iloc[:-D].values plt.plot(naive_cfr.index, naive_cfr.values, label='naive CFR') plt.plot(adjusted_cfr.index, adjusted_cfr.values, label=f'CFR with {D}-day lag') plt.legend(); plt.show()分析要点:绘制校正后的病死率随时间的曲线,观察其是否随检测能力、医疗资源、病毒株变化而演化;对多个国家重复该流程,可将病死率曲线的形态差异与各国疫情阶段对应起来。完成时应说明 D 的取值依据(来自任务三),形成闭环论证。
第二部分:COVID-19 论文分析
2.1 数据获取与文本特征构造
本部分处理 CORD-19 科学论文数据集(metadata.csv,含论文元数据与摘要)。注意:仓库不提供该数据集的副本,需按课程 README 指引自行下载(数据量约 1GB,notebook 提示在线加载可能耗时约 5 分钟)。加载后先做两件事:把publish_time转为datetime并绘制直方图观察发表时间分布;然后对摘要做关键词计数。
notebook 采用"预定义词表 + 子串计数"的朴素文本挖掘:分别维护药物清单与诊断清单,遍历摘要统计每个词的出现次数,并利用 Pandas 向量化apply生成新列:
medications = ['hydroxychloroquine', 'chloroquine', 'tocilizumab', 'remdesivir', 'azithromycin', 'lopinavir', 'ritonavir', 'dexamethasone', 'heparin', 'favipiravir', 'methylprednisolone'] diagnosis = ['covid', 'sars', 'pneumonia', 'infection', 'diabetes', 'coronavirus', 'death'] for m in medications: df[m] = df['abstract'].apply(lambda x: str(x).lower().count(' ' + m)) for d in diagnosis: df[d] = df['abstract'].apply(lambda x: str(x).lower().count(' ' + d))这里有一个极易踩的坑(notebook 第 9 号单元专门强调):匹配子串时必须在词首加空格,否则chloroquine会误命中hydroxychloroquine的内部子串;同时必须强制str(x)转换以规避缺失值报错。词表还可按月聚合(groupby([index.year, index.month]).sum())绘制治疗策略随时间的演化趋势。
2.2 任务一:构建药物共现矩阵
作业要求把课程中的"药物-诊断共现矩阵"改造成**"药物-药物共现矩阵"**:统计任意两种药物在同一篇摘要中同时被提及的次数。核心是 NumPy 二维累加:
n = len(medications) cooc = np.zeros((n, n)) # cooc[i][j] = 药物 i 与药物 j 同篇共现次数 for a in df['abstract']: x = str(a).lower() hit = [(' ' + m) in x for m in medications] # 该摘要是否提到每种药物 for i in range(n): if hit[i]: cooc[i, i] += 1 # 对角元:提及次数 for j in range(i + 1, n): if hit[j]: cooc[i, j] += 1 cooc[j, i] += 1 # 对称填充注意与 notebook 中"药物×诊断"版本(外层循环诊断、内层循环药物,m[j, i] += 1)的结构差异:药物-药物矩阵是对称方阵,行与列都是同一份药物清单。结果解读:非对角元大的药物对即为常被联合提及的组合(如羟氯喹与阿奇霉素常出现在同一摘要中),这可以反映临床联合用药方案或学术讨论热点。若把"药物"替换为"诊断",同一套代码即可生成诊断-诊断共现矩阵——作业明确允许这样做。
2.3 任务二:用热力图可视化共现矩阵
notebook 对药物×诊断矩阵的热力图实现可直接迁移:plt.imshow(m, interpolation='nearest', cmap='hot'),配合set_xticks/yticks与set_xticklabels/yticklabels标注词表(诊断标签旋转 90° 避免重叠):
plt.imshow(cooc, interpolation='nearest', cmap='hot') ax = plt.gca() ax.set_yticks(range(n)); ax.set_yticklabels(medications) ax.set_xticks(range(n)) ax.set_xticklabels(medications, rotation=90) plt.colorbar(label='co-occurrence count') plt.show()热力图应能一眼看出"热点区块"(亮色区域),配合np.unravel_index(np.argmax(cooc, axis=None), cooc.shape)输出最常共现的药物对。若觉得imshow刻度拥挤,可改用seaborn.heatmap(cooc, annot=True, fmt='.0f')增加数值标注,两者效果等价。
2.4 延伸目标一:chord 图可视化共现关系
热力图适合整体观察,但**弦图(chord diagram)**能更优雅地表达"谁和谁相连、连接多强":圆周上排列药物节点,节点之间弧线宽度代表共现频次。作业推荐使用 PyPI 上的chord库(pip install chord),该库基于plotly或holoviews后端渲染交互式图表,直接接收矩阵与标签即可成图:
# pip install chord from chord import Chord Chord(cooc, medications).to_html() # 输出交互式 HTML若希望完全复用课程代码栈,也可仿照 notebook 第 28 号单元用plotly.graph_objects手工构造 Sankey 图(go.Sankey,需要节点列表、source/target 索引与边权重),并设置阈值过滤弱连接——notebook 中的通用sankey(cat1, cat2, m, treshold, h1, h2)函数可直接改造成药物-药物版本。两条路线任选其一即可满足"至少完成一个延伸目标"的要求。
2.5 延伸目标二:用正则提取药物剂量
第二个延伸目标是结构化信息抽取:从自由文本(如take 400mg of chloroquine daily)中用正则表达式提取药物剂量(如400mg),并汇总成"药物 × 剂量"的 DataFrame。核心挑战是作业特别强调的"数值必须与药名在文本上邻近"——不能全文乱匹配,否则会把无关的400归到错误的药名下。
import re pat = re.compile(r'(\d+(?:\.\d+)?)\s*(mg|g|mcg|µg|gram|milligram)', re.IGNORECASE) records = [] for m in medications: for a in df['abstract']: x = str(a).lower() # 找到药名出现位置,仅在其前后 WINDOW 个字符内匹配剂量 start = 0 while True: pos = x.find(m, start) if pos == -1: break window = x[max(0, pos - 40): pos + len(m) + 40] for num, unit in pat.findall(window): records.append((m, f'{num}{unit.lower()}')) start = pos + 1 dose_df = pd.DataFrame(records, columns=['medication', 'dose']) dose_df['count'] = 1 dose_df = dose_df.groupby(['medication', 'dose']).size().reset_index(name='count') dose_df.sort_values(['medication', 'count'], ascending=[True, False])窗口宽度(示例 40 字符)与剂量单位词表都可按需调整;findall配合位置约束保证了"数值邻近药名"这一关键约束。产出表应能回答:羟氯喹的常见剂量区间是多少、不同药物是否有代表性剂量模式。把unit统一转为mg(如1g = 1000mg)可让跨单位比较更有意义。
评分标准(Rubric)
| Exemplary(示范级) | Adequate(合格级) | Needs Improvement(待改进) |
|---|---|---|
| 全部任务完成,图形化呈现并给出解释,且完成两个延伸目标中的至少一个 | 完成 5 项以上任务,未尝试延伸目标,或结果不清晰 | 完成少于 5 项(但多于 3 项)任务,可视化无法帮助论证观点 |
对照该标准自查时请格外关注三件事:其一,"图形化 + 解释"是硬性要求——每个任务都应配图并在图中/文后说明结论,尤其是多国 Rt对比、共现热力图这两处"看点";其二,任务三与任务四之间存在依赖(病程天数 D 是病死率校正的前提),务必在文中明确 D 的取值依据;其三,两个延伸目标只需完成其一即可达到 Exemplary,优先选择你更有把握的路线(chord 库开箱即用,正则方案则无需额外依赖)。
完成建议与运行前提
- 运行环境:作业代码依赖
pandas、numpy、matplotlib(延伸目标还需plotly/chord),建议在 Jupyter Notebook 中逐单元执行。入门版 Pandas 概念(Series 索引对齐、DataFrame 过滤、groupby聚合、apply、resample)可参考 基础 notebook.ipynb 与课程 README;R 语言学习者可对照 R 版本 notebook 理解同一作业的等价实现。 - 数据前提:第一部分可直接使用仓库 data/COVID/ 的离线 CSV;第二部分需自行获取 CORD-19
metadata.csv(仓库不包含该数据集),首次加载约需数分钟。 - 版本口径:notebook 中的
fillna(method='pad')为旧版 Pandas API,新版可用fillna(method='ffill')或ffill()替代;其余代码在主流 Pandas 版本下均可直接运行。 - 验收闭环:对照上表 Rubric 逐项核对"任务完成 → 可视化 → 文字解释",并确保延伸目标(chord 图或剂量 DataFrame)出现在交付物中,即可达到示范级评分。
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考