疫情数据分析与可视化Python课设:从数据清洗到闭环报告 简介一份面向计算机、电子信息工程、数学等专业大学生的新冠肺炎疫情数据分析与可视化完整课设项目可用于课程设计、期末大作业或毕业设计参考适合需要从零搭建数据采集、清洗、分析与可视化流程的读者。项目包共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, nrows5) print(raw.columns.tolist()) print(raw.head())nrows5 只读前 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_namedate, value_nameconfirmed, ) long_df[date] pd.to_datetime(long_df[date], format%m/%d/%y) long_df ( long_df.groupby([Country/Region, date], as_indexFalse)[confirmed] .sum() .sort_values([Country/Region, date]) ) long_df[daily_new] ( long_df.groupby(Country/Region)[confirmed] .diff() .fillna(0) .clip(lower0) )这段代码的逻辑分四步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(lower0) 是为了去掉 diff 算出来的负值——后文避坑章会细说。2.3 清洗的隐藏问题重复行、负值、日期格式宽表转成长表只是第一步。真正让课设翻车的往往是下面这些看起来不起眼的小问题。重复行非常常见多个数据源拼接时同一国家同一日期可能出现两次。如果不去重日新增会被翻倍。去重代码在聚合前后都要执行一次long_df long_df.drop_duplicates( subset[Country/Region, date], keeplast )keeplast 表示后面出现的覆盖前面出现的适合“后更新的数据更准”的场景。注意 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, encodingutf-8)如果报 UnicodeDecodeError改成 encodinggbk 或 encodinglatin1 逐个试。清洗完导出时我会用 utf-8-sig 而不是 utf-8因为 utf-8-sig 会在文件开头写入 BOMExcel 打开中文不会乱码long_df.to_csv(data/covid_clean.csv, indexFalse, encodingutf-8-sig)indexFalse 是为了不把 pandas 的行号写进文件utf-8-sig 是课设报告里 Word 引用表格的后悔药。提示清洗阶段保存一份 covid_clean.csv之后所有画图和建模都从这里读不要再碰原始文件。这样一旦分析出错能很快区分是数据问题还是代码问题而不是每次从头跑。3. 可视化用 pyecharts 做报告能直接引用的趋势图和地图数据清洗完接下来进入视觉部分。选对可视化库课设的观感能直接提升一个档次。3.1 为什么是 pyecharts 而不是 matplotlibmatplotlib 适合论文里那种严谨的小尺寸图但如果要做课设答辩或可视化大屏展示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_optsopts.InitOpts(width960px, height540px)) line.add_xaxis(date_labels) line.add_yaxis( 日新增确诊, daily_values, is_smoothTrue, label_optsopts.LabelOpts(is_showFalse), linestyle_optsopts.LineStyleOpts(width3, color#d14a1e), ) line.set_global_opts( title_optsopts.TitleOpts(title日新增确诊趋势), tooltip_optsopts.TooltipOpts(triggeraxis, axis_pointer_typecross), datazoom_opts[opts.DataZoomOpts(range_start30, range_end100)], ) line.render(charts/line_daily.html)add_yaxis 里的 is_smoothTrue 让折线平滑避免折线像锯齿一样扎眼label_opts 的 is_showFalse 是不在每个数据点上标数字否则几百个点全堆在图上没法看。datazoom_opts 是 pyecharts 的亮点图表下方会出现一个缩放条默认 range_start30 表示只显示后 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_optsopts.TitleOpts(title累计确诊 Top10 国家), xaxis_optsopts.AxisOpts( axislabel_optsopts.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_optsopts.TitleOpts(title国内各省累计确诊热力), visualmap_optsopts.VisualMapOpts( min_0, max_int(province_values.max()), is_piecewiseTrue, 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_piecewiseTrue 配合 pieces 列表会把图例分成几个区间段而不是用一个连续色条。这样设计的好处是低数值省份不会被高数值省份的颜色淹没视觉效果更接近正式可视化大屏。pieces 里的 min、max、color 按自己数据量级改如果最大确诊是 5 万就把第一档的 min 改成 50000。如果想把地图做成随时间变化的动画用 Timelinefrom pyecharts.charts import Timeline timeline Timeline() for d in sample_dates: piece build_map_for_date(d) timeline.add(piece, time_pointd) timeline.add_schema(play_interval800, is_auto_playTrue) timeline.render(charts/timeline.html)build_map_for_date 是“传一个日期返回一个该日期的 Map 实例”的函数你可以把上面的 Map 代码包进这个函数换数据源即可。add_schema 的 play_interval800 是自动播放时每帧间隔 800 毫秒is_auto_playTrue 让页面打开后自动播放。答辩演示到这一页时地图上色块随时间燃烧扩散比任何静态图都有说服力这也是“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_periods1).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 的 odeintimport 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 是“感染者每天康复的比例”。beta0.21、gamma0.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, labelSIR 模拟感染人数, color#d73027) plt.scatter(real_idx, real_daily / scale_factor, s8, label真实日新增(归一化)) plt.xlabel(Time (days)) plt.ylabel(People) plt.legend() plt.savefig(charts/sir_compare.png, dpi200)dpi200 保证截图放进 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导出用 encodingutf-8-sig。pyecharts 画图时在 TitleOpts 和 LabelOpts 里显式设置 font_family例如 font_familyMicrosoft YaHei。如果图表是英文数据标题写成中文依然可能乱码原因就是第二种。5.2 pyecharts 版本一变老教程代码整段不能跑现象网上抄的代码报错信息是 “ImportError: cannot import name ‘Map’ from ‘pyecharts’” 或 “Timeline” 导入失败。原因pyecharts 1.x 和 2.x 的 API 差异很大老教程多是 0.5.x 版本Map 的导入路径完全不同。解决锁定版本一条命令统一环境pip install pyecharts2.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 后立刻 clipchina[daily_new] china[daily_new].clip(lower0)同时报告里写一句“负值来源于统计口径修正已按 0 处理”。这样数据说明部分反而成了加分项。5.5 报告里的图表糊成一片渲染与截图分辨率现象html 里图表很清晰截图放到 Word 后文字发虚折线边缘锯齿严重。原因普通截屏按屏幕分辨率输出Word 显示时进行了缩放如果直接用 pyecharts 的 render 输出 png默认分辨率也不高。解决用 snapshot-selenium 组件渲染高分辨率 pngpip 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_ratio2)pixel_ratio2 表示按 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_smoothTrue) line.set_global_opts( title_optsopts.TitleOpts(titlef{country} 累计确诊趋势), datazoom_opts[opts.DataZoomOpts()], ) components.html(line.render_embed(), height480, width980)selectbox 生成下拉框组件会监听用户选择并重新执行脚本。render_embed 是 pyecharts 提供的方法把图表 html 内嵌到 streamlit 页面里不需要额外生成文件。运行方式是streamlit run app.py我第一次交这类课设时只画了折线图和地图答辩老师一句话就问住了我“日新增这个数到底是你算的还是文件里给的”从那以后我养成了一个习惯任何数据项目先把口径写在最前面再把验证过程留一手宁可图少一张也要把逻辑说到位。希望这份笔记能让你少走几段弯路把疫情数据课设做成一个真正能讲的完整作品。本文还有配套的精品资源点击获取