疫情数据可视化分析系统:Flask+ECharts从数据清洗到论文配图全攻略 简介基于Python的疫情数据可视化分析系统毕业设计资料包面向计算机及相关专业学生也适合需要完整项目参考的开发者。压缩包共747个文件大小约21.81MB文件类型覆盖后端Python脚本40个py、前端Vue组件43个vue、HTML页面、JavaScript交互脚本、CSS样式以及SQL数据库文件、docx论文文档、运行辅助脚本和SVG/GIF/PNG等可视化素材。特别是包含安装.bat、运行.bat、2-run.bat、3-build.bat等一键式批处理可简化环境部署另有多份.bak备份文件如index.html.bak、IndexMain.vue.bak保留了页面改版痕迹方便对照阅读。整体项目结构清晰从数据采集、存储到可视化展示形成闭环涵盖数据清洗、聚合与可视化映射等环节对理解系统的前后端分层、图表渲染与接口调用均有参考价值。目前已有28人学习下载整体目录划分细致适合用于毕业设计选题、系统实现参考或二次开发扩展。1. 疫情数据可视化分析系统论文项目为什么总卡在“能跑”和“能讲”之间如果你手头这个疫情数据可视化分析系统--论文.zip是打算用来做毕业设计、课程设计或者技能认证交付的那它绝不是“画几张图”那么简单。盲审老师或答辩评委真正想看的是你如何把一堆原始疫情数据变成可交互的图表并且每一张图都能支撑论文里的一句结论——这两件事分开做都不难合在一起才是这类项目的真正门槛。常见做法是采用Flask ECharts的组合Python 写数据接口和数据处理逻辑ECharts 负责渲染前端不搞复杂框架三两天就能把主流程跑通。适合的人群很明确有一定 Python 和 SQL 基础、但对完整 Web 系统不熟、需要快速拿出一套“代码 文档 数据”闭环的在校生或转开发者。这篇笔记就按这个思路把从数据清洗到论文配图的整条链路拆开讲。2. 疫情数据从哪来公开数据集与预处理管道的三个关键选择2.1 数据源选择别一上来就爬卫健委先看有没有结构化 CSV做疫情可视化第一步不是写爬虫而是确认数据形态。疫情数据的公开来源大致有三类Github 上开源的疫情数据仓库如 CSSEGISandData 的 time_series_covid19_confirmed.csv、各省市卫健委的公告页面、以及第三方聚合站导出的 Excel。我的建议是论文项目优先用 CSV 数据集原因是它自带时间维度和地区维度能直接进 Pandas不用处理反爬和页面结构变化。爬虫虽然很显技术含量但对疫情这类历史数据爬下来还要清洗半年后裁判再跑你的代码时网站可能已经改版复现性会很差。拿到 CSV 之后先做三件小事用pd.read_csv()读进来、看列名是否规范、确认日期列的格式是否统一。常见翻车点是某些数据集的日期列是2021/1/5有些是2021-01-05混合格式会在后面画折线图时直接让 x 轴错乱。2.2 清洗原则数据透视表比 VLOOKUP 式左连接更顺手疫情数据通常是“一列一个国家/省份一行一个日期”的宽表也有“三列式”的长表日期、地区、数值。两者都行但后续用 ECharts 时更推荐转成长表因为前端按地区过滤和按日期聚合都更方便。以下是把宽表转长表的常用脚本import pandas as pd df pd.read_csv(time_series_covid19_confirmed.csv) # 宽表转长表日期列全部堆叠为 date 字段 id_vars [Province/State, Country/Region, Lat, Long] date_cols [c for c in df.columns if c not in id_vars] df_long df.melt( id_varsid_vars, value_varsdate_cols, var_namedate, value_nameconfirmed ) # 日期标准化为 yyyy-mm-dd df_long[date] pd.to_datetime(df_long[date]).dt.strftime(%Y-%m-%d) df_long df_long[df_long[confirmed] 0] # 剔除无数据行 print(df_long.head()) print(df_long[date].min(), df_long[date].max())逻辑说明melt是 Pandas 里宽表转长表的标准接口id_vars是保留不变的维度字段value_vars是需要堆叠的日期列var_name和value_name分别指定新生成的日期列和数值列的名字。最后一步过滤掉确诊为 0 的行既能减小数据集体积也避免图表上出现大量前导零的平直线。参数说明date_cols的筛选逻辑是“不在 id_vars 里的列都视为日期”这要求 CSV 里只有地理坐标和日期列两类变量。如果你的数据里混入了“备注”“来源”之类的列必须先手动剔除否则melt会把它们也当成日期。2.3 人话总结清洗的终点取决于论文要讲哪三个故事不要为了清洗而清洗。你在论文里大概率只讲三个故事全国/全球总体趋势折线图、省份或国家对比柱状图/排名、地理分布地图。所以清洗阶段就按这三个故事的维度去聚合——建一个daily_confirmed.csv每日累计值、一个province_latest.csv最新一天各省份值、一个country_trend.csv多个国家/地区的日增曲线。这样后端接口和前端图表都只需读这几个小而干净的文件而不是每次查询都做全量聚合。3. 后端接口设计用 Flask 提供 4 个数据接口而不是把 CSV 直接丢给前端3.1 为什么不能直接让前端读 CSV很多初学者图省事用 ECharts 的dataset配置项直接加载本地 CSV 文件页面也能画出图。但论文答辩时评委大概率会问你“数据更新怎么做多表关联怎么做”如果答案是“手动更新CSV”印象分会打折。更关键的是浏览器直接读本地 CSV 在跨域和文件路径上有不少限制部署到服务器后经常变成“本机能跑、别人打不开”。所以我要把“数据读写”和“数据展示”拆开Flask 负责从 CSV 读数据并返回 JSON前端只对接 JSON 接口。这样数据源换了前端不用动。3.2 最小 Flask 应用与接口定义下面是我们要实现的四个接口的骨架接口路径返回内容对应论文图表/api/trend全国/全球每日累计确诊与新增折线图趋势分析/api/provinces指定日期各省份确诊数排序柱状图对比分析/api/map省份对应的数值用于地图着色中国地图/世界地图/api/summary总量、峰值、增速等统计指标论文摘要数据引用其中/api/trend是最核心的接口不仅返回累计值还要算“每日新增”——新增 当日累计 - 前一天累计这个计算放后端做比前端省事得多。来看接口实现的关键代码from flask import Flask, jsonify, request import pandas as pd app Flask(__name__) # 启动时加载一次避免每个请求都重新读文件 daily pd.read_csv(daily_confirmed.csv, parse_dates[date]) app.route(/api/trend, methods[GET]) def trend(): country request.args.get(country, China) start request.args.get(start, 2020-01-01) end request.args.get(end, 2021-12-31) df daily[(daily[country] country)] df df[(df[date] start) (df[date] end)].sort_values(date) # 计算每日新增 df[new] df[confirmed].diff().fillna(0) return jsonify({ dates: df[date].dt.strftime(%Y-%m-%d).tolist(), confirmed: df[confirmed].tolist(), new: df[new].tolist() }) if __name__ __main__: app.run(debugTrue, port5000)逻辑说明parse_dates在read_csv阶段就把日期列转成datetime64这是为了后面的切片比较和diff()计算都能按时间语义走。request.args.get给接口留了参数位答辩时你可以现场演示“只看 2020 年上半年”“只看美国”之类的交互。diff()是 Pandas 专门做差分计算的函数.fillna(0)把第一行的空值补成 0避免接口返回NaN导致前端渲染异常。参数说明diff()默认按行索引当前位置与前一行做差一定保证sort_values(date)在diff()之前执行。如果数据里有重复日期或者某一行的累计值比上一行小部分数据集修正后会出现负数新增这段代码就会计算出负值。用真实数据时最好在diff()后面加一行df[new] df[new].clip(lower0)把修正导致的负新增归零。3.3 多条件聚合接口的二三事/api/provinces的写法思路类似区别在于多了按日期过滤和按数值排序app.route(/api/provinces, methods[GET]) def provinces(): date request.args.get(date, 2021-07-01) df pd.read_csv(province_latest.csv, parse_dates[date]) df df[df[date] date] df df.sort_values(confirmed, ascendingFalse).head(20) return jsonify({ names: df[province].tolist(), values: df[confirmed].tolist() })这里的province_latest.csv是清洗阶段就生成好的文件字段包括date、province、confirmed。如果不在请求里传日期接口默认查2021-07-01前端拿到数据后按名字与数值一一对应即可。这儿要特别注意province的命名必须和 ECharts 地图的name完全一致比如“广西壮族自治区”不能简写成“广西”。如果需要前端做映射会非常痛苦这个坑在 4.3 节再展开。4. ECharts 可视化落地折线图、地图、动态排序三个必做图表4.1 接线层在 HTML 里用fetch调用后端接口前端的骨架非常简单一个index.html加一个app.js。核心逻辑是用fetch向刚才的 Flask 接口拿数据再塞进 ECharts 的setOption。这是最不容易写错的方式——先在浏览器控制台看接口返回的 JSON 对不对再去调图表。!-- index.html 局部 -- div idtrend-chart stylewidth: 100%; height: 400px;/div div idmap-chart stylewidth: 100%; height: 500px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script script srcapp.js/script4.2 折线图的配置双 y 轴处理“累计”和“新增”量级差疫情数据里累计确诊可能是几百万每日新增只有几千同轴显示会导致新增曲线被压成一条直线。这里用双 y 轴左轴给累计值右轴给新增值两个系列各自绑定对应的轴。具体配置写法// app.js 节选 async function initTrendChart() { const res await fetch(/api/trend?countryChinastart2020-01-01end2021-12-31); const data await res.json(); const chart echarts.init(document.getElementById(trend-chart)); chart.setOption({ tooltip: { trigger: axis }, legend: { data: [累计确诊, 每日新增] }, xAxis: { type: category, data: data.dates }, yAxis: [ { type: value, name: 累计确诊 }, { type: value, name: 每日新增, splitLine: { show: false } } ], series: [ { name: 累计确诊, type: line, yAxisIndex: 0, data: data.confirmed, smooth: true }, { name: 每日新增, type: line, yAxisIndex: 1, data: data.new, smooth: true, areaStyle: { opacity: 0.2 } } ] }); } initTrendChart();逻辑说明yAxis写成一个数组就是定义多坐标轴series里的yAxisIndex: 0和yAxisIndex: 1决定数据走哪条轴。areaStyle给新增曲线加了半透明填充视觉上强调波动。trigger: axis让悬浮提示在同一个 x 坐标点上同时显示两条曲线的值这是趋势类图表最常用的交互。参数说明smooth: true会让折线变圆润但如果时间粒度特别细或数据本身噪声大建议关掉 smooth否则会掩盖真实拐点。数据集如果跨年建议在接口层做好 start/end 过滤避免前端一次性渲染 600 多个点导致交互卡顿。4.3 地图图表注册 GeoJSON 之后再匹配数据疫情项目里最有视觉冲击力的是中国地图。ECharts 5 本身不带地图数据需要先加载 GeoJSON 再注册地图。这里有个非常隐蔽的坑GeoJSON 里properties.name是“北京市”而后端返回的数据里如果写的是“北京”地图上就永远只显示一个高亮块其余省份全部空白。先注册地图再做数据映射// app.js 节选 - 地图部分 import chinaJson from ./china.json; // 需要放在项目本地不能用 CDN echarts.registerMap(china, chinaJson); async function initMapChart() { const res await fetch(/api/map?date2021-07-01); const data await res.json(); const chart echarts.init(document.getElementById(map-chart)); chart.setOption({ tooltip: { trigger: item }, visualMap: { min: 0, max: 50000, left: left, text: [高, 低], inRange: { color: [#e0f3f8, #abd9e9, #4575b4] } }, series: [ { name: 确诊数, type: map, map: china, roam: true, data: data.items // [{name: 广东省, value: 1000}, ...] } ] }); }逻辑说明registerMap是全局注册注册一次直接可复用。visualMap的min和max需要参考实际数据量级来定如果写死 50000 但实际最大只有 3000整个地图颜色会几乎不变毫无区分度。我用的是从后端动态读取 max 值的方式// 动态计算最大值 const maxVal Math.max(...data.values); // setOption 时再用 maxVal 替换 max参数说明roam: true允许用户拖拽和缩放地图答辩演示时放大某个省份看细节会显得很专业。ECharts 的颜色范围建议用 3~4 段渐变色就够了不要炫技式地搞 10 段低值区域的省份会变成深色视觉上也会误导读者。4.4 动态排序图论文“最靓的仔”和它的两个前提如果想在论文里加一页“疫情发展过程的动态排名”可以用 ECharts 的timeline组件或纯setInterval驱动数据更新。这里我推荐轮播柱状图每天更新一次排名配合sort: desc属性做倒序排列。整体实现逻辑是后端一次性返回“每天各省份确诊数”的长表前端按天切片循环更新 series。这个图表有很好的演示效果但有两个前提必须满足一是数据必须覆盖一个完整时间段比如疫情开始到论文定稿前二是省份数量建议控制在 10 个以内超过 15 个排名变化观众根本看不清。5. 避坑指南疫情数据可视化项目最容易翻车的 5 个细节5.1 中文乱码与编码问题导致接口返回“???”现象后端返回的 JSON 里省份名全是问号或乱码。原因绝大多数是 Windows 环境下 Pandas 读 CSV 时默认按 GBK 解析而数据文件是 UTF-8 编码或者 Flask 返回 JSON 时没有声明 UTF-8。解决在read_csv里显式指定编码同时在 Flask 接口上设置响应头df pd.read_csv(province_latest.csv, encodingutf-8) # 接口返回统一走 make_response 设置 charset from flask import make_response resp make_response(jsonify(data)) resp.headers[Content-Type] application/json; charsetutf-85.2 时间轴的钟摆效应数据点前后顺序错乱现象折线图横轴上的时间顺序不对先显示 2021 年后显示 2020 年。原因后端按字符串排序导致的“字典序”问题2021-01-01排在2020-12-31后面本质上没错但如果数据里有2020/1/1这种非标准格式两种格式混在一起就会乱。解决清洗阶段强制统一格式不要只strftime要先pd.to_datetime再格式化。判断是否洗干净的验证方法是打印df[date].dtype结果必须是datetime64[ns]而不能是object。5.3 地图上总是少几个省现象中国地图上某些省份始终不着色或者鼠标悬浮时显示不了名字。原因后端返回的省份名与 GeoJSON 里的name不一致。比如数据里写“内蒙古”GeoJSON 里写“内蒙古自治区”。解决提供一个映射表统一处理而不是去改 GeoJSON。写一个normalize_province_name()函数把简称映射到全称province_map { 北京: 北京市, 内蒙古: 内蒙古自治区, # 其余省略 } def normalize_province_name(name): return province_map.get(name, name)5.4 论文里的图表和正文数据对不上现象论文里写“截至 2021 年 7 月 1 日全国累计确诊 X 万例”但论文附的图里数值明显不对。原因数据集在论文写作期间更新过或者接口参数默认值不一样导致前端看到的和论文图截的不是同一批数。解决答辩前固定数据版本。把接口的默认参数写入清洗阶段的config.yaml论文里引用的每个数字都在图下方标注“数据来源某某数据集截至某日”。更稳妥的做法是把生成论文图表的代码单独抽一个paper_charts.py每次跑完都输出一个带日期的文件确保截图和数据同步。5.5 代码压缩包无法直接跑起来现象评审老师解压 zip 后运行程序Flask 启动报错缺依赖或者地图一片空白。原因requirements.txt没有写全或者 ECharts 用了 CDN 链接但答辩机房没外网。解决requirements.txt里至少写 Flask、pandas、numpy 三个地图的 GeoJSON 文件必须放本地ECharts 也下载一份 min.js 放静态目录。这样确保断网环境也能完整展示。6. 论文加分的进阶技巧从“图表展示”升级为“分析结论”最后说一个拉开差距的技巧把可视化从“展示现状”升级成“验证结论”。疫情数据的价值不在图多而在能不能支撑你论文里的决定性语句。一个常用的进阶方案是增速对比图取“每日新增”再做 7 日滑动平均然后把不同国家/区域的滑动平均曲线叠在一张图上面。滑动平均的意义在于消除周末数据滞后带来的锯齿状波动让趋势肉眼可见。实现上只需要在 Flask 接口里加一段df[new_smooth] df[new].rolling(window7, min_periods3).mean()这行代码算出来的new_smooth序列可以直接以另一条 series 加进图表。但不要把它做成文章里的大图表而是做成辅助图放在趋势图下方作为“疫情高峰拐点”的论据。论文的文字表述可以是“2020 年 2 月中旬起7 日平均新增数持续下降表明第一波传播得到控制”——这就是一张图支撑一个判断的分析型写法。第二个进阶是加一个简单的排名变换动画作为答辩开场演示。后端提供一个/api/topn/daily接口返回每天排名前 10 的省份与数值前端用setInterval每隔 300 毫秒更新一次柱状图数据。这个动画做出来的整体效果非常抓人评审在答辩前 30 秒就能看到你的系统不是死图。我自己在带类似项目时见过太多人把时间花在改图表颜色和多加几张饼图上却很少有人在“数据结论”上做文章。实际上你只要能把数据、图表、文字三者串成一条完整论证链——数据说明什么、图表证明了什么、论文里怎么表述——这比任何炫技都有效。技术方面代码能跑通只是第一步加上“数据版本固定”“断网可演示”“参数可交互”这三件事才是论文项目最该有的底气。希望帮到你。本文还有配套的精品资源点击获取