简介:这套基于Python的中美疫情数据可视化分析与展示源码,面向需要快速上手数据分析与可视化项目的学习者、竞赛备赛者以及对疫情趋势感兴趣的研究者,完整展示了从读取Excel/CSV数据、用Python进行数据处理与预测,到生成HTML交互页面的全链路过程。压缩包共19个文件,核心包括6个Python脚本、7个HTML页面、4个Excel数据源以及CSV与txt说明文件,整体仅145KB,结构清晰且便于按模块研读。脚本覆盖国内分省疫情分析、中美疫情对比和未来14天新增趋势预测等典型场景;HTML页面则呈现全球/国内疫情总览、中美对比与预测结果,读者可直接运行源码、复现图表,并在此基础上替换数据或调整算法开展二次开发。已有296人学习,适合想通过完整项目案例提升Python数据分析和可视化能力的人群。
1. 疫情数据可视化项目:20个文件把Python分析和HTML展示串成了一条线
假设你手上有一份疫情CSV,不光想画折线图,还要输出一份带预测、可以传给别人的网页报告,这时候单靠Excel透视表是撑不住的。这个基于Python的中美疫情数据可视化项目就是一个完整的源码包:6个Python脚本负责数据清洗、分析和预测,7个HTML页面负责展示,中间用CSV和Excel文件衔接。它解决了“数据从哪来、怎么算、怎么展示”三个问题,输出的每一个HTML都能单独用浏览器打开,也适合拿去跟同伴讲解数据趋势。适合正在学Python数据分析、看完教程想动手做一个完整项目的人,也适合手里已有数据、想快速跑通可视化链路并做二次开发的人。下面我从文件结构开始,逐个把脚本逻辑和踩坑点拆开讲。
2. 项目全景:20个文件的分工与数据从CSV到Excel的流转
2.1 四类文件的分工:Python脚本是发动机,HTML是仪表盘
这20个文件乍一看很杂,但归类后只有四类,各管一段。先把文件清单捋出来,后续脚本分析才不会迷路。
| 类别 | 文件 | 职责 |
|---|---|---|
| 原始数据 | history.csv | 全球疫情历史序列,包含日期、国家/地区、累计确诊等字段 |
| 中间数据 | China.xlsx、America.xlsx、China_province.xlsx、world.xlsx | 按国家或省份拆好的结构化数据 |
| Python脚本 | World.py、China——province.py、Compare.py、China_xz_predict.py、America_xz_predict.py、pr.py | 读取、清洗、聚合、对比、预测、输出HTML |
| 展示页面 | index.html、全球疫情情况.html、国内疫情情况.html、中美疫情对比.html、中国未来14天新增预测.html、美国未来14天新增预测.html、疫情预测分析.html | 可视化终端,直接供阅读 |
| 辅助文件 | readme.txt、upload.zip | 使用说明和打包备份 |
Python脚本是发动机,负责从CSV和Excel读入数据、算累计或新增指标、再用模型预测未来14天;HTML是仪表盘,所有分析结果最终都落到独立的网页文件里。这个项目最值得学习的点在于:脚本跑完不是打印几行数字就结束,而是生成一份能发给别人的静态报告,这也是企业级数据可视化项目的常规思路。
2.2 清洗路径:pr.py把history.csv拆成China.xlsx和America.xlsx
history.csv是原始数据源,里面通常包含全球所有国家和地区的每日记录。如果直接拿这个文件去画中美对比图,每次都要做筛选,既慢又容易出错。所以第一步应该把原始CSV处理成每个项目脚本都能直接读的中间Excel文件,这个任务正好由pr.py承担。
# pr.py 数据清洗核心逻辑 import pandas as pd df = pd.read_csv('history.csv') # 先摸清列名,防止大小写或空格不一致 print(df.columns.tolist()) # 统一列名:去掉首尾空格,统一英文小写 df.columns = [col.strip().lower() for col in df.columns] # 如果CSV里按省/州拆行,需要先按国家聚合 # 假设关键列是 country、date、confirmed daily = (df.groupby(['date', 'country'], as_index=False)['confirmed'] .sum() .sort_values('date'))这里有两个关键操作:columns.tolist()把原CSV的字段名打出来看一眼,这一步能节约后面大量的排查时间;groupby(['date', 'country'])把同一国家多个省份的记录合并成国家维度,as_index=False保证分组后country仍然是列而不是索引。聚合完之后,再用条件筛选拆出中美两国存成单独文件。
# 接上面,拆出中美数据并落地 cn = daily[daily['country'] == 'China'].copy() us = daily[daily['country'] == 'US'].copy() # 写回Excel,显式指定openpyxl引擎 cn.to_excel('China.xlsx', index=False, engine='openpyxl') us.to_excel('America.xlsx', index=False, engine='openpyxl') # 整体数据也存一份,给World.py用 daily.to_excel('world.xlsx', index=False, engine='openpyxl')注意to_excel里我用engine='openpyxl'显式指定了写xlsx文件的引擎。pandas默认行为在不同版本里不一致,不写这个参数,有时会静默降级到旧版xlwt引擎,生成的文件实际上是损坏的。等你再read_excel读回来,报错信息会指向文件格式,根本猜不到是写的时候出了问题。
China_province.xlsx是省级粒度数据,由China——province.py读取。这个脚本的文件名里有个中文全角破折号“——”,这是和Windows、Linux文件系统都能兼容的命名,但在代码里引用路径时极容易打错,后续第5章我会专门讲这个坑。
2.3 index.html的入口作用:页面之间的导航关系
index.html在整个项目里是目录页,作用是把其他六个HTML页面串起来。它的内容不复杂,核心就是一组超链接。
<!-- index.html 目录导航示意 --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>新冠数据可视化分析</title> </head> <body> <h1>新冠疫情数据可视化分析</h1> <ul> <li><a href="全球疫情情况.html">全球疫情情况</a></li> <li><a href="国内疫情情况.html">国内疫情情况</a></li> <li><a href="中美疫情对比.html">中美疫情对比</a></li> <li><a href="中国未来14天新增预测.html">中国未来14天新增预测</a></li> <li><a href="美国未来14天新增预测.html">美国未来14天新增预测</a></li> <li><a href="疫情预测分析.html">疫情预测分析</a></li> </ul> </body> </html>这些页面文件名都是中文,你本地双击index.html用file://协议打开,部分浏览器可能会因为编码差异导致链接找不到文件。我的习惯是把整个项目文件夹丢进VS Code,然后用Live Server插件起一个本地服务再访问,或者用命令行python -m http.server 8080,这样中文路径问题基本不会出现。
3. 核心脚本拆解:14天预测与中美对比的算法逻辑
3.1 China_xz_predict.py:线性回归预测未来14天新增
预测脚本是这堆文件里技术含量最高的。疫情数据本质是时间序列,但这里用的并不是LSTM这类复杂模型,而是线性回归——把日期转换成连续整数天数,再用“天数”去拟合“新增确诊”。为什么选这么简单的模型?因为疫情数据波动大、样本量有限,复杂模型在短序列上很容易过拟合,线性回归反而稳定、可解释,出图也自然。
# China_xz_predict.py 核心推理逻辑 import pandas as pd import numpy as np from sklearn.linear_model import LinearRegression # 读取已清洗好的中国数据 df = pd.read_excel('China.xlsx', engine='openpyxl') df['date'] = pd.to_datetime(df['date']) df = df.sort_values('date') # 日期转成距起始日的整数天数,作为回归特征 df['day'] = (df['date'] - df['date'].min()).dt.days X = df['day'].values.reshape(-1, 1) y = df['new_cases'].values # 训练线性回归模型 model = LinearRegression() model.fit(X, y) # 构造未来14天的天数序列 last_day = df['day'].max() future_days = np.arange(last_day + 1, last_day + 15).reshape(-1, 1) pred = model.predict(future_days) # 生成对应日期,带格式输出 future_dates = pd.date_range( start=df['date'].max() + pd.Timedelta(days=1), periods=14 ) result = pd.DataFrame({ 'date': future_dates.strftime('%Y-%m-%d'), 'pred_new_cases': np.round(pred, 1) }) print(result)这里有两个参数要特别说明。第一是X.reshape(-1, 1),sklearn的线性回归要求特征矩阵是二维的,哪怕只有一列特征也必须显式变成行向量,很多人第一次跑就挂在shape不匹配上。第二是periods=14,这个数字直接控制预测窗口长度,改成7就是未来一周预测,改成30就是月度预测。
3.2 America_xz_predict.py与数据平滑处理
America_xz_predict.py的结构和中国版本几乎一致,只是读取America.xlsx、输出美国未来14天新增预测.html。但实际跑的时候,美国数据有一个典型问题:单日新增波动非常大,周末数据偏低、周一反弹是常态。直接用原始序列喂给线性回归,拟合出来的直线会被噪声带偏。
常见做法是在回归之前先做一次平滑,用7天移动平均把周末效应抹掉。
# America_xz_predict.py 数据平滑逻辑 df = pd.read_excel('America.xlsx', engine='openpyxl') df['date'] = pd.to_datetime(df['date']) df = df.sort_values('date') # 7天移动平均,min_periods=1保证前6天不出现NaN df['new_cases_smooth'] = ( df['new_cases'] .rolling(window=7, min_periods=1) .mean() ) df['day'] = (df['date'] - df['date'].min()).dt.days X = df['day'].values.reshape(-1, 1) y = df['new_cases_smooth'].values # 后续回归流程与China版一致rolling(window=7)是移动平均的窗口大小,min_periods=1表示窗口内最少有1个有效值就算平均,否则前6天会全部变成NaN,回归就废了。如果你跑出来的预测线前段是断的,先检查这里有没有写min_periods。
3.3 Compare.py与pr.py:中美对比的指标选择
Compare.py做的事情是把China.xlsx和America.xlsx按日期合并,然后算对比指标。中美两国人口基数差了四倍多,直接拿累计确诊绝对值画双线图,美国的线会高高在上,视觉上完全看不出中国的增长趋势。所以对比脚本里一定要做归一化。
# Compare.py 对比指标计算 import pandas as pd cn = pd.read_excel('China.xlsx', engine='openpyxl') us = pd.read_excel('America.xlsx', engine='openpyxl') cn = cn[['date', 'confirmed']].rename(columns={'confirmed': 'cn_confirmed'}) us = us[['date', 'confirmed']].rename(columns={'confirmed': 'us_confirmed'}) # 内连接,只保留两边都有数据的日期 merged = pd.merge(cn, us, on='date', how='inner') # 归一化:每百万人口确诊数 CN_POP = 1400000000 US_POP = 331000000 merged['cn_per_million'] = merged['cn_confirmed'] * 1000000 / CN_POP merged['us_per_million'] = merged['us_confirmed'] * 1000000 / US_POPhow='inner'这个参数决定了合并策略。内连接只保留双方共有的日期,如果某天只有中国数据没有美国数据,那一行会被丢弃。相比外连接,内连接不会产生NaN,但代价是样本量可能减少。美国疫情数据公布节奏不稳定,用内连接是更稳的选择。人口常量我直接写死在脚本里,如果你要改成别的国家对比,记得同步替换这两个常量。
pr.py在项目里承担的是数据预处理职责,除了前面拆CSV的操作,它还会对日期格式做统一处理。原始CSV里日期可能有2020-1-5、2020/1/5、20200105三种格式混着,统一用pd.to_datetime加format参数解析最省心。
4. 可视化输出:从DataFrame到ECharts的HTML渲染
4.1 全球、国内、中美三个维度页面怎么生成
7个HTML页面覆盖三个维度:全球、国内、中美对比。生成方式本质上都是同一个套路——脚本里先准备好数据,再拼HTML模板,最后渲染出图表。
World.py读取world.xlsx,按国家聚合算Top排行榜,输出到全球疫情情况.html;China——province.py读取China_province.xlsx,渲染国内省级分布,输出到国内疫情情况.html;Compare.py读两国数据,输出中美疫情对比.html。模板里用到的图表库是ECharts,这也是目前数据可视化项目里最主流的选型。
# 通用HTML模板渲染文件 def render_html(title, chart_div_id, option_json, output_file): html_content = f""" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>{title}</title> <script src="./static/echarts.min.js"></script> </head> <body> <div id="{chart_div_id}" style="width: 900px; height: 600px;"></div> <script> var chart = echarts.init(document.getElementById('{chart_div_id}')); chart.setOption({option_json}); </script> </body> </html> """ with open(output_file, 'w', encoding='utf-8') as f: f.write(html_content) print(f"已生成 {output_file}")这个模板的核心是把ECharts的option配置以JSON字符串形式嵌入script标签。chart.setOption是ECharts的入口方法,所有配置都放在这个对象里。注意script的src我写的是./static/echarts.min.js,意味着你需要在项目下建一个static目录并放一份ECharts库文件,这样才能离线打开页面。
4.2 预测结果在HTML里如何区分实际值与预测值
中国未来14天新增预测.html和美国未来14天新增预测.html这两个页面,图表上同时有两条线:历史实际值和未来预测值。前端要区分它们,靠的不只是颜色,而是数据里带一个标记字段。
# 构造带标记的历史+预测拼接数据 import pandas as pd df = pd.read_excel('China.xlsx', engine='openpyxl') # 取最近30天实际新增 history = df.tail(30)[['date', 'new_cases']].copy() history.rename(columns={'new_cases': 'value'}, inplace=True) history['is_predict'] = False # 预测结果构造为DataFrame future = pd.DataFrame({ 'date': future_dates, 'value': pred, 'is_predict': True }) # 拼接并转JSON combined = pd.concat([history, future], ignore_index=True) data_for_chart = { 'dates': combined['date'].astype(str).tolist(), 'values': combined['value'].round(0).tolist(), 'isPredict': combined['is_predict'].tolist() }is_predict这个布尔字段是关键,前端拿到它之后,可以给预测线段设置成虚线、浅色,和实际值形成视觉区分。pd.concat按行拼接两个结构相同的DataFrame,ignore_index=True保证行号重新从0开始,否则图表数据会被旧索引打乱。
4.3 JSON注入:ensure_ascii和编码相关的坑
按上面的方式把data_for_chart变成JSON字符串时,有一个容易被忽视的细节:Python的json.dumps默认会把中文转成\uXXXX转义序列,HTML源码里看起来全是反斜杠,虽然也能运行,但可读性很差,排查问题的时候非常痛苦。
import json # ensure_ascii=False让中文原样输出,HTML里直接可读 option_json = json.dumps(data_for_chart, ensure_ascii=False)这样生成的HTML里日期和中文标题都保留原始字符,浏览器解析毫无压力。另一个点是写HTML文件时open(output_file, 'w', encoding='utf-8')必须带上encoding参数,否则Windows默认的gbk编码会把中文字符写坏,页面打开就是一堆乱码。这两个编码问题我建议养成固定习惯,写文件永远指定utf-8。
5. 避坑指南:跑通这个项目最容易翻车的五个地方
5.1 现象:FileNotFoundError,文件明明就在脚本同目录
原因:China——province.py这个文件名里是中文全角破折号“——”,代码里如果引用的是半角“--”或直接复制路径时混入全角字符,Python会把文件路径当成另一个字符串去查找,自然找不到。
解决:进入项目目录执行ls或dir命令,先确认文件实际名字,再复制粘贴进代码。更彻底的做法是改文件名为ASCII安全的China_province.py,然后全局替换所有引用。从那以后我接手的每个项目,文件名一律不用中文和全角符号。
5.2 现象:read_excel报错“Missing optional dependency 'openpyxl'”
原因:pandas读.xlsx文件需要安装openpyxl库,你的虚拟环境里只装了pandas,缺了底层依赖。另一个关联场景是写Excel时没指定engine,pandas偷偷用旧引擎生成损坏文件。
解决:在项目根目录执行pip install openpyxl,然后代码中统一写成pd.read_excel('China.xlsx', engine='openpyxl')和to_excel(..., engine='openpyxl')。显式声明引擎虽然啰嗦,但避免了版本行为差异导致的各种玄学报错。
5.3 现象:HTML页面打开后图表空白,控制台报ECharts加载失败
原因:页面模板里script标签引用的是远程CDN路径,比如https://cdn.jsdelivr.net/npm/echarts,别忘了有些内网环境或者本机没有外网,加载就会超时失败,图表区域自然空白。
解决:把ECharts库下载后放到项目的static目录,script标签改成相对路径./static/echarts.min.js。用离线文件虽然会占用一点项目体积,但保证在任何网络环境下打开都秒出图。这也是从“自己机器能跑”到“交付到别人机器也能跑”的一道分水岭。
5.4 现象:预测曲线在某个点突然断崖式下跌或跳变
原因:原始CSV里有缺失日期,比如某一天中国数据没更新,groupby聚合之后那一整天就消失了。回归模型的X是连续整数天数,y却是跳跃的,中间空缺一天就会让拟合线产生巨大偏移。
解决:读取后做一次日期补全,把缺失日期自动填充为零值。
df['date'] = pd.to_datetime(df['date']) df = df.set_index('date').resample('D').sum().reset_index() # resample('D')按天重采样,缺失日期的所有列自动补0resample('D')是pandas时间重采样的核心方法,sum()决定缺失期怎么聚合。这个操作放在读文件和构造day列之间,预测结果会平滑很多。
5.5 现象:脚本跑完了、HTML文件也更新了,浏览器打开还是旧数据
原因:浏览器缓存了上一次的HTML文件,服务器返回的是缓存内容,你的新文件根本没被加载。
解决:开发阶段在浏览器按Ctrl+F5强制刷新,或者在HTML的head区域加入一行禁用缓存的meta标签,发布时再删掉。
<meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache">这组标签只对浏览器本地缓存生效,不会影响服务器端配置,适合开发调试阶段快速验证效果。
6. 把预测脚本改成常用工具:数据源替换与图表演进技巧
6.1 替换成实时数据源
项目里用的history.csv是静态文件,但实际使用中数据每天都会更新。改动最小的方法是用Python直接去公开接口拉数据,再落成本地CSV,替代原来的读文件逻辑。
# 从接口拉数并更新本地缓存 import requests import pandas as pd resp = requests.get('https://your-data-api.example.com/covid') data = resp.json() df = pd.DataFrame(data) df.to_csv('history.csv', index=False, encoding='utf-8') print(f"数据已更新,共 {len(df)} 行")requests.get默认会阻塞直到响应返回,接口如果比较慢,建议加超时参数timeout=30,避免脚本长期卡住。拿到JSON之后转DataFrame这步是通用的,不管接口返回什么结构,先用normalize或json_normalize展平嵌套字段再落盘。
6.2 改图表类型的技巧
如果不想用折线图,想换成柱状图或者面积图,只需要改ECharts的option配置。ECharts的series类型字段type决定图表形态,从line改成bar即可。
// 图表配置中的series部分 series: [{ name: '中国新增', type: 'bar', // line改成bar就是柱状图 data: [120, 200, 150, 80, 70] }]改图表类型时,颜色、坐标轴、图例这些配置都不用动,ECharts会自动适配。如果你要做大屏展示,series里再加一个smooth: true,折线会变成平滑曲线,视觉上专业很多。
6.3 用本地服务器固定访问方式
以前我总喜欢双击HTML文件直接看,后来发现文件协议下ECharts的字体加载和异步请求经常出问题,就改成固定起一个本地服务器。
# 在项目根目录起服务,端口8080 python -m http.server 8080这个命令会把当前目录作为根目录开启HTTP服务,局域网内其他电脑可以访问你的IP加端口直接看到所有HTML页面,比U盘拷贝给同事方便得多。从那以后我每次交付可视化项目都会顺带在readme里写一句最简单的启动命令,希望帮到你。
本文还有配套的精品资源,点击获取