新冠病毒数据集实战:从地理位置到传播地图的完整处理链路

发布时间:2026/10/14 16:25:00
新冠病毒数据集实战:从地理位置到传播地图的完整处理链路
简介这份资源面向数据分析初学者、地理可视化爱好者及疫情数据研究者提供一套可直接上手的新冠病毒疫情数据集用于练习Python数据处理与地图绘制。压缩包共3个文件包含2个csv与1个json整体约360KB体积轻便易于加载。其中json文件提供地理边界数据可用于绘制区域轮廓一份csv记录全国城市坐标便于将疫情数据定位到具体城市另一份csv为2020年2月9日的中国新冠疫情数据涵盖各地确诊等统计信息。三者配合可完成从数据清洗、城市坐标匹配到地理边界渲染的完整流程适合用于疫情分布地图、区域对比分析等练习场景。目前已有4703人学习下载说明该数据集在同类练习中具有一定参考价值能帮助读者快速搭建可视化项目并理解疫情数据的空间分布特征。1. 新冠病毒数据集从地理位置到城市坐标一份能直接跑出传播地图的数据该怎么用2020年2月9日前后很多团队都在做同一件事把每日通报的病例数字落到地图上。但真正动手才会发现数字本身好拿难的是把“某省某市”这行文本变成经纬度再和病例数拼成一张能画热力图、能算距离、能做时空聚合的表。这份新冠病毒数据集包含地理位置、全国城市位置、2020-2-9新冠病毒数据集解决的正是这个断层——它把疫情统计表、全国城市经纬度表、以及特定时间切片的数据放在一起让做数据可视化、空间分析、传播建模的人不用从零爬坐标。适合谁用做疫情传播复盘的数据分析师、需要练手时空数据的算法同学、以及想快速搭一个“省份-城市-坐标-病例数”四表联查的开发者。下面按我实际处理这类数据的顺序把字段、清洗、坐标匹配、地图渲染和踩坑一次讲透。2. 先看清三张表的结构字段含义与关联键怎么定拿到一份带地理位置的疫情数据集第一件事不是画图而是把表拆开看。标题里其实已经点明了三个组成部分地理位置信息、全国城市位置、2020-2-9的新冠病毒数据。它们大概率是三张独立表也可能合并成一张宽表。无论哪种形态你都要先确认关联键——是城市名、城市编码还是省市二级拼接。这一步定错后面所有坐标匹配都会错位。2.1 疫情统计表的典型字段与时间切片含义2020-2-9这个日期很关键。它不是一个随便选的时间点而是早期全国通报口径相对稳定、且覆盖城市较多的一天。疫情统计表通常包含这些列省份、城市、确诊数、疑似数、死亡数、治愈数有的版本还会带当日新增。你要特别留意“累计”和“新增”是否混在一列里——很多公开数据在早期会把累计确诊写成“确诊”把当日新增单独放一列如果误把新增当累计热力图的颜色分级会完全失真。我一般先做一次字段探查用pandas把列名、非空计数、唯一值数量打出来import pandas as pd # 读取疫情统计表注意编码早期数据常见utf-8和gbk混用 df_cases pd.read_csv(covid_20200209.csv, encodingutf-8) # 查看字段结构、非空情况、唯一值数量 print(df_cases.info()) print(df_cases.nunique()) print(df_cases.head(10)) # 确认省市两级是否齐全有没有“待明确地区”这类脏值 print(df_cases[省份].unique()[:10]) print(df_cases[城市].unique()[:10])这段代码的目的不是做分析而是排雷。info()看缺失nunique()看重复head()看实际内容。如果“城市”列里出现“境外输入”“待明确”这类非地理实体必须在关联坐标前剔除或单独归类否则后面匹配经纬度会大量落空。参数上encoding要根据实际文件调整读进来乱码就换gbk或utf-8-sig。2.2 全国城市位置表的坐标精度与命名规范全国城市位置表一般包含省份、城市、经度、纬度有的还带行政区划代码。这份表的质量直接决定地图准不准。常见问题是城市名带“市”和不带“市”混用比如“武汉”和“武汉市”同时存在或者直辖市在省份列和城市列重复。你要做的第一件事是统一命名去掉“市”“地区”“自治州”等后缀或者反过来统一补全。我的习惯是保留一份原始名另建一列标准化名做关联这样出问题能回溯。# 读取城市坐标表 df_geo pd.read_csv(china_city_coords.csv, encodingutf-8) # 标准化城市名去掉常见后缀便于和疫情表关联 def normalize_city(name): if pd.isna(name): return name for suffix in [市, 地区, 自治州, 盟]: name name.replace(suffix, ) return name.strip() df_geo[城市标准] df_geo[城市].apply(normalize_city) df_cases[城市标准] df_cases[城市].apply(normalize_city) # 检查关联键唯一性避免一对多导致行数膨胀 print(df_geo[城市标准].duplicated().sum()) print(df_cases[城市标准].duplicated().sum())这里的关键参数是后缀列表。不同来源的坐标表后缀不一样你要按实际数据增删。duplicated().sum()如果大于0说明标准化后出现重名城市比如不同省份有同名区县这时必须把省份也纳入关联键用“省份城市”联合匹配否则会把病例数挂到错误坐标上。2.3 用省份城市双键做关联避免同名城市错配单靠城市名关联在早期数据里翻车概率很高。全国有多个“城关区”“新城区”这类同名区划虽然地级市层面重名少但一旦数据粒度到区县就会出问题。稳妥做法是构造“省份_城市”联合键# 构造联合键省份也做标准化 df_cases[省份标准] df_cases[省份].str.replace(省, ).str.replace(市, ) df_geo[省份标准] df_geo[省份].str.replace(省, ).str.replace(市, ) df_cases[key] df_cases[省份标准] _ df_cases[城市标准] df_geo[key] df_geo[省份标准] _ df_geo[城市标准] # 左连接保留疫情表所有行坐标缺失的能看出来 df_merged df_cases.merge( df_geo[[key, 经度, 纬度]], onkey, howleft ) # 统计匹配失败的行这些就是需要手工补坐标的 print(df_merged[df_merged[经度].isna()][[省份, 城市]])左连接后经度为空的行就是没匹配上的。别急着丢先看是命名差异还是坐标表确实缺这个城市。常见情况是坐标表只到地级市而疫情表有省直辖县级市这时要么手工补几个坐标要么把县级市归到所属地级市。参数上howleft保证不丢病例数据匹配率用df_merged[经度].notna().mean()算一下低于90%就说明命名标准化还不够。3. 从CSV到传播地图坐标匹配、聚合与渲染的完整链路字段对齐之后才进入真正的落地环节。这一章把数据清洗、空间聚合、地图渲染串成一条能复现的链路。你不需要GIS背景用pandas加一个可视化库就能跑出省级和市级两张图。重点在于聚合层级怎么选、坐标怎么去重、颜色分级怎么定这三步决定了图能不能用。3.1 按省份聚合与按城市聚合的取舍同一份数据按省份聚合和按城市聚合讲的是两个故事。省级图适合看整体分布市级图适合看局部爆发点。2020-2-9这个时间点很多省份的病例集中在省会市级图能明显看出单点高亮。我的做法是两份都出但聚合前先确认病例数列没有重复累加。# 按省份聚合求和确诊数 province_agg df_merged.groupby(省份标准).agg( 确诊总数(确诊, sum), 城市数(城市标准, nunique) ).reset_index() # 按城市聚合保留坐标 city_agg df_merged.groupby([省份标准, 城市标准, 经度, 纬度]).agg( 确诊总数(确诊, sum) ).reset_index() # 检查聚合后总数是否和原始总和一致防止重复行导致虚高 print(df_merged[确诊].sum(), province_agg[确诊总数].sum())groupby的列选择很关键。城市聚合时把经纬度也放进分组键是因为同一城市可能有多行比如不同日期或不同来源坐标相同才能安全聚合。如果坐标有微小差异分组会拆成多行这时要先对城市做坐标去重取第一组坐标即可。总数校验那行是后悔药——一旦发现聚合后总数大于原始总和说明关联时产生了一对多得回头查坐标表重复。3.2 经纬度落图前的三个校验范围、重复、缺失坐标进地图前必须过三关。第一关范围中国经纬度大致在东经73到135、北纬3到53之间超出这个范围的坐标基本是错的。第二关重复同一城市出现多个坐标取哪个要定规则我一般取出现次数最多的那个。第三关缺失匹配不上的城市要么补坐标要么在图上标注为“无坐标”。# 范围校验 valid_mask ( (df_merged[经度].between(73, 135)) (df_merged[纬度].between(3, 53)) ) print(超出范围的坐标数, (~valid_mask df_merged[经度].notna()).sum()) # 重复坐标处理同一城市取第一条 df_merged_dedup df_merged.drop_duplicates(subset[省份标准, 城市标准], keepfirst) # 缺失坐标单独存一份便于手工补 missing_coords df_merged[df_merged[经度].isna()] missing_coords.to_csv(missing_coords.csv, indexFalse, encodingutf-8-sig)between的边界值按实际数据微调比如有些数据把南海诸岛坐标也放进来了范围要相应放宽。drop_duplicates的keepfirst意味着你信任第一条坐标如果坐标表本身有错这个规则会把错误保留下来所以更稳的做法是先对坐标表做人工抽查。缺失坐标导出成CSV用utf-8-sig是为了Excel打开不乱码方便手工补。3.3 用散点图快速验证坐标分布是否合理在正式出热力图之前我习惯先用散点图扫一眼。散点图不追求好看只追求“形状对不对”——如果点全挤在东部沿海西部空白那大概率是坐标表只覆盖了部分城市如果点跑到境外就是坐标错位。这一步能省掉后面反复调地图样式的功夫。import matplotlib.pyplot as plt # 只画有坐标的点点大小按确诊数缩放 plot_data df_merged_dedup.dropna(subset[经度, 纬度]) plt.figure(figsize(10, 8)) plt.scatter( plot_data[经度], plot_data[纬度], splot_data[确诊总数] / plot_data[确诊总数].max() * 300 10, alpha0.6, cplot_data[确诊总数], cmapReds ) plt.colorbar(label确诊总数) plt.xlabel(经度) plt.ylabel(纬度) plt.title(2020-2-9 城市病例分布散点校验) plt.savefig(scatter_check.png, dpi150)s参数控制点大小这里用确诊数归一化后映射到10到310之间避免最大点盖住整张图。alpha设0.6让重叠点可见。cmapReds是疫情图常用色系颜色越深病例越多。这张图不用来发布只用来判断坐标是否合理。如果发现某省的点明显偏移回去查该省城市的坐标记录。3.4 输出可交互地图的最小配置静态图验证通过后如果要给别人看交互地图更合适。用常见的地图可视化库把城市聚合结果传进去配置好经纬度列和数值列即可。核心是让鼠标悬停能看到城市名和病例数缩放时点不丢失。import folium from folium.plugins import HeatMap # 以中国中心为初始视角 m folium.Map(location[35, 105], zoom_start4, tilesCartoDB positron) # 热力层数据纬度、经度、权重 heat_data df_merged_dedup.dropna(subset[经度, 纬度])[[纬度, 经度, 确诊总数]].values.tolist() HeatMap(heat_data, radius25, blur15, max_zoom8).add_to(m) # 逐城市打点悬停显示名称和病例数 for _, row in df_merged_dedup.dropna(subset[经度, 纬度]).iterrows(): folium.CircleMarker( location[row[纬度], row[经度]], radius3, popupf{row[城市标准]}{row[确诊总数]}, colorblue, fillTrue ).add_to(m) m.save(covid_map_20200209.html)HeatMap的radius和blur决定热力斑块的大小和柔和度城市级数据用25和15比较合适太小会碎太大会糊成一片。max_zoom8防止放大后热力层消失。CircleMarker的popup把城市名和病例数绑上去这是交互地图最实用的部分。保存成HTML后直接浏览器打开不依赖服务器。4. 避坑与排查坐标匹配和聚合里最容易翻车的五件事这类数据集看着简单实际处理时坑很集中。下面五条是我和同行反复遇到的每条按现象、原因、解决写清楚你对照自己的数据排查。4.1 匹配率只有六成大量城市坐标为空现象左连接后经度为空的行超过三成地图上大片区域没点。原因通常不是坐标表缺数据而是命名不一致——疫情表写“武汉市”坐标表写“武汉”或者疫情表省份写“湖北省”坐标表写“湖北”。解决先做后缀剥离和省份简称统一再检查是否有“省直辖县级行政区”这类特殊层级。如果标准化后仍匹配不上把未匹配的城市名单独导出和坐标表城市名做一次集合差集一眼就能看出差异模式。4.2 聚合后总数比原始数据大热力图颜色虚高现象按城市groupby求和后确诊总数比原始表sum大出一截。原因关联时坐标表存在重复城市导致一行病例匹配到多行坐标行数膨胀后求和翻倍。解决关联前对坐标表按“省份城市”去重drop_duplicates(subset[省份标准,城市标准], keepfirst)。关联后再做一次总数校验两个数必须相等不等就回头查重复。4.3 直辖市数据被拆成省份和城市两行现象北京、上海、重庆、天津在疫情表里省份列和城市列都是同一个名字聚合时按省份和按城市各算一次总数对不上。原因直辖市的行政层级特殊数据录入时有的把“北京市”放省份列、城市列留空有的两列都填。解决统一规则直辖市省份列和城市列都填市名聚合时按城市聚合即可省级聚合时单独处理。或者在标准化阶段把直辖市的城市标准设为市名省份标准也设为市名保证联合键唯一。4.4 坐标落在境外或海洋散点图出现离群点现象散点图上有几个点明显跑到国外或海上。原因坐标表里经度纬度写反或者用了错误的投影坐标没转换。解决先做范围校验超出东经73到135、北纬3到53的点标出来人工核对。经度纬度写反的特征是点集中在赤道附近或经度值小于纬度值一眼能看出来。如果是投影坐标需要用对应库转成WGS84再落图。4.5 中文编码导致城市名读进来是乱码现象read_csv之后城市名显示为乱码匹配全失败。原因文件编码是GBK或GB18030而默认按UTF-8读。解决先试encodingutf-8-sig不行换gbk再不行用chardet探测。导出中间文件时统一用utf-8-sig这样Excel和pandas都能正常读。如果已经读乱码了别硬改重新按正确编码读一遍乱码后的字符串无法可靠还原。5. 把2020-2-9切片用出增量价值时间对比与传播半径估算单日数据能画图但真正体现这份数据集价值的是把它当成时间序列的一个锚点。你可以用同结构的数据补齐前后几天做增量对比也可以用城市坐标算传播半径看早期病例是否围绕某个中心扩散。这一章给两个进阶用法都是我在处理类似时空数据时常用的。5.1 用前后切片算日增量识别早期快速上升城市如果你手头有2月8日和2月10日的同结构数据把三天按城市对齐就能算出日增量。日增量比累计值更能反映早期传播速度。做法是把三天的城市聚合结果按“省份城市”做外连接缺失日期的增量填0然后算差值。# 假设已有三天聚合结果city_agg_0208, city_agg_0209, city_agg_0210 # 统一列名后按城市键合并 def rename_agg(df, day): return df.rename(columns{确诊总数: f确诊_{day}}) agg_all rename_agg(city_agg_0208, 0208)[[省份标准, 城市标准, 确诊_0208]] agg_all agg_all.merge( rename_agg(city_agg_0209, 0209)[[省份标准, 城市标准, 确诊_0209]], on[省份标准, 城市标准], howouter ).merge( rename_agg(city_agg_0210, 0210)[[省份标准, 城市标准, 确诊_0210]], on[省份标准, 城市标准], howouter ).fillna(0) # 计算日增量 agg_all[增量_0209] agg_all[确诊_0209] - agg_all[确诊_0208] agg_all[增量_0210] agg_all[确诊_0210] - agg_all[确诊_0209] # 按增量排序看哪些城市在快速上升 print(agg_all.sort_values(增量_0209, ascendingFalse).head(20))howouter保证三天里出现过的城市都保留fillna(0)处理某天没有记录的情况。增量可能为负那是数据修订导致的分析时可以把负值截断为0或单独标注。排序后看头部城市如果某个城市连续两天增量都靠前就是早期重点观察对象。这个方法的边界是它依赖三天数据口径一致如果通报标准变了增量会失真。5.2 以省会为圆心估算病例分布半径另一个用法是算传播半径。以各省省会坐标为中心算该省有病例城市到省会的平均距离和最大距离。这能粗略反映早期病例是集中在省会还是已经扩散到地市。用Haversine公式算球面距离不需要额外装GIS库。import numpy as np def haversine(lon1, lat1, lon2, lat2): # 经纬度转弧度 lon1, lat1, lon2, lat2 map(np.radians, [lon1, lat1, lon2, lat2]) dlon lon2 - lon1 dlat lat2 - lat1 a np.sin(dlat/2)**2 np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2 c 2 * np.arcsin(np.sqrt(a)) return 6371 * c # 地球平均半径单位公里 # 假设省会坐标表 province_center 有省份标准、省会经度、省会纬度 df_dist df_merged_dedup.merge(province_center, on省份标准, howleft) df_dist[距省会距离] haversine( df_dist[经度], df_dist[纬度], df_dist[省会经度], df_dist[省会纬度] ) # 按省统计平均距离和最大距离 radius_stats df_dist.groupby(省份标准).agg( 平均距离(距省会距离, mean), 最大距离(距省会距离, max), 有病例城市数(城市标准, nunique) ).reset_index() print(radius_stats.sort_values(最大距离, ascendingFalse))haversine函数里6371是地球平均半径算出来是公里。np.radians把经纬度转弧度这是公式要求。合并省会坐标时用左连接保证每个有病例的城市都能算距离。如果某省省会坐标缺失该省统计会为空需要先补全。这个半径只是直线距离不代表实际传播路径但作为早期扩散范围的粗略指标够用了。我一般会把最大距离特别大的省挑出来看看是不是有远离省会的城市早期就出现病例那往往意味着独立的输入性传播链。5.3 把常用清洗步骤封装成函数换日期直接复用最后给一个实用习惯把标准化、关联、校验三步封装成函数换一份日期数据只改文件名就能跑。这样你处理2月9日之后几天的数据时不用重复写代码也减少手误。def build_city_dataset(cases_path, geo_path): df_cases pd.read_csv(cases_path, encodingutf-8) df_geo pd.read_csv(geo_path, encodingutf-8) for df in [df_cases, df_geo]: df[省份标准] df[省份].astype(str).str.replace(省, ).str.replace(市, ) df[城市标准] df[城市].astype(str).apply(normalize_city) df[key] df[省份标准] _ df[城市标准] df_geo_dedup df_geo.drop_duplicates(subset[key], keepfirst) df_merged df_cases.merge( df_geo_dedup[[key, 经度, 纬度]], onkey, howleft ) match_rate df_merged[经度].notna().mean() print(f坐标匹配率{match_rate:.2%}) return df_merged # 换日期只需改路径 df_0210 build_city_dataset(covid_20200210.csv, china_city_coords.csv)这个函数把编码、标准化、去重、关联、匹配率打印都包进去了。match_rate低于0.9就说明这份数据的命名和坐标表差异较大需要单独处理。封装的好处是每次换数据都能看到匹配率不会闷头画图最后发现坐标错了一半。我自己的习惯是每处理一份新切片先跑这个函数看匹配率再决定要不要手工补坐标。这套流程不复杂但能让你把精力放在分析上而不是反复修数据。希望帮到你。本文还有配套的精品资源点击获取