COVID-19数据可视化实战:Python从清洗到图表全流程
简介面向数据分析初学者和公共卫生研究者的COVID-19每日病例报告分析与可视化资源基于约翰·霍普金斯大学公民影响中心持续更新的美国县级疫情数据用Jupyter Notebook搭建了从数据获取、清洗到多维度可视化展示的完整流程。压缩包大小约92.76MB内容以notebook代码和配套数据集为主可直接运行学习。已有576人学习下载。资源梳理了多项数据源县级确诊与死亡病例、纽约自治市病例、年龄段人口与种族、医疗床位分布、人口与贫困就业信息并包含了状态检测、住院、恢复等主题同时提供了桌面端与移动端可视化仪表板的实现思路。读者既能跟随代码理解疫情数据整合逻辑也能参考其中的图表设计方法将其迁移到其他时序数据分析项目中是一份兼顾数据认知与可视化实操的参考资料。 做数据分析这几年我越来越觉得COVID-19的公开数据尤其是每日病例报告是练习数据分析和可视化最好的素材之一。它的数据量级足够真实清洗过程有挑战时间序列的趋势又特别适合做各种图表。这篇文章我想完整拆解一下我自己的处理思路从拿到原始CSV开始到完成清洗、聚合、计算关键指标再到输出不同类型的可视化图表整套流程走下来你会对数据分析项目到底该怎么做有一个很明确的体感。这个项目适合三类人看刚学完pandas但不知道做什么练手的人准备简历里放数据分析项目的求职者以及日常需要做日报、周报类数据汇报的运营和产品同学。它解决的也不是什么高深的问题而是把一堆日期、地区、病例数字变成能看懂、能讲故事的结论。1. 项目整体思路与方案选型做这个项目前得先说清楚一件事单纯画出图不叫分析得让图和指标能回答业务问题。我给自己定的目标是任何一张图拿出去都能直接说清楚趋势怎么样、拐点在哪、哪个地区最严重而不是让对方看了图还问所以呢。1.1 核心需求拆解这个项目真正考的是什么表面上看这就是下载数据 → 处理一下 → 画几张图三步。但真正动手后会发现问题全藏在细节里。比如数据格式不统一、日期字段解析报错、某些地区数据缺失、宽表结构和长表结构怎么转换、不同国家的累计病例数量级差太大导致图表没法看。我最终把需求拆成了四层。第一层是数据可靠保证拿到的数据经过清洗后是准确的第二层是指标清晰定义好累计病例、新增病例、7日移动平均、每百万人口病例数这些口径第三层是可视化表达根据不同的使用场景选对图表第四层是结论可解释从图表里提取出明确的趋势判断。这四个层级的顺序不能乱数据错了后面全是白做。1.2 为什么用Python pandas plotly这套组合这个项目我见过有人用Excel做也有人用Tableau做但我的建议是学数据分析的人还是优先用Python。原因有三个一是pandas处理这种时间序列表格数据的能力是最顺手的宽表和长表的转换、按日期分组聚合、窗口计算几行代码就搞定二是Python的可视化生态极度丰富同一份数据既能出论文风格的静态图也能出网页交互图三是这套流程可以完全脚本化下次换一份数据跑一遍就行。具体到工具选型我用了pandas做数据处理matplotlib出静态图plotly出交互图seaborn做了一些统计类的辅助图。数据集用的Johns Hopkins大学的全球COVID-19公开数据和Our World in Data的补充数据文件是CSV格式结构清晰非常适合做案例。如果你也想复现直接认准这两个来源就行网上大量教程都基于它们遇到问题也好搜索。2. 数据获取与预处理很多人做数据分析项目不重视预处理上来就画图结果图出来全是错的。我用这套COVID数据踩过的坑不在少数所以单独把预处理环节拎出来讲。一份干净的数据集是整个项目的地基。2.1 数据集怎么选两种主流数据源的对比目前我接触到的COVID公开数据集主要有两种组织方式。一种是Johns Hopkins的格式按省/州、国家/地区、纬度、经度、然后是一长串日期列排列属于典型的宽表一行是一个地区的全时间序列。另一种是Our World in Data的格式每一行是一个国家在某个日期的记录附带测试量、疫苗接种、人口、GDP等四十多个字段属于长表信息更丰富但更杂。我两个都用了。练手阶段优先推荐Johns Hopkins的宽表因为宽表转长表这个操作本身就是数据分析里的高频技能而且数据结构简单适合掌握核心逻辑。等熟悉了再切换到OWID数据做深入分析比如分析疫苗覆盖率对住院率的影响这种字段只有OWID才有。如果你是想做一个看起来信息量很大的项目展示直接用OWID就够了字段多自然显得内容多但代价是数据质量参差不齐很多列缺失严重。2.2 清洗过程详解日期解析、宽表转长表、缺失值处理宽表转长表是处理这套数据的第一个关键操作。原始数据是一列一个日期但pandas做时间序列分析最好是一列日期、一列数值、一列地区标识。代码很简单用melt函数把日期列全部放入新的一列import pandas as pd df pd.read_csv(time_series_covid19_confirmed_global.csv) # 把宽表转成长表 id_vars [Province/State, Country/Region, Lat, Long] df_long df.melt(id_varsid_vars, var_nameDate, value_nameConfirmed) df_long[Date] pd.to_datetime(df_long[Date])这里有一点值得注意。不同的数据集日期格式可能不一样有的是1/22/20这种美式格式有的是2020-01-22这种ISO格式。读进来之后不要急着分析先跑一遍df.dtypes确认Date列是datetime类型否则后面按日期切片、绘图时的坐标轴排序都会出问题。我一开始没注意结果plotly画图的时候日期轴乱序排查了半天才发现是字符串排序导致的。缺失值处理也不能忽略。COVID数据里有些国家下面分省有些没有省这一级所以Province/State列大量为空。我的处理原则是做国家层面分析时直接去掉省份维度按国家分组对Confirmed求和如果做的分析需要保留省州维度就把空值填成Unknown避免pandas在后续groupby时产生歧义。此外还有一类缺失是数据上报延迟造成的表现是某一天新增为0这不一定代表真实为0只是没更新。对这种问题你可以选择保留原样也可以在报告里注明受上报节奏影响。3. 核心分析逻辑指标怎么定义决定了结论怎么讲数据清洗完毕接下来是分析层面。这里的核心不是代码而是指标口径。我整理了一套固定的分析流程做任何时间序列数据都能直接用。3.1 累计病例 vs 新增病例先想清楚再动手累计病例和新增病例是解读疫情走势的两个基础视角。累计病例适合看整体规模但它是一条单调递增的曲线趋势变化不明显新增病例才真正反映疫情的传播速度和是否进入拐点。计算新增病例我的习惯是用diff()方法而不是自己写循环去减因为diff()在pandas里是向量化操作处理几十万行数据时快得多。df_global df_long.groupby(Date).agg(Confirmed(Confirmed, sum)).reset_index() df_global[DailyNew] df_global[Confirmed].diff() df_global[DailyNew] df_global[DailyNew].fillna(0)注意第一天的diff()结果是NaN因为前面没有值可以减这里要fillna成0。另外如果数据来自多个地区合并你需要先按日期groupby求和到全球维度再计算新增顺序反了会得到错误的趋势线。3.2 7日移动平均为什么是7不是3周效应到底影响多大疫情数据存在非常明显的周期性最典型的是周效应很多地区周末不上报或上报量减少导致周一数字偏高、周末偏低。如果直接看每日新增你会看到锯齿状的剧烈波动根本没法判断真实趋势。解决方式是用移动平均把周期噪声抹平。我选的窗口是7天因为疫情的传播周期恰好以周为单位。代码是df_global[SmoothNew] df_global[DailyNew].rolling(window7).mean()。这里有个细节rolling默认会取前6天加上当天共7天的均值所以画图时前6天是NaN图上会有一段空白可以在画图时用dropna()处理。窗口选3天可以吗可以但平滑效果很差只是把锯齿减轻了而已。选14天呢曲线很光滑但反应速度慢峰值的出现会明显偏晚。我做了几次对比后发现7日移动平均是兼顾时效性和平滑度的最佳方案这也是流行病学报告里最常用的口径。3.3 增长率、环比与每百万人口标准化让不同地区可对比除了绝对数字我还算了两类派生指标。第一类是日环比增长率计算公式是(当日新增 - 前一日新增) / 前一日新增 * 100这个指标能捕捉疫情是加速还是减速。但要注意当基数很小时环比会剧烈波动比如昨天新增10例今天新增20例增长率是100%这种数字在报告里要谨慎使用最好配合绝对数一起看。第二类是每百万人口病例数计算方式是病例数 / 人口数 * 1000000。为什么需要这个指标因为不同国家人口规模差异悬殊绝对病例数高的国家不一定每百万人病例数高后者更公平地反映了疫情在某国的渗透程度。如果要比较两个国家或地区我会把它们每百万人口的新增病例画在同一张图上而不是直接对比绝对数否则人口大国永远看起来更严重。4. 可视化实现从静态图到交互大屏可视化是这个项目最出彩的部分也是最容易翻车的部分。我做了一个三类图表的组合方案静态图用于报告和文档交互图用于网页展示地图用于全局概览。三者各司其职效果会好于只堆砌一种图。4.1 静态趋势图matplotlib画出论文级别的曲线静态图用途是存档和打印所以要尽量自包含信息。我最常用的一张图是全球每日新增病例 7日移动平均曲线它回答的问题是全球疫情周期到底走完了几波。绘制时我叠加了两条线浅色细线是原始每日新增深色粗线是7日移动平均一虚一实对比强烈读者一眼就能看到平滑后的主趋势。import matplotlib.pyplot as plt import matplotlib.dates as mdates fig, ax plt.subplots(figsize(12, 6)) ax.plot(df_global[Date], df_global[DailyNew], linewidth0.8, alpha0.5, labelDaily New) ax.plot(df_global[Date], df_global[SmoothNew], linewidth2.2, label7-day Average) ax.xaxis.set_major_locator(mdates.MonthLocator(interval6)) ax.set_title(Global Daily New COVID-19 Cases) ax.legend() plt.xticks(rotation45) plt.tight_layout() plt.savefig(covid_trend.png, dpi200)这里有个重要的技巧x轴的日期刻度要设置间隔否则几百个日期标签叠在一起图就废了。我用的是MonthLocator(interval6)每6个月显示一个标签保证可读性。如果你在画多国家对比图建议取对数坐标plt.yscale(log)因为不同国家的数量级能差到100倍以上普通坐标轴会把小数字压成一条直线。4.2 交互式图表与大屏展示plotly让数据活起来静态图适合深度阅读但放到网页或大屏上交互式图表明显更合适。plotly express提供了两行代码出图的体验非常适合快速验证想法。比如我想做一个能支持悬停查看数据的全球新增病例趋势图import plotly.express as px fig px.line( df_long_country, xDate, yConfirmed, colorCountry/Region, titleCOVID-19 Confirmed Cases by Country, ) fig.update_layout(hovermodex unified) fig.show()你看代码连手工配置hover内容都省了plotly会自动把所有字段显示出来。做数据大屏时我会把几个关键卡片放在上方——累计确诊、今日新增、7日均值、累计死亡——下方放趋势图和地图。大屏的布局逻辑是上KPI、中趋势、下地图视野从左到右分布信息密度从高到低递进。用plotly的subplot或者直接把多个div塞进一个网页框架都行重点不是技术而是叙事节奏。4.3 热力地图从全局视角看扩散路径地图是全局概览的最好形式。plotly的choropleth_mapbox或choropleth都能实现前者需要申请地图token后者可以直接用内置地理数据。我用的choropleth画过一版全球累计确诊分布图颜色越深代表累计病例越多鼠标悬停能查看具体数值。fig px.choropleth( world_cases, locationsiso_alpha, colorConfirmed, hover_nameCountry/Region, color_continuous_scaleYlOrRd, titleWorldwide COVID-19 Confirmed Cases, ) fig.show()这里最容易踩的坑是国家名称的写法China vs CN vs CHN三套体系不一样plotly默认需要ISO三个字母代码如果你的原始数据里是国家全称就得先做一次映射否则地图上会大量显示为灰色。我的做法是在数据清洗阶段就手动加一列iso_alpha用pycountry库或字典映射都行这一步逃不掉。5. 常见问题与排查技巧实录写代码没有不踩坑的踩坑不可怕可怕的是不知道怎么排查。我做这个项目过程中遇到过不少问题这里挑几个最典型的写下来都是实打实会遇到的情况。5.1 高频报错与处理方式速查表问题现象产生原因排查思路与解决方案日期列排序错乱日期是字符串类型按字母序而非时间序排序pd.to_datetime转换然后sort_valuesmatplotlib图片中文变方块系统中文字体缺失指定字体如plt.rcParams[font.sans-serif] [SimHei]并加plt.rcParams[axes.unicode_minus] FalseFutureWarning: Groupby操作pandas版本升级后的行为变更阅读警告详情按提示换用新API如observedTrue参数plotly地图大部分为灰国家名称不是ISO代码使用pycountry库做名称映射转换后传入locations数据量过大导致浏览器卡死绘制过多国家过多点先聚合到世界或大洲维度或用downsample抽样diff()首日为NaN第一行没有前值.fillna(0)或dropna()处理图表坐标轴标签重叠日期/文本标签过多设置plt.xticks间隔或旋转角度用MaxNLocator控制密度这张表基本覆盖了我见过的高频问题。如果你遇到表外的报错我的排查习惯是先把报错信息拷进搜索引擎看前三条结果绝大多数情况下已经有人踩过同样的坑了。5.2 独家经验数据分析项目的落地心得最后分享几个从项目中沉淀下来的实操心得这些经验比具体的函数更有长期价值。第一个心得是参数化一切路径。写脚本时不要一条路径写死用pathlib.Path构建路径用config.py集中管理列名、日期格式、文件URL这样你换一份新数据时只需要改配置文件而不是通篇搜索替换。第二个心得是保留数据快照。原始数据每次从网上下载都可能变化所以我会在下载后第一时间复制一份副本作为原始快照后续清洗在副本上进行。这保证了项目的可复现性别人拿到代码跑出来的结果和你文章里截图对得上。第三个心得是分析要有why层的结论。很多人做数据分析项目最后只写了数据呈上升趋势这种废话那不算结论。真正的结论应该是东南亚地区在2022年3月后出现持续性上升与当地放宽检测政策在时间上吻合需要结合具体政策进一步验证。就算不做因果验证也要在结论里明确说明数据现象、时间窗口和可能的原因假设。如果你也在做类似的数据分析项目我建议不要只满足于把官方示例跑通。把数据集换成你所在地区的数据增加一两个你自己定义的指标整份报告做出来的感觉会和抄教程完全不一样。COVID数据只是一个很好的起点把它玩熟了你会发现自己掌握的不只是绘图函数而是一套拿任何数据都能讲故事的框架能力。本文还有配套的精品资源点击获取