简介:一份面向计算机、电子信息工程、数学等专业大学生的新冠肺炎疫情数据分析与可视化完整课设项目,可用于课程设计、期末大作业或毕业设计参考,适合需要从零搭建数据采集、清洗、分析与可视化流程的读者。项目包共57个文件,包含17个Python脚本(涉及微博舆情爬虫、疫情数据爬取、分词、逻辑回归预测、地图与词云可视化),2个Jupyter Notebook(疫情分析与预测、NLP处理),以及5个CSV疫情数据集、5个HTML交互页面、算法设计Word报告和多张结果图片,整体约3.94MB。已有424人学习下载。借助这份高分作品,读者可以快速掌握新冠时序预测与情感分析的具体实现,理解数据爬取、预处理、模型构建、可视化展示的完整链路,并直接复用其中代码和图表生成方案,有效节省课设开发与报告撰写时间。
1. 从 zip 解压到跑通只算开始:疫情课设要的是能说清的分析闭环
很多人拿到“新冠肺炎疫情数据分析与可视化python完整课设和代码+报告.zip”之后,第一件事是解压、装库、运行,看到弹出折线图就以为完事了。真正到答辩或验收时,老师盯着图问一句“这个日新增是你算出来的,还是文件里直接带的”“地图上的省份名和你数据里的省份对不对得上”,很多人当场垮掉。这门课设的核心不是“能跑出图”,而是“数据怎么来→清洗成什么结构→图上画的是什么→结论怎么从图里长出来”整条链路闭环。“新冠肺炎疫情数据分析与可视化python”这串词,说白了就是拿一份公开的疫情表格,用 pandas 清洗,用 pyecharts 画趋势、画地图,再用一两段指标或模型把结论写进报告。适合刚学完 Python 基础、要交课程设计,或者想在简历里放一个“从数据到可视化到报告”完整作品的人。如果已经有底子,直接跳到第 5 章避坑清单也不亏。
2. 数据获取与清洗:把疫情 csv 揉成一张能画图的长表
公开疫情数据和你在教程里看到的小样例最大的区别是“它有手有脚”:要么宽表里横着一排日期列,要么长表动辄几十万行;字段名也不是同一个风格。做这个课设,第一步不是画图,而是定口径。先搞清楚我们要什么:一张“日期 + 国家 + 累计确诊 + 累计死亡 + 累计康复”的干净长表,再加一个“日新增”列,后面所有图表都从这里出。
2.1 先看清表格结构:宽表和长表是两种世界
拿到 zip 后先别急着写画图代码,打开数据文件看前几行。疫情公开数据常见两种存储格式:
- 宽表:每行是一个国家或省份,后面的列是连续的日期,格子里的值是“截止该日期的累计确诊”。这种表人类看着方便,但 pandas 画图很难直接消费。
- 长表:只有几列,一般包含 Province/State、Country/Region、Date、Confirmed、Deaths、Recovered。这种结构对 pandas 最友好,groupby、筛选、合并都不容易出错。
先做这一步:
import pandas as pd raw = pd.read_csv("data/covid_global.csv", nrows=5) print(raw.columns.tolist()) print(raw.head())nrows=5 只读前 5 行,目的是快速确认列名、日期格式和分隔符。如果你拿到的文件是 xlsx 或 xls,把 pd.read_csv 换成 pd.read_excel 即可,两者后续处理一致。这一步花不了一分钟,但能避免后面几十次报错。
宽表和长表没有谁好谁坏,但处理路径完全不同。宽表适合用 melt 压缩成行;长表适合直接 rename 和 groupby。先判断自己手里是哪种,再决定走哪个清洗分支,比强行套网上的教程代码靠谱得多。见过不少翻车现场:手里是宽表,硬套长表的代码,跑出来的图全是一条水平线,因为 pandas 把整列日期当成了一个单独字段。
2.2 用 pandas 三行命令把宽表变成可计算的长表
宽表转长表是疫情课设里最高频的操作。以一份全球累计确诊宽表为例,结构大概是:Country/Region 作为第一列,后面跟着 1/22/20、1/23/20…… 这种日期列。转换代码如下:
long_df = raw.melt( id_vars=["Province/State", "Country/Region", "Lat", "Long"], var_name="date", value_name="confirmed", ) long_df["date"] = pd.to_datetime(long_df["date"], format="%m/%d/%y") long_df = ( long_df.groupby(["Country/Region", "date"], as_index=False)["confirmed"] .sum() .sort_values(["Country/Region", "date"]) ) long_df["daily_new"] = ( long_df.groupby("Country/Region")["confirmed"] .diff() .fillna(0) .clip(lower=0) )这段代码的逻辑分四步:melt 把几十个日期列压缩成 date 和 confirmed 两列;pd.to_datetime 指定 format 把美国式日期转成 pandas 的 datetime 类型;groupby 按“国家 + 日期”聚合,解决同一个国家下分省份、分州导致多行的问题;最后用 diff 算相邻日期的差值,得到每日新增。
参数说明:format 里的 "%m/%d/%y" 必须和文件里的日期格式一致,如果文件里是 2020-01-22,就要改成 format="%Y-%m-%d"。groupby 里的 sum() 把同一国家不同省份当日数据合并;如果某国数据是长表且已经只有国家维度,这步也不会出错,因为一个“国家+日期”只有一行。clip(lower=0) 是为了去掉 diff 算出来的负值——后文避坑章会细说。
2.3 清洗的隐藏问题:重复行、负值、日期格式
宽表转成长表只是第一步。真正让课设翻车的,往往是下面这些看起来不起眼的小问题。
重复行非常常见:多个数据源拼接时,同一国家同一日期可能出现两次。如果不去重,日新增会被翻倍。去重代码在聚合前后都要执行一次:
long_df = long_df.drop_duplicates( subset=["Country/Region", "date"], keep="last" )keep="last" 表示后面出现的覆盖前面出现的,适合“后更新的数据更准”的场景。注意 subset 不包含 confirmed,因为我们要的是“国家+日期”维度上去重。
日期格式统一要在画图之前完成。如果不转 datetime,直接画图时横轴会被当成字符串,顺序会是 2020-01-01、2020-02-01…… 这种词典序,节假日和换月的地方排序乱掉。转成 datetime 后,matplotlib 和 pyecharts 都能自动处理时间轴。
还有一个容易忽略的点:file 编码。pandas 默认按 utf-8 读,但很多公开 csv 是 ANSI 编码。读取时可以显式指定:
long_df = pd.read_csv("data/covid_global.csv", encoding="utf-8")如果报 UnicodeDecodeError,改成 encoding="gbk" 或 encoding="latin1" 逐个试。清洗完导出时,我会用 utf-8-sig 而不是 utf-8,因为 utf-8-sig 会在文件开头写入 BOM,Excel 打开中文不会乱码:
long_df.to_csv("data/covid_clean.csv", index=False, encoding="utf-8-sig")index=False 是为了不把 pandas 的行号写进文件;utf-8-sig 是课设报告里 Word 引用表格的后悔药。
提示:清洗阶段保存一份 covid_clean.csv,之后所有画图和建模都从这里读,不要再碰原始文件。这样一旦分析出错,能很快区分是数据问题还是代码问题,而不是每次从头跑。
3. 可视化:用 pyecharts 做报告能直接引用的趋势图和地图
数据清洗完,接下来进入视觉部分。选对可视化库,课设的观感能直接提升一个档次。
3.1 为什么是 pyecharts 而不是 matplotlib
matplotlib 适合论文里那种严谨的小尺寸图,但如果要做课设答辩或可视化大屏展示,pyecharts 更合适:pyecharts 本质是把 echarts 数据可视化 的能力搬进了 python,输出是一个自包含的 html 文件,鼠标悬停有提示框,图可以缩放、拖拽,不需要前端功底就能拿到交互效果。报告里截图用静态图,答辩现场打开 html 给老师看交互,两种场景都覆盖。
另一个关键理由是地图。pyecharts 的 Map 组件对国内省份的中国地图和全球国家地图都有现成支持,不需要自己找 GeoJSON、算投影。一套代码下来,趋势图、Top10 柱状图、省份地图、时间线动画全是同一个库的语法,学习成本低很多。
3.2 折线图和柱状图:把趋势与 Top10 写进报告
报告里最保险、最不跳戏的三张图:全国或全球累计确诊折线图、每日新增折线图、累计确诊 Top10 国家柱状图。折线图代码:
from pyecharts.charts import Line from pyecharts import options as opts line = Line(init_opts=opts.InitOpts(width="960px", height="540px")) line.add_xaxis(date_labels) line.add_yaxis( "日新增确诊", daily_values, is_smooth=True, label_opts=opts.LabelOpts(is_show=False), linestyle_opts=opts.LineStyleOpts(width=3, color="#d14a1e"), ) line.set_global_opts( title_opts=opts.TitleOpts(title="日新增确诊趋势"), tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"), datazoom_opts=[opts.DataZoomOpts(range_start=30, range_end=100)], ) line.render("charts/line_daily.html")add_yaxis 里的 is_smooth=True 让折线平滑,避免折线像锯齿一样扎眼;label_opts 的 is_show=False 是不在每个数据点上标数字,否则几百个点全堆在图上没法看。datazoom_opts 是 pyecharts 的亮点,图表下方会出现一个缩放条,默认 range_start=30 表示只显示后 70% 的数据,老师拖缩放条就能看任意时间段细节。width 和 height 设置画布尺寸,报告里引用截图时分辨率能撑得住。
柱状图更简单,但有个参数必须调:
from pyecharts.charts import Bar bar = Bar() bar.add_xaxis(top10_countries) bar.add_yaxis("累计确诊", top10_values) bar.set_global_opts( title_opts=opts.TitleOpts(title="累计确诊 Top10 国家"), xaxis_opts=opts.AxisOpts( axislabel_opts=opts.LabelOpts(rotate=-15) ), ) bar.render("charts/bar_top10.html")rotate=-15 是 x 轴标签旋转 15 度。国名通常较长,不旋转会叠在一起,报告里截出来的图全是黑坨坨。Top10 的选取很简单:按确诊总数排序取前 10 个国家名和数值,先算好再喂给柱状图,不要在 add_xaxis 里临时筛选。
3.3 中国地图和时间线:做出可视化大屏的观感
国内省份热力地图是疫情课设的保留节目。代码并不长:
from pyecharts.charts import Map map_chart = Map() map_chart.add( "累计确诊", [list(z) for z in zip(province_names, province_values)], "china", ) map_chart.set_global_opts( title_opts=opts.TitleOpts(title="国内各省累计确诊热力"), visualmap_opts=opts.VisualMapOpts( min_=0, max_=int(province_values.max()), is_piecewise=True, pieces=[ {"min": 10000, "color": "#c12e2a"}, {"min": 1000, "max": 9999, "color": "#fd8f42"}, {"min": 100, "max": 999, "color": "#ffd45e"}, {"max": 99, "color": "#eaf6ee"}, ], ), ) map_chart.render("charts/map_province.html")is_piecewise=True 配合 pieces 列表,会把图例分成几个区间段,而不是用一个连续色条。这样设计的好处是低数值省份不会被高数值省份的颜色淹没,视觉效果更接近正式可视化大屏。pieces 里的 min、max、color 按自己数据量级改,如果最大确诊是 5 万,就把第一档的 min 改成 50000。
如果想把地图做成随时间变化的动画,用 Timeline:
from pyecharts.charts import Timeline timeline = Timeline() for d in sample_dates: piece = build_map_for_date(d) timeline.add(piece, time_point=d) timeline.add_schema(play_interval=800, is_auto_play=True) timeline.render("charts/timeline.html")build_map_for_date 是“传一个日期,返回一个该日期的 Map 实例”的函数,你可以把上面的 Map 代码包进这个函数,换数据源即可。add_schema 的 play_interval=800 是自动播放时每帧间隔 800 毫秒,is_auto_play=True 让页面打开后自动播放。答辩演示到这一页时,地图上色块随时间燃烧扩散,比任何静态图都有说服力,这也是“python数据分析与可视化实践”里最能体现工作量的一块。
4. 数据分析与模型:用指标和 SIR 模型把结论写进课设报告
可视化解决“数据长什么样”,分析解决“数据说明什么”。只画图不分析,报告就没有结论,答辩时只能念图例。这一章讲三个能落地到课设报告的产出:增长率指标、粗糙 R0、SIR 模型曲线。
4.1 从图到结论:环比增速与一个粗糙 R0
累计确诊从 1 万涨到 2 万,和从 100 万涨到 101 万,同样都涨了 1 万,但传播势头完全不同。所以要算环比增长率:
china = clean_df[clean_df["country"] == "China"].copy() china = china.sort_values("date") china["growth_rate"] = china["confirmed"].pct_change().fillna(0) * 100 china["daily_new_7d"] = ( china["daily_new"].rolling(7, min_periods=1).mean() ) china["r0_approx"] = ( china["daily_new"].rolling(7).mean() / china["confirmed"].shift(1).rolling(7).mean() ).fillna(0)pct_change() 自动算相邻日期的百分比变化,乘以 100 变成百分比。growth_rate 在疫情早期很高,后期趋近于 0,报告里可以写“增长率从早期的高位回落到低位,说明传播被逐步压制”。
r0_approx 是我习惯用的粗糙估计:用 7 天平均日新增除以 7 天前的累计确诊,表示每 1 个已感染者在一周内平均催生出的新感染人数。这个值不是流行病学严格意义上的 R0,但课程设计报告里作为“疫情扩散速度的近似观察”完全够用,答辩时老师更关心你是否理解它为什么只是一个近似。
4.2 SIR 模型补一条趋势曲线:odeint 参数与边界
SIR 模型是疫情课设的加分项。它把人群分成易感者 S、感染者 I、康复者 R 三组,用两个参数 beta 和 gamma 描述传播和康复速度。实现用 scipy 的 odeint:
import numpy as np from scipy.integrate import odeint N = 1400.0 beta, gamma = 0.21, 0.07 I0, R0 = 1.0, 0.0 S0 = N - I0 - R0 t = np.linspace(0, 300, 300) def sir(state, t, beta, gamma): S, I, R = state dSdt = -beta * S * I / N dIdt = beta * S * I / N - gamma * I dRdt = gamma * I return dSdt, dIdt, dRdt result = odeint(sir, [S0, I0, R0], t, args=(beta, gamma)) S, I, R = result.Tbeta 是“感染者每天有效接触的人数”,gamma 是“感染者每天康复的比例”。beta=0.21、gamma=0.07 时,一个感染者平均传染 3 个人、病程约 14 天,模拟曲线会在大约第 60 天达到感染峰值。想调参数看效果:beta 越大峰值越高越靠前;gamma 越大康复越快峰值越低。这两组参数在报告里必须写明,否则模型就是黑匣子。
把模拟感染人数和真实日新增画在同一张图时,注意量纲。模拟的 N 是 1400 万(抽象量级),真实日新增是几百到几万,直接叠加是一条压扁的线。常见的做法有两种:真实日新增除以一个缩放常数,或者模拟值乘一个缩放系数。更严谨的会做参数拟合,但对课程设计来说,散点与曲线的形态对照已经足够表达“模型趋势 vs 现实趋势”的关系。我会在代码里注明:这里做的是演示性对照,不是拟合结果。
import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt plt.figure(figsize=(10, 6)) plt.plot(t, I, label="SIR 模拟感染人数", color="#d73027") plt.scatter(real_idx, real_daily / scale_factor, s=8, label="真实日新增(归一化)") plt.xlabel("Time (days)") plt.ylabel("People") plt.legend() plt.savefig("charts/sir_compare.png", dpi=200)dpi=200 保证截图放进 Word 里不糊。scale_factor 需要你根据真实数据的峰值手动调,让两条线大致同高,目的是看形态而不是精确数值。报告里一定要写清楚“SIR 模型没有考虑隔离政策、无症状感染、检测能力变化,曲线仅作趋势示意”。这样反而显得你懂边界。
4.3 报告编排:结论先行,图表作为证据
“代码+报告.zip”里的报告,比代码难写得多。一份能扛住答辩的报告结构,我建议按这个顺序组织:
- 摘要:数据源、时间范围、清洗方式,三句话说清分析对象和主要结论。
- 数据说明:字段表、保留哪些维度、如何处理缺失值,写出 daily_new 的计算口径。
- 图表解读:每张图配 1-2 句结论,例如“7 月第二周起日新增开始下降,结合移动平均确认拐点”。
- 模型与局限:SIR 的参数设定、为什么不能直接用模拟值,放在“局限”里说透。
- 附录:代码清单、依赖环境、运行顺序。
报告里不要贴大段代码,把关键统计量和参数放正文或表格,代码放附录。我见过不少课设报告把 200 行代码直接复制进正文,老师一眼扫过去就会觉得是在凑页数。结论先行的意思是:每一节第一句话就回答“这张图说明了什么”,然后再展开细节。数据分析课设的答辩时间通常很短,老师看摘要就能判断你有没有真正跑通,而不是只改了别人的图。
5. 避坑:疫情课设最常见的 5 个翻车现场与排查
课程设计出问题的地方往往不在分析本身,而在环境、版本、编码这些“yìn”上。下面 5 条是疫情课设里重复率最高的坑,每条都是“现象 → 原因 → 解决”的排查逻辑。
5.1 图表里全是方块字:中文字体与编码问题
现象:pyecharts 生成的 html 里,标题和图例全是方块、问号或乱码,尤其是 Windows 系统。
原因分两层:一是数据源里的中文在读取/导出时用了错误编码,csv 文件里的中文变成乱码,画图自然乱;二是 pyecharts 生成的 html 里字体栈没声明中文字体,浏览器回退失败。
解决:数据文件读取时显式指定 encoding,导出用 encoding="utf-8-sig"。pyecharts 画图时在 TitleOpts 和 LabelOpts 里显式设置 font_family,例如 font_family="Microsoft YaHei"。如果图表是英文数据,标题写成中文依然可能乱码,原因就是第二种。
5.2 pyecharts 版本一变,老教程代码整段不能跑
现象:网上抄的代码报错信息是 “ImportError: cannot import name ‘Map’ from ‘pyecharts’” 或 “Timeline” 导入失败。
原因:pyecharts 1.x 和 2.x 的 API 差异很大,老教程多是 0.5.x 版本,Map 的导入路径完全不同。
解决:锁定版本,一条命令统一环境:
pip install pyecharts==2.0.9导入方式统一写成:
from pyecharts.charts import Map, Timeline, Line, Bar from pyecharts import options as opts这样能避开绝大多数旧教程坑。如果用的是 Python 3.11 以上,建议再确认 pyecharts 依赖的 simplejson 版本,缺什么装什么。
5.3 中国地图整块空白:省份名与地图资源两个坑
现象:地图渲染出来全空白,或只有个别省份有颜色。
原因有两类。第一类是省份名称不匹配:pyecharts 中国地图内置使用“北京”“上海”“湖北”这种不带“市”“省”后缀的名称,而数据里可能写的是“湖北省”“北京市”,匹配不上就画不出来。第二类是地图资源没加载:新版 pyecharts 本地地图文件需要单独机制,离线环境下 CDN 加载不到 echarts 地图脚本,整图空白。
解决:名称不匹配就用“湖北省”.replace("省", "") 这类规则清洗,或者维护一个手工映射表。地图资源加载失败的,先检查项目里是否有 map js 文件,没有的话手动把地图 js 文件下载到本地并注册,再用 render 生成 html。这门课设的所有图表都要能在拆了网的答辩教室跑起来,这一点提前确认。
5.4 “日新增”出现负值:diff 的副作用
现象:每日新增折线图里突然出现一个向下的尖刺,数值为负。
原因:diff() 算的是“今天的累计值减昨天的累计值”。某些数据源在某个日期修正了之前的统计口径(例如把之前多算的病例减回去),今天的累计值反而比昨天小,diff 就产生负值。
解决:在计算 daily_new 后立刻 clip:
china["daily_new"] = china["daily_new"].clip(lower=0)同时报告里写一句“负值来源于统计口径修正,已按 0 处理”。这样数据说明部分反而成了加分项。
5.5 报告里的图表糊成一片:渲染与截图分辨率
现象:html 里图表很清晰,截图放到 Word 后文字发虚,折线边缘锯齿严重。
原因:普通截屏按屏幕分辨率输出,Word 显示时进行了缩放;如果直接用 pyecharts 的 render 输出 png,默认分辨率也不高。
解决:用 snapshot-selenium 组件渲染高分辨率 png:
pip install snapshot-seleniumfrom pyecharts.render import make_snapshot from snapshot_selenium import snapshot make_snapshot(snapshot, "charts/line_daily.html", "charts/line_daily.png", pixel_ratio=2)pixel_ratio=2 表示按 2 倍分辨率输出,图里文字在 Word 里放大也清晰。比手动截图稳定得多。
注意:这 5 个坑不是互相独立的。地图空白时先看省份名再查资源文件;日新增负值修完以后再画图,别带着脏数据直接可视化。
6. 进阶:用留出段数据自我验证,再用 Streamlit 做成可交互看板
6.1 先验证再交付:留出三个月做回头确认
课程设计交出去之前,给自己留一道“凸出测试”:只用截止到某年某月的数据清洗并画图,之后三个月的真实数据不参与任何计算,当作未来数据来验证。做法很简单:数据读进来后先按日期切一刀,把后三个月单独放一个变量:
train = clean_df[clean_df["date"] < "2021-10-01"] test = clean_df[clean_df["date"] >= "2021-10-01"]训练部分观察到的趋势拐点、增长率回落等结论,用 test 部分的真实值去比对:如果测试段的实际变化方向与基于 train 段得出的趋势判断一致,说明分析逻辑是成立的;如果明显矛盾,优先检查清洗是否有误。这个过程不用写到报告里,但它能帮你避免“分析结论只是事后诸葛亮式地描述一张图”的尴尬。
6.2 十个 Streamlit + pyecharts 代码把静态报告变成可交互看板
课设验收时,如果评委可以自己选国家、拉时间范围,效果比翻 ppt 强很多。用 streamlit 把之前渲染 html 的代码包一层,就是一个小型可视化大屏:
import streamlit as st import pandas as pd from pyecharts.charts import Line from pyecharts import options as opts import streamlit.components.v1 as components st.title("疫情数据展示看板") df = pd.read_csv("data/covid_clean.csv", parse_dates=["date"]) country = st.selectbox("选择国家或地区", sorted(df["country"].unique())) sub = df[df["country"] == country].sort_values("date") line = Line() line.add_xaxis(sub["date"].dt.strftime("%Y-%m-%d").tolist()) line.add_yaxis("累计确诊", sub["confirmed"].tolist(), is_smooth=True) line.set_global_opts( title_opts=opts.TitleOpts(title=f"{country} 累计确诊趋势"), datazoom_opts=[opts.DataZoomOpts()], ) components.html(line.render_embed(), height=480, width=980)selectbox 生成下拉框,组件会监听用户选择并重新执行脚本。render_embed 是 pyecharts 提供的方法,把图表 html 内嵌到 streamlit 页面里,不需要额外生成文件。运行方式是:
streamlit run app.py我第一次交这类课设时,只画了折线图和地图,答辩老师一句话就问住了我:“日新增这个数到底是你算的还是文件里给的?”从那以后,我养成了一个习惯:任何数据项目,先把口径写在最前面,再把验证过程留一手,宁可图少一张,也要把逻辑说到位。希望这份笔记能让你少走几段弯路,把疫情数据课设做成一个真正能讲的完整作品。
本文还有配套的精品资源,点击获取