☰
Python+ECharts疫情地图实战:数据清洗与交互可视化
2026/10/13 17:39:19 网站建设 项目流程

1. 数据可视化项目的整体设计思路

1.1 为什么选择 Python + ECharts 这条技术路线

做数据可视化这些年,我试过不少方案。Excel 做图快但交互性差,Tableau 效果好但部署成本高,D3.js 灵活但学习曲线陡峭。最后落到 Python + ECharts 这个组合上,原因很实在:Python 负责数据处理,ECharts 负责前端渲染,两者各干各擅长的事,中间用 JSON 数据打通,逻辑清晰,维护起来也省心。

具体到这个项目,核心需求是把一份结构化的疫情统计数据,转化成一张可交互的全国地图。用户打开页面后,能看到各省份用不同颜色标注的疫情严重程度,鼠标悬停可以查看具体数值,还能通过时间轴切换不同日期的数据。这种需求用静态图片根本满足不了,必须上交互式图表。

ECharts 的地图组件(geo/map)在这方面非常成熟。它内置了全国各省份的地理坐标数据,你只需要把省份名称和对应的数值传进去,它就能自动完成颜色映射、图例生成、鼠标交互这些工作。而 Python 的角色是把原始数据清洗成 ECharts 能识别的格式——通常是[{name: '省份', value: 数值}, ...]这样的列表结构。

提示:ECharts 从 5.0 版本开始对地图组件的引入方式做了调整,地图数据不再默认打包在主文件里,需要单独引入对应的地图 JSON 文件。这一点在后面实操部分会详细说明。

1.2 项目涉及的核心技术点拆解

这个项目看起来只是"画一张地图",但真正动手做起来,涉及的技术环节比想象中多。我把它拆成四个核心模块:

数据获取与清洗。原始数据往往来自公开的统计表格或 CSV 文件,格式五花八门。有的用"广东省"有的用"广东",有的把直辖市单独列出来,有的混在一起。ECharts 的地图数据对省份名称有严格要求,必须和内置的 geoJSON 中的名称完全匹配,否则那个省份就会显示为灰色(没有数据)。所以数据清洗这一步是基础中的基础。

Python 数据处理。用 pandas 读取数据,做筛选、聚合、格式转换,最终输出一个标准的 JSON 文件。这一步的关键是理解 ECharts 需要的数据结构,以及如何处理缺失值和异常值。

ECharts 配置与渲染。这是前端部分,需要配置 visualMap(视觉映射)、tooltip(提示框)、title(标题)、timeline(时间轴)等组件。visualMap 是地图可视化的灵魂,它决定了数值如何映射到颜色。

页面集成与部署。把生成的 HTML 文件放到 Web 服务器上,或者直接本地打开。如果需要动态更新数据,还要考虑前后端的数据接口设计。

1.3 数据可视化中"准确"与"直观"的平衡

做疫情地图这类项目,有一个容易被忽视的问题:颜色的选择会直接影响读者对数据的感知。我见过不少项目直接用红绿灯配色——绿色代表安全,红色代表危险。这种配色在直觉上没问题,但在实际数据分布不均匀的情况下,会导致大部分地区都是浅色,只有少数几个地区是深红色,视觉冲击力过强,反而掩盖了数据本身的层次。

我的做法是:先用 pandas 看一下数据的分布情况,计算分位数,然后根据分位数来设置 visualMap 的分段阈值。比如数据集中在 0-100 之间,但有个别值达到 1000,那就不能简单地用线性映射,而应该用分段映射,把 0-10、10-50、50-100、100-500、500 以上分成五档,每档给一个颜色。这样既能体现差异,又不会让极端值主导整个视觉。

另外,颜色方案要考虑到色盲用户。红绿色盲是最常见的色觉障碍,如果只用红绿两色来区分高低,这部分用户就完全看不懂了。ECharts 的 visualMap 支持自定义颜色数组,我一般会用从浅蓝到深红的渐变色系,或者从浅黄到深橙的暖色系,这样对色觉障碍用户更友好。

2. 数据准备与清洗的实操细节

2.1 数据来源与格式规范

假设我们拿到了一份 CSV 格式的疫情统计数据,字段包括:日期、省份、累计确诊、新增确诊、治愈、死亡等。这份数据可能来自公开的统计报表,也可能是从网页上抓取下来的。不管来源如何,第一步都是先把它读进 pandas,看看长什么样。

import pandas as pd df = pd.read_csv('epidemic_data.csv', encoding='utf-8') print(df.head()) print(df.dtypes) print(df.shape)

这三行代码看起来简单,但能帮你快速判断数据质量。head()看前几行,确认列名和数据类型;dtypes看每列的数据类型,如果"累计确诊"是 object 类型而不是 int,说明里面混了非数字字符;shape看数据量,判断是否有缺失。

我踩过的一个坑是:有些 CSV 文件用 GBK 编码保存,用 utf-8 读取会报错。这时候要么用encoding='gbk',要么用chardet库自动检测编码。还有一种情况是文件开头有 BOM 字符,导致第一列列名前面多了一个不可见字符,用encoding='utf-8-sig'可以解决。

2.2 省份名称标准化处理

这是整个数据清洗中最关键的一步。ECharts 内置的中国地图 geoJSON 中,省份名称是固定的,比如"北京市"、"广东省"、"内蒙古自治区"、"广西壮族自治区"、"西藏自治区"、"宁夏回族自治区"、"新疆维吾尔自治区"、"香港特别行政区"、"澳门特别行政区"。

但原始数据里往往不是这么写的。常见的问题包括:

  • 用简称:"北京"而不是"北京市","广东"而不是"广东省"
  • 用旧称:"内蒙古"而不是"内蒙古自治区"
  • 直辖市混用:"重庆"和"重庆市"同时出现
  • 港澳台数据缺失或名称不一致

我的处理方式是建一个映射字典,把所有可能的变体都映射到标准名称:

province_mapping = { '北京': '北京市', '天津': '天津市', '上海': '上海市', '重庆': '重庆市', '河北': '河北省', '山西': '山西省', '辽宁': '辽宁省', '吉林': '吉林省', '黑龙江': '黑龙江省', '江苏': '江苏省', '浙江': '浙江省', '安徽': '安徽省', '福建': '福建省', '江西': '江西省', '山东': '山东省', '河南': '河南省', '湖北': '湖北省', '湖南': '湖南省', '广东': '广东省', '海南': '海南省', '四川': '四川省', '贵州': '贵州省', '云南': '云南省', '陕西': '陕西省', '甘肃': '甘肃省', '青海': '青海省', '台湾': '台湾省', '内蒙古': '内蒙古自治区', '广西': '广西壮族自治区', '西藏': '西藏自治区', '宁夏': '宁夏回族自治区', '新疆': '新疆维吾尔自治区', '香港': '香港特别行政区', '澳门': '澳门特别行政区' } df['省份'] = df['省份'].map(province_mapping).fillna(df['省份'])

注意最后那个fillna(df['省份']),它的作用是:如果某个省份名称不在映射字典里,就保留原值,而不是变成 NaN。这样即使有遗漏,也能在后续检查中发现,而不是直接丢失数据。

提示:处理完映射后,一定要做一次校验。用set(df['省份']) - set(echarts_provinces)找出数据中有但 ECharts 地图中没有的省份名称,以及set(echarts_provinces) - set(df['省份'])找出地图中有但数据中没有的省份。前者会导致数据无法显示,后者会导致地图上出现灰色区域。

2.3 缺失值与异常值的处理策略

疫情数据中常见的缺失情况有两种:一是某个省份某天没有报告数据,二是数据采集时漏掉了某些字段。对于第一种情况,如果只是个别日期缺失,可以用前一天的数值填充(因为累计确诊是单调递增的);如果是连续多天缺失,就需要考虑是否用插值法或者直接标记为无数据。

异常值方面,最常见的是"核减"导致的数据回退。比如某天累计确诊突然比前一天少了,这在实际统计中是可能发生的(因为之前误报被修正了)。但在可视化时,如果直接用原始数据,地图颜色会出现闪烁,影响阅读体验。我的做法是:对于累计确诊这类单调指标,用cummax()取累计最大值,保证数据不会回退。

df['累计确诊'] = df.groupby('省份')['累计确诊'].cummax()

这行代码的意思是:按省份分组,对每组的累计确诊列取累计最大值。这样即使中间有回退,最终显示的数据也是单调递增的。

2.4 输出 ECharts 可识别的 JSON 格式

ECharts 地图组件需要的数据格式是[{name: '省份', value: 数值}, ...]。如果要做时间轴动画,还需要按日期组织成[{date: '2020-01-01', data: [...]}, ...]这样的结构。

import json def generate_echarts_data(df, date): day_data = df[df['日期'] == date] result = [] for _, row in day_data.iterrows(): result.append({ 'name': row['省份'], 'value': int(row['累计确诊']) }) return result dates = sorted(df['日期'].unique()) timeline_data = [] for d in dates: timeline_data.append({ 'date': d, 'data': generate_echarts_data(df, d) }) with open('map_data.json', 'w', encoding='utf-8') as f: json.dump(timeline_data, f, ensure_ascii=False, indent=2)

这里用ensure_ascii=False是为了让中文正常显示,而不是被转义成\uXXXX的形式。indent=2是为了让 JSON 文件可读,方便调试。如果数据量很大,可以去掉 indent 来减小文件体积。

3. ECharts 地图配置与前端渲染

3.1 地图组件的引入与初始化

ECharts 5.x 版本中,地图数据需要单独引入。有两种方式:一种是用echarts.registerMap()注册地图数据,另一种是直接用geo组件配合在线地图服务。考虑到项目需要离线可用,我推荐第一种方式。

首先需要获取中国地图的 geoJSON 文件。这个文件包含了每个省份的边界坐标数据,文件大小通常在几百 KB 到 1 MB 之间。获取方式有多种,可以从公开的数据源下载,也可以用 Python 的geopandas库从 shapefile 转换。

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>全国疫情地图</title> <script src="https://cdn.example.com/echarts.min.js"></script> <script src="china.js"></script> </head> <body> <div id="map-container" style="width: 100%; height: 800px;"></div> <script> var chart = echarts.init(document.getElementById('map-container')); // 后续配置代码 </script> </body> </html>

注意:china.js是包含地图注册代码的文件,它内部会调用echarts.registerMap('china', geoJsonData)。如果你从 CDN 引入 ECharts,要确保版本一致,否则可能出现 API 不兼容的问题。

3.2 visualMap 视觉映射的核心参数

visualMap 是地图可视化的核心组件,它决定了数值如何映射到颜色。关键参数包括:

  • min和max:数据的最小值和最大值。如果不设置,ECharts 会自动计算,但自动计算的结果可能不理想。
  • type:'continuous' 表示连续映射,'piecewise' 表示分段映射。对于疫情数据,我推荐用分段映射,因为数据分布通常不均匀。
  • pieces:分段映射的具体配置,每段包含min、max、label、color。
  • inRange:连续映射时的颜色范围,用一个颜色数组表示。
  • calculable:是否显示拖拽手柄,允许用户手动调整映射范围。
visualMap: { type: 'piecewise', pieces: [ {min: 1000, label: '1000以上', color: '#7f1d1d'}, {min: 500, max: 999, label: '500-999', color: '#b91c1c'}, {min: 100, max: 499, label: '100-499', color: '#dc2626'}, {min: 10, max: 99, label: '10-99', color: '#f87171'}, {min: 1, max: 9, label: '1-9', color: '#fca5a5'}, {min: 0, max: 0, label: '0', color: '#fef2f2'} ], left: '10%', bottom: '10%', textStyle: { color: '#333' } }

这套分段方案是我根据实际数据分布调整出来的。核心思路是:低数值区间分得细一些,高数值区间分得粗一些。因为大部分省份的数值集中在低位,如果均匀分段,低位的差异就看不出来了。

3.3 tooltip 与交互体验优化

tooltip 是鼠标悬停时显示的提示框,它直接影响用户获取信息的效率。默认的 tooltip 只显示省份名称和数值,但我们可以自定义格式,加入更多信息。

tooltip: { trigger: 'item', formatter: function(params) { if (params.value) { return params.name + '<br/>' + '累计确诊:' + params.value + '例<br/>' + '占比:' + (params.value / total * 100).toFixed(2) + '%'; } else { return params.name + '<br/>暂无数据'; } } }

这里有个细节:params.value可能是 undefined(如果该省份没有数据),所以要先判断。另外,total需要从外部传入,表示全国总数。

还有一个提升体验的小技巧:给地图区域加上emphasis效果,鼠标悬停时高亮显示。

series: [{ type: 'map', map: 'china', emphasis: { label: { show: true, color: '#fff' }, itemStyle: { areaColor: '#3b82f6', borderColor: '#1e40af' } } }]

3.4 时间轴动画的实现方式

如果要展示疫情随时间的变化,timeline 组件是必不可少的。它的核心配置包括:

  • data:时间点的列表,比如 ['2020-01-01', '2020-01-02', ...]
  • axisType:'category' 表示离散时间点,'time' 表示连续时间轴
  • playInterval:自动播放的间隔,单位毫秒
  • autoPlay:是否自动播放
timeline: { data: dates, axisType: 'category', autoPlay: true, playInterval: 1000, left: '10%', right: '10%', bottom: '5%', label: { color: '#333' } }, options: timelineData.map(function(item) { return { title: { text: '全国疫情地图 ' + item.date }, series: [{ type: 'map', map: 'china', data: item.data }] }; })

这里用options数组来存储每个时间点的配置,ECharts 会自动根据 timeline 的当前索引来切换。注意options中的每个配置项不需要重复写 visualMap、tooltip 这些公共配置,ECharts 会自动合并。

4. 常见问题与排查技巧实录

4.1 地图显示为空白或灰色

这是最常见的问题,通常有三个原因:

原因一:地图数据未注册。如果你用的是 ECharts 5.x,但没有引入china.js或者没有调用echarts.registerMap(),地图就不会显示。排查方法是打开浏览器控制台,看是否有 "Map china not exists" 的报错。

原因二:省份名称不匹配。数据中的省份名称和 geoJSON 中的名称不一致,导致该省份没有数据,显示为灰色。排查方法是把数据中的省份名称和 geoJSON 中的名称都打印出来,做差集对比。

原因三:visualMap 的 min/max 设置不当。如果所有数据都小于 min,或者都大于 max,地图就会全部显示为同一种颜色。排查方法是检查数据的实际范围,确保 min 和 max 覆盖了所有数据。

4.2 数据量过大导致页面卡顿

当时间点很多(比如 100 天以上),每个时间点都有 34 个省份的数据时,JSON 文件可能达到几 MB,页面加载和切换会明显卡顿。我的优化方案是:

  • 对数据进行抽稀,比如每隔 3 天取一个时间点,而不是每天都有
  • 用JSON.parse()替代eval()来解析数据,速度更快
  • 开启 ECharts 的large模式(如果数据点超过阈值)
  • 用 Web Worker 在后台线程处理数据,避免阻塞主线程

4.3 颜色映射不符合预期

有时候你会发现,明明设置了 visualMap,但地图颜色就是不对。常见原因包括:

  • series.data中的 value 是字符串而不是数字,导致 ECharts 无法正确比较大小
  • visualMap 的dimension设置错误,默认是 0,但如果数据是对象数组,可能需要指定其他维度
  • pieces中的区间有重叠或遗漏,导致某些数值没有对应的颜色

排查方法是:在控制台打印chart.getOption().visualMap,看看实际生效的配置是什么。

4.4 移动端适配问题

ECharts 默认的字体大小和布局在移动端可能偏大或偏小。我的做法是用media查询来设置不同屏幕尺寸下的配置:

var option = { baseOption: { // 基础配置 }, media: [{ query: { maxWidth: 500 }, option: { title: { textStyle: { fontSize: 14 } }, visualMap: { itemWidth: 10, itemHeight: 10, textStyle: { fontSize: 10 } } } }] };

另外,移动端的地图容器高度要设置得小一些,否则地图会被压扁。我一般用height: 60vh而不是固定像素值。

4.5 常见问题速查表

问题现象可能原因排查方法解决方案
地图空白地图未注册控制台看报错引入 china.js 或调用 registerMap
省份灰色名称不匹配打印名称做差集建立映射字典统一名称
颜色单一visualMap 范围不当检查数据 min/max调整 min/max 或改用分段映射
页面卡顿数据量过大看 JSON 文件大小抽稀时间点、开启 large 模式
tooltip 不显示trigger 设置错误检查 trigger 值改为 'item'
时间轴不动autoPlay 未开启检查 timeline 配置设置 autoPlay: true

提示:排查 ECharts 问题时,浏览器控制台是你的好朋友。chart.getOption()可以打印当前生效的所有配置,chart.getDataURL()可以导出当前地图的图片,方便对比。

5. 项目扩展与个人经验分享

5.1 从静态地图到动态数据看板

这个项目做完之后,最自然的扩展方向是把它变成一个动态数据看板。具体来说,就是加一个后端服务,定时从数据源拉取最新数据,更新 JSON 文件,前端通过 AJAX 定时刷新。这样就不需要每次手动重新生成数据了。

后端可以用 Flask 或 FastAPI 写一个简单的接口:

from flask import Flask, jsonify import pandas as pd app = Flask(__name__) @app.route('/api/map_data') def get_map_data(): df = pd.read_csv('epidemic_data.csv') # 数据处理逻辑 result = generate_echarts_data(df, latest_date) return jsonify(result) if __name__ == '__main__': app.run(debug=True)

前端用setInterval定时请求:

setInterval(function() { fetch('/api/map_data') .then(response => response.json()) .then(data => { chart.setOption({ series: [{ data: data }] }); }); }, 60000);

5.2 数据可视化的伦理边界

做疫情地图这类项目,有一个问题值得每个开发者思考:可视化会不会放大恐慌?我个人的经验是,颜色的选择、分段的阈值、标题的措辞,都会影响读者的情绪。比如用深红色表示高数值,用大号字体显示"确诊人数",这些都会强化紧张感。

我的做法是:在保证信息准确的前提下,尽量用中性色系,避免过于刺眼的颜色;在标题和 tooltip 中加入"累计"、"现有"等限定词,避免歧义;如果数据是历史数据,明确标注时间范围,避免读者误以为是实时数据。

5.3 我踩过的几个坑

第一个坑是忽略了数据更新频率。一开始我每天手动更新数据,后来发现太麻烦,改成每周更新一次,结果地图上的数据总是滞后。后来加了自动更新脚本,用schedule库定时执行,才解决了这个问题。

第二个坑是没有做数据校验。有一次数据源里某个省份的数值多了一个零,导致地图上那个省份颜色异常深,看起来像是疫情爆发。后来加了校验逻辑:如果某个数值超过历史均值的 10 倍,就标记为异常,人工确认后再决定是否使用。

第三个坑是移动端测试不足。在电脑上看着很完美的地图,到了手机上发现图例挡住了地图,tooltip 点不出来。后来用 Chrome 的移动端模拟器反复调试,才把布局调好。

5.4 后续可以这样扩展

如果你已经跑通了这个项目,可以考虑以下几个扩展方向:

  • 多指标切换:除了累计确诊,还可以加入治愈、死亡、新增等指标,用按钮或下拉框切换。
  • 省份下钻:点击某个省份,地图切换到该省份的市级地图,展示更细粒度的数据。
  • 趋势图表联动:在地图旁边加一个折线图,显示选中省份的历史趋势,地图和图表联动。
  • 数据导出:加一个导出按钮,把当前地图导出为 PNG 或 PDF,方便做报告。

这些扩展不需要推翻现有架构,只需要在现有基础上增加配置和交互逻辑。我个人的经验是,先把核心功能做扎实,再逐步加扩展,不要一开始就追求大而全。

最后分享一个小技巧:ECharts 的chart.getOption()返回的是一个深拷贝的对象,你可以把它序列化成 JSON 保存下来,作为配置模板。下次做类似项目时,直接改改数据源和颜色方案就能复用,省去大量重复配置的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询