简介:面向新冠肺炎疫情数据分析与可视化的Python完整课设资料包,主要适用于计算机、电子信息工程、数学等专业大学生,可作为课程设计、期末大作业或毕业设计的参考资料。项目基于真实疫情数据,完整覆盖数据采集、清洗、时序预测、微博评论情感分析与多种可视化方案,代码和报告经导师指导后获得98分,参考价值较高。压缩包共57个文件,总大小3.94MB,包含17个Python脚本、2个Jupyter Notebook、5个CSV数据集、5个HTML页面、16张PNG图片,以及docx报告、词云JS、Markdown说明等,目录结构清晰,便于按模块查阅复用。当前已有424人学习。借助完整源码和报告,读者可快速掌握爬虫、jieba分词、TF-IDF、Logistic回归、地图与折线图绘制等核心技术,直接将工程范式迁移到自己的课设或毕业设计场景中。
1. 一张能扛住答辩的疫情数据分析课设:看完这篇你就知道工作量在哪
“新冠肺炎疫情数据分析与可视化”这个题目,几乎是 Python 课设里的常青树。每年都有大量学生选它,以为自己会 Pandas 读个 CSV、Matplotlib 画个折线图就完事,结果答辩时被老师一句“你这个图说明什么问题?”问得哑口无言。这门课设真正考察的从来不是你会不会调用plt.plot(),而是你能否完成一条完整的数据分析链路——从数据获取、清洗、聚合,到可视化呈现和结论提炼。本文要拆解的这份“完整课设和代码+报告”方案,本质上是给你一套可以直接复现的落地流程:数据源选型、Pandas 核心操作、可视化图表规划、报告写作框架,以及最容易被忽视的踩坑点。适合正在做 Python 数据分析与可视化课设、但不太确定从哪里下手的同学。读完你不需要再到处找示例代码,照着做就能搭出你自己的版本。
2. 数据从哪来、怎么装:疫情数据源选型与 Pandas 读取的完整流程
2.1 疫情数据集的三种常见来源:不是所有数据都适合直接拿来做课设
做疫情数据分析,第一步不是写代码,而是找数据。数据源选错了,后面所有分析都是空中楼阁。我做过不少数据分析项目,也帮人看过课设代码,发现大家最常犯的错是用别人二次处理过的 Excel 表,字段没有统一说明,日期格式乱七八糟,分析到一半才发现数据根本对不上。常见做法是这样。
第一类,WHO 和 Johns Hopkins 的开源仓库。它们提供 CSV 或 JSON 格式的全球疫情日更数据,字段命名规范,比如Country/Region、Confirmed、Deaths、Recovered,不同国家地区的数据都有。这类数据的优点是覆盖时间长、格式稳定,适合做全球维度的趋势分析。缺点是字段名是英文,国内课设通常需要翻译或重命名,而且数据量大,如果不做聚合,Pandas 读取后运行会明显卡顿。
第二类,国内主要做的是国家卫健委和各省卫健委的公开数据,通常以 JSON 接口的形式提供,但接口经常变动,需要爬虫不断调试。课设时间有限,不建议把精力耗在写爬虫上——除非你的题目明确要求必须爬取实时数据。
第三类,GitHub 上有不少“疫情可视化”项目的数据集,比如国内某个城市的确诊病例明细,或者某段时间的境外输入数据。这些数据常用于数据分析工程实践,拿来练手可以,但如果报告里要写“数据来源于某某仓库”,一定要确认数据的更新日期和采集方式,别拿一个 2020 年就停更的数据集硬撑到 2023 年,答辩时经不起追问。
我一般建议课设选第一类或第三类,因为 CSV 和 JSON 读取稳定,不依赖网络。你甚至可以只挑一个国家(比如中国)或一个省份的数据做深入分析,避免全球数据量过大导致可视化交互卡死。
2.2 读取 CSV 和 JSON 的最小可运行代码:解析日期列和字段重命名
确定数据源后,先写一个最小读取脚本。假设你下载了 Johns Hopkins 的time_series_covid19_confirmed_global.csv,这是全球累计确诊数的时序数据。
import pandas as pd # 读取全球累计确诊数据 file_path = "time_series_covid19_confirmed_global.csv" df = pd.read_csv(file_path) # 查看前5行,确认字段结构 print(df.head()) # 查看列名和缺失值 print(df.columns.tolist()) print(df.isnull().sum())这段代码只做三件事:读文件、看结构、看缺失情况。很多课设翻车就在这一步——你没有先看数据长什么样就急着画图,结果画出来的折线图 x 轴全是日期字符串,y 轴是字符串拼接,图出来全是乱码。df.head()和df.columns.tolist()是最廉价的调试手段。
接下来是字段重命名和日期标准化。Johns Hopkins 的数据里,前几列是Province/State、Country/Region、Lat、Long,后面每一列是一个日期字符串,如1/22/20。这种宽表格式方便人阅读,但 Pandas 处理起来非常别扭——你没法直接按日期分组。所以要把宽表转换成长表:
# 将宽表转为长表:id_vars是保留的标识列,var_name是新列名,value_name是数值列名 df_melted = df.melt( id_vars=["Province/State", "Country/Region", "Lat", "Long"], var_name="date_str", value_name="confirmed" ) # 将日期字符串转换为真正的日期类型 df_melted["date"] = pd.to_datetime(df_melted["date_str"], format="%m/%d/%y") # 重命名列,方便后续代码阅读 df_melted.rename(columns={ "Province/State": "province", "Country/Region": "country" }, inplace=True) print(df_melted.head())参数说明要记清楚:melt里的id_vars是你不希望被折叠的列,var_name和value_name是自己起的列名,一般叫date和value;pd.to_datetime的format参数必须和源数据里的1/22/20完全对应,否则 Pandas 只能用默认解析器去猜,速度慢而且容易出错。这里改成%m/%d/%y是因为美国日期格式是月/日/年。
2.3 数据目录规划:一个不容易被老师翻出问题的项目结构
数据读取只是开始,更实际的问题是项目管理。课设交付时老师会看你的目录结构,一个乱七八糟的目录会拉低印象分。我一般这样组织:
covid19_analysis/ ├── data/ │ ├── raw/ # 原始数据,只读不写 │ └── processed/ # 清洗后的数据 ├── notebooks/ # Jupyter Notebook 分析过程 ├── src/ │ ├── data_loader.py # 数据读取 │ ├── data_clean.py # 清洗逻辑 │ └── visualization.py # 画图函数 ├── figures/ # 输出的图表 ├── report/ │ ├── 报告.md │ └── 答辩PPT.md └── requirements.txt这个结构不是拍脑袋定的。data/raw放原始文件,确保你随时能回溯;src把代码按职责拆开,别把 500 行代码全塞进一个main.py,老师看代码时看到你按函数拆分,至少能说明你有模块化意识。requirements.txt里写清楚依赖版本,比如pandas==2.0.3、matplotlib==3.7.2、pyecharts==2.0.5,这样换一台电脑也能复现。
3. 数据清洗与聚合:从混乱的原始表到可分析的结构化数据
3.1 处理缺失值和重复行:疫情数据里最常见的“隐形地雷”
疫情数据不是完美样本,缺失值和重复行是最常遇到的问题。Johns Hopkins 的仓库里,很多省份的Province/State列是 NaN,比如中国的数据就统一放在“China”下。如果你不去管这些缺失,后面按国家分组时会得到一堆奇怪的键。
# 处理缺失值:如果省份为空,直接用国家名填充 df_melted["province"] = df_melted["province"].fillna(df_melted["country"]) # 删除完全重复的行 df_clean = df_melted.drop_duplicates() # 检查每个国家的数据条数,确认没有明显异常 country_counts = df_clean.groupby("country").size() print(country_counts.head(20))逻辑说明:fillna用国家名填充省份,保证每条记录都有地区标识;drop_duplicates清理原始文件里可能存在的重复行。这里有个细节——drop_duplicates默认是整行完全重复才删,如果你只想去重某几列,需要传subset参数,比如drop_duplicates(subset=["country", "date"])。疫情数据是时间序列,同一国家同一日期不应该出现两行。
参数说明:fillna的method参数也有讲究,比如method="ffill"(向前填充)适合补时序数据的缺失值,但不适合这种需要按列填充的场景。所以这里直接传值是最直观的。
3.2 计算每日新增、死亡率与增长率:别把累计值当增量用
这是整个课设里最关键的一步,也是很多人的认知盲区。原始数据里通常只有累计值(比如累计确诊),但你想分析“每日新增”,不能用累计值直接画趋势线,需要自己算差分。
# 先按国家+日期排序,排序是差分计算的前提 df_sorted = df_clean.sort_values(["country", "date"]) # 计算每日新增确诊:groupby之后用diff,是当前行减去上一行 df_sorted["daily_confirmed"] = df_sorted.groupby("country")["confirmed"].diff() # 计算死亡率:累计死亡 / 累计确诊 df_death = pd.read_csv("time_series_covid19_deaths_global.csv") # 转换和merge过程略,只展示计算部分 df_sorted["death_rate"] = df_death["deaths"] / df_sorted["confirmed"] * 100 # 计算环比增长率 = (今天累计 - 昨天累计) / 昨天累计 df_sorted["growth_rate"] = df_sorted.groupby("country")["confirmed"].pct_change() * 100 # 将第一个值(diff结果NaT)填充为0 df_sorted[["daily_confirmed", "growth_rate"]] = df_sorted[ ["daily_confirmed", "growth_rate"] ].fillna(0) print(df_sorted.head(10))逻辑说明:groupby("country")["confirmed"].diff()的意思是,对每个国家的累计确诊列做一阶差分,得到的就是每日新增。pct_change()得到的是相对上一个日期的变化百分比,这个指标用来判断疫情是处于扩散期还是控制期非常有用。死亡率必须用deaths / confirmed,顺序别搞反。
避坑提醒:diff()的结果在第一行一定是 NaN,因为第一行没有“上一天”。如果你不fillna(0),后续画图时,这些空值会被 Matplotlib 默认跳过,但如果在 Excel 里打开处理后的 CSV,你会看到一堆空白——老师看到这种数据会认为你连基本的数据处理都没做。
3.3 按国家/地区聚合:全球对比分析的 pivot_table 操作
当你需要对比几个国家的疫情趋势时,光有长表还不够,要把它转成适合对比的宽表格式。pivot_table是这里最常用的工具。
# 选取几个重点国家做对比 countries_of_interest = ["China", "US", "India", "Brazil", "United Kingdom"] # 生成以行为日期、列为国家的累计确诊矩阵 pivot_confirmed = df_sorted[df_sorted["country"].isin(countries_of_interest)].pivot_table( index="date", columns="country", values="confirmed", aggfunc="max" ) # 对缺失值做向前填充,然后删除全空的行 pivot_confirmed = pivot_confirmed.ffill().dropna(how="all") print(pivot_confirmed.tail(5))参数说明:pivot_table的index是你希望作为行索引的列,columns是希望变成列标题的列,values是要聚合的数值列,aggfunc取决于你的数据粒度。这里用了max,因为同一日期同一个国家可能有多条记录,取最大累计值避免重复。ffill()是向前填充,如果某天没有数据,就用前一天的值补上,保证画折线图时不会断线。
这里特别提醒:你的课设报告里一定要写清楚每个字段的计算口径,比如“每日新增 = 今日累计 - 昨日累计”,不要只放一张图。答辩老师很可能就问:“你这个每日新增是怎么算的?”,答不上来就尴尬了。
4. 可视化方案设计:疫情分析到底该画哪几张图、怎么画
4.1 Matplotlib 画折线趋势图:中文乱码与日期轴格式化的完整解法
拿到结构化数据后,开始画图。第一张图通常是“重点国家累计确诊趋势对比”,用折线图最直观。
import matplotlib.pyplot as plt import matplotlib.dates as mdates # 解决中文乱码:指定中文字体 plt.rcParams["font.sans-serif"] = ["SimHei"] # Windows 用 SimHei,Mac 用 PingFang SC plt.rcParams["axes.unicode_minus"] = False # 解决负号显示为方块的问题 # 创建画布,设置尺寸让x轴刻度有足够的空间 fig, ax = plt.subplots(figsize=(12, 6)) # 逐个国家画折线 for country in countries_of_interest: if country in pivot_confirmed.columns: ax.plot( pivot_confirmed.index, pivot_confirmed[country], label=country, linewidth=1.8 ) # 设置x轴为日期格式,每隔30天显示一个刻度 ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1)) ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m")) ax.set_title("主要国家新冠疫情累计确诊数趋势对比", fontsize=14) ax.set_xlabel("日期") ax.set_ylabel("累计确诊人数") ax.legend(loc="upper left") ax.grid(alpha=0.3) # 旋转x轴刻度避免重叠 plt.xticks(rotation=45) plt.tight_layout() plt.savefig("figures/top_countries_trend.png", dpi=300, bbox_inches="tight") plt.show()这段代码是课设里比较核心的部分。中文乱码是几乎所有做 Python 数据可视化课设时都会遇到的问题——plt.rcParams["font.sans-serif"] = ["SimHei"]这行代码就是治它的,但前提是你的系统里装了 SimHei 字体。如果你在 Linux 服务器上跑,这个字体可能不存在,可以用fc-list :lang=zh查看系统可用中文字体,或者改用"Noto Sans CJK SC"。MonthLocator(interval=1)让 x 轴每 1 个月显示一个刻度,一方面避免刻度太密,另一方面也符合疫情的长期趋势分析节奏。dpi=300保证保存的图片清晰,不要用默认 100,答辩 PPT 投影出来会糊。
如果你觉得累计确诊对比太普通,可以换一张“每日新增确诊对比”。只需把values="confirmed"换成values="daily_confirmed",语义完全不一样——累计量看趋势定调,新增量看拐点和波动,两张图配合使用才有说服力。
4.2 Pyecharts 画中国地图和全球地图:省市数据如何匹配到地理坐标
折线图是基础,但课设要有亮点,建议加一张地图。Pyecharts 是做交互可视化比较顺手的库,它可以在 Notebook 里直接渲染出支持鼠标悬停的 HTML 页面,生成的是独立的 HTML 文件,不用依赖 Python 环境就能打开。
from pyecharts.charts import Map from pyecharts import options as opts # 假设你已经有一份各省累计确诊的数据:province_list 和 confirmed_list province_list = ["广东", "河南", "湖北", "浙江", "湖南"] confirmed_list = [2500, 1800, 68000, 1500, 1200] # 创建地图实例 map_chart = Map() map_chart.add( series_name="累计确诊", data_pair=list(zip(province_list, confirmed_list)), maptype="china", is_map_symbol_show=False ) map_chart.set_global_opts( title_opts=opts.TitleOpts(title="中国部分地区疫情分布"), visualmap_opts=opts.VisualMapOpts(max_=5000, is_piecewise=True) ) map_chart.set_series_opts( label_opts=opts.LabelOpts(is_show=False) ) # 输出成独立的HTML文件 map_chart.render("figures/china_map.html")这里有一个非常容易踩的坑:Pyecharts 的省份名称映射。它识别的是“广东”这样的标准名称,如果你数据里写的是“广东省”,地图上就显示不出来;如果写“湖北”,它会映射到“湖北”而不是“湖北省”的图形区域。1.0 版本之后 Pyecharts 对名称匹配的容错比前面的版本好一些,但仍然建议先把省份列清洗成标准名称,比如去掉“省/市/自治区”后缀,再把“内蒙古”、“广西”、“新疆”等特殊名称单独核对一遍。验证方法很简单,渲染完 HTML 后打开看一眼,哪个区域没上色,就是名称没匹配上。
地图并非必须每张都做。如果你的题目重点是国家趋势分析,一张全球地图或中国地图做点缀就够,把精力花在解释数据上更有价值。
4.3 热力图与堆叠柱状图:关注哪些场景适合用,哪些是误导
疫情分析里还有一个常见需求:按“月份 × 国家”看新增确诊的分布。这种二维关系用热力图比较合适,用颜色深浅表达数值大小。
import seaborn as sns # 提取年-月字段,用于聚合 df_sorted["year_month"] = df_sorted["date"].dt.to_period("M") # 按月和国家聚合新增确诊 pivot_monthly = df_sorted[df_sorted["country"].isin(countries_of_interest)].pivot_table( index="year_month", columns="country", values="daily_confirmed", aggfunc="sum" ) # 画热力图 fig, ax = plt.subplots(figsize=(10, 8)) sns.heatmap(pivot_monthly, annot=True, fmt=".0f", cmap="YlOrRd", linewidths=0.5, ax=ax) ax.set_title("重点国家月度新增确诊热力图") plt.xticks(rotation=45) plt.tight_layout() plt.savefig("figures/monthly_heatmap.png", dpi=300) plt.show()逻辑说明:先按月份切分数据,再按国家和月份分组求和。热力图的行是月份,列是国家,格子里的数字是当月总新增。这张图能快速看出某个国家疫情的“高峰期”在什么时间段,横向对比也很直观。cmap="YlOrRd"是黄色到红色的渐变,数值越大颜色越深,符合直觉。
但要注意,热力图不适合展示量级差异过大的数据。如果某个国家单月新增 500 万,另一个国家只有 1000,颜色会被最大值拉满,小数值全是白色,没有区分度。这种情况可以考虑对数据做对数变换,或者改用分位数映射。我一般会在课上跟学生强调可视化项目的一个原则:选图前先看数据分布,而不是反过来。
5. 疫情课设最容易翻车的 5 个坑:从数据结构到图表呈现的常见问题排查
5.1 坑一:日期字符串排序出错,折线图在时间轴上乱跳
现象:画图时 x 轴刻度的顺序不是按时间先后排的,而是按字符串首字母排的,比如 “10/1/20” 会排在 “2/1/20” 前面——因为字符串排序把 “1” 和 “2” 当成字符比较,结果先排 “1” 开头的所有日期,再排 “2” 开头的。
原因:读取 CSV 后日期列是字符串类型,没有通过pd.to_datetime转换成datetime64类型。Pandas 不会自动识别日期列。
解决:在处理原始数据时,立刻执行pd.to_datetime(df["date"]),转换后你可以用df.dtypes验证,date列应该是datetime64[ns]而不是object。转完之后再排序才可靠。
5.2 坑二:累计值和新增值混用,一张图里画了两个口径
现象:图例标题写着“每日新增”,但折线的数据实际是累计值,或者反过来。如果你同时画累计确诊和每日新增两条线,两者的量级差了好几倍,新增数据会被压在 x 轴上成为一条线。
原因:数据处理的中间变量被覆盖。常见场景是在清洗时执行了df["confirmed"] = df["confirmed"].diff()或类似的重赋值操作,之后想再画累计曲线,发现数据已经不可逆了。
解决:不要用df["confirmed"] = ...这种方式覆盖原始列。新增一列df["daily"] = df.groupby("country")["confirmed"].diff(),保留原始列。另外,画出图之后检查一眼纵轴最大值是否和已知数据吻合(比如中国累计确诊应该在几十万量级),比对着代码猜快得多。
5.3 坑三:中国地图显示不出来,或者省份颜色全是一个色
现象:HTML 渲染后,地图只显示灰色背景,没有任何省份有颜色;或者在 Notebook 里报错mapchinanot found。
原因:有两种可能性。第一种是 Pyecharts 没有注册中国地图的数据文件,老版本的 Pyecharts(0.5.x)需要额外安装echarts-countries-js等地图扩展包;新版本(1.0+)虽然内置了部分地图,但如果你的环境里缺少pyecharts-snapshot或网络无法加载地图脚本,也可能显示不了。第二种是数据名称匹配不到,省份名是全称、缩写或非标准写法,地图组件的 geo 数据源里找不到对应用户输入。
解决:第一步,检查 Pyecharts 版本:import pyecharts; print(pyecharts.__version__),如果是 0.5.x,去升级到 2.x。第二步,固定地图脚本的加载方式,直接把render("figures/china_map.html")生成的 HTML 文件用浏览器打开,如果地图底图是白色但省份有颜色,说明是 JS 加载问题,检查网络或换成离线包。
5.4 坑四:Melt 之后数据条数翻倍,内存爆炸
现象:原本几十兆的 CSV,经过melt后数据量暴增到几百万行,Pandas 处理明显变慢,甚至 Jupyter Notebook 直接卡死。
原因:宽表转长表会让行数等于“原本的列数 - 标识列数”乘以“原本的行数”,这是结构性正常现象。但如果你做了一次melt之后又对结果再做一次melt,或者对多个宽表反复拼接,就会指数级放大数据量。
解决:melt之前先做一次列过滤,比如只保留日期列里某几个月的数据;或者直接读取时指定usecols参数,只在内存中加载需要的列。如果是多个 CSV 文件需要合并,把它们统一 melt 后再concat,而不是反着来。
5.5 坑五:报告里的结论和图表对不上,答辩被问到数据口径就卡壳
现象:报告里写“某月疫情达到峰值”,但图表上的峰值月份其实是另一边;图表用的是全球数据,但报告正文分析的是中国情况。
原因:报告和代码不是同一个人写的,或者写报告的时间距离做分析的时间太长,自己对数据都没印象了。这是很多数据课设的痛处,项目做完就忘了细节。
解决:做分析的时候养成一个好习惯——每做完一张图,直接在 Notebook 里以 Markdown 单元格写下这张图的 2-3 个关键结论。比如“图 3.1 显示中国在 2020 年 2 月达到第一个新增峰值,之后逐步下降”。这样写报告时直接组装,不容易出现数据和结论脱离的情况。
6. 让课设多走一步:从静态图到交互式看板的十分钟迁移
静态图只能证明你会用 Matplotlib,但 Python 数据分析的历史课设千篇一律。如果想给自己增加一点竞争力,学会把静态图替换成交互式看板是一个性价比很高的方向——不增加工作量,但成品效果提升明显。
Pyecharts 已经能满足大部分交互需求,但它的图是会生成 HTML 文件,不能直接嵌入到 Jupyter Notebook 中实时联动的。如果你想在 Notebook 里拖动滑块看不同时间段的数据,或者悬浮鼠标查看具体某一天某个国家的数值,可以考虑用 Plotly Express。
import plotly.express as px # 使用前面处理好的长表数据:df_sorted 包含 country, date, confirmed 列 # 选择几个国家,避免图例过多 df_plot = df_sorted[df_sorted["country"].isin(countries_of_interest)] # 绘制交互式折线图 fig = px.line( df_plot, x="date", y="confirmed", color="country", title="主要国家累计确诊交互式趋势图", labels={"confirmed": "累计确诊人数", "date": "日期"} ) # 设置悬停信息 fig.update_traces(hovertemplate="%{y} 人<br>%{x}") # 输出为独立HTML,也可以直接在Notebook中展示 fig.write_html("figures/interactive_trend.html")这段代码的核心价值在于:color="country"自动分组上色,不需要手动循环ax.plot;鼠标悬停能看到精确数值,这份可交互的成果比一张静态 PNG 在答辩展示时能留下更深的印象。hovertemplate自定义了悬浮提示的文字格式,%{y}和%{x}分别是纵轴和横轴变量。
但有一条血泪经验要告诉你:交互式图表适合展示,不适合作为唯一的结果输出。你仍然要在报告里放静态图,因为老师打印你的报告时,HTML 文件没法直接打印。所以正确做法是报告里放静态图,答辩 PPT 里放交互式图,两者内容一致。最后,无论是代码还是报告,记得把注释写清楚。我见过太多人的代码变量名是a、b、c,图表标题还是默认的 “Figure 1”,这种代码即使画出来的图再漂亮,印象分也会被拉低。用这些技巧把课设收尾,希望帮到你。
本文还有配套的精品资源,点击获取