拿到这类数据包,我最怕的不是文件太大,而是打开之后“看起来正常、用起来全错”。1980-2024年全国省市县三级逐日最低气温数据,听上去就是一张干干净净的Excel表加几个Shapefile,但真放进GIS里操作,编码、单位、日期格式、行政区划变动,每个环节都能让你的成果图变成一张噪声图。下面这篇就把我从“解压数据”到“出图分析”整个流程里遇到的关键问题逐个拆开讲清楚,包含字段结构、格式陷阱、应用场景、数据清洗和验证方法,适合刚接触这类日尺度气象栅格/行政区统计数据的GIS从业者、气象分析人员和农业、能源领域的研究者参考。
1. 数据集整体结构:45年逐日记录到底意味着什么
1.1 时间维度:1980-2024的日历天数和序列组织方式
1980年1月1日到2024年12月31日,含首尾,一共45个年份。很多人会想当然地认为45年就是45乘365天,但里面还有12个闰年,所以完整日历天数是16437天。这意味着,如果你把一个县级、逐日、全字段的数据放在一张Excel表里,全国大约2800个县级行政区,累积下来接近5000万行记录。所以这类分享数据基本不会真的给你一个“无敌大工作簿”,更常见的组织方式是“按年份拆分”“按月份拆分”或者“按日期拆出成千上万个文件夹”。
拿到手之后,不要急着双击打开最大那个Excel,先看目录树。目录里通常会有年份文件夹,里面再按月份或日期放置文件。对日尺度数据来说,日期是可检索性最高的维度,也是后续做时间序列分析时最容易踩坑的地方。如果数据包结构很混乱,我的建议是先统一整理为“日期、行政区划代码、行政区划名称、最低气温”四列的长表,再存成Parquet或CSV,后续随便怎么筛选聚合都顺畅。
1.2 空间维度:省、市、县三级的组织逻辑
“省市县三级”不是普通的粒度堆叠,它意味着同一个时点同一份气温被重复记录了三轮:
- 省级一份,每天大约几十行;
- 市一级一份,每天大约三百多行;
- 县一级一份,每天大约两千八百多行。
三级数据单独看都能用,组合起来更有意思。基层研究用县级,区域对比用市或省,不同层级之间可以互相验证。比如县级数据某天出现一个异常低温值,用上一级市级面的均值去核验,往往能很快判断是真实极端事件还是数据错位。
需要留意的是,行政区划不是一成不变的。过去几十年里,撤地设市、撤县设区、设立新县级单位这些事情非常多。因此数据包里的“现行行政区划”和“历史某一年的真实区划”可能存在差异。做跨年对比时,尽量使用“行政区划代码”而不是行政区划名称来进行关联和匹配,名称相似但代码不同的情况在长期序列里几乎必然存在。
1.3 Shp和Excel的定位:一个管图,一个管数
Shp和Excel不是同一份数据的两种简单复制,它们服务的分析流程完全不同。
Shapefile适合空间可视化。一个县一个面要素,属性表里挂着温度值,打开GIS就能画出等值区域图。但Shapefile由多个同名文件组成(.shp、.dbf、.shx、.prj等),发送或拷贝时一旦漏掉.dbf,属性表就全空了。温度数据的关键字段都在.dbf里,所以看到.shp文件时,务必确认旁边几个辅助文件都在。
Excel则更适合做统计建模。数据按行列存放,日期是一列、区域代码是一列、温度是一列,用Pandas或R语言读进来后,可以轻松做分组统计、面板回归、机器学习特征工程。Excel格式最大的问题在于数据量太大时运行卡顿,所以建议不要直接打开编辑,先转化成更为高效的数据格式。
| 维度 | Shapefile格式 | Excel格式 |
|---|---|---|
| 核心用途 | 制图、空间分析、切片展示 | 表格清洗、统计建模、跨表合并 |
| 最小分析单元 | 一个行政面要素 | 一行时间-区域记录 |
| 易碎点 | 多个辅助文件缺失、编码乱码 | 行数过多、日期类型混乱 |
| 适用人群 | GIS制图师、遥感分析者 | 数据分析师、科研人员 |
2. 打开文件前必须弄懂的字段与格式细节
2.1 Shapefile属性表里的中文字段和编码问题
中文属性表乱码是我见过最多的“假故障”。表现很直接:QGIS里打开属性表,省、市、县名称全部变成“锟斤拷”或者“?”一类内容。原因不是数据坏了,而是Shapefile的.dbf文件本身用GBK或GB2312编码存储中文字符,而GIS软件默认按UTF-8解析。
解决办法很简单:在加载Shapefile时,把数据源编码手动指定为GBK或GB18030。QGIS的“数据源编码”选择里通常有“GBK”“System”等选项,切换后重新加载即可。如果所有办法都试过仍然乱码,可以用Notepad++打开.dbf附近的.cpg文件查看编码声明,再按声明指定编码。编码问题解决之前,最好不要对字段做任何重命名或导出操作,避免把错误编码固化到新文件里。
2.2 坐标系统、单位和读数精度的判断
这类行政区温度数据大多是经纬度坐标系,常见的是WGS84或CGCS2000,投影坐标也可能出现。打开图层后,如果和在线底图错位很远,先看是否把十进制度数当成了平面坐标;如果只是几十米的偏移,可以检查是不是参考椭球或坐标系定义略有差异。
对气温数值来说,坐标系影响不大,但平面投影与经纬度存储会影响距离计算和面积统计。我的习惯是:日常查看用经纬度坐标,计算县域面积或做格点插值时再投影到当地合适的平面坐标系。
2.3 温度单位:直接是摄氏度,还是缩放了10倍
这是最隐蔽的坑之一。很多气象数据为了节省存储空间,会把温度乘以10记录成整数,比如-3.5℃写成-35,0.6℃写成6。如果你没有看字段说明,直接把-35当零下35度画图,整张图就会“异常偏冷”,而且很难发现。
判断方法很简单:载入数据后先看最低气温列的取值范围。如果大部分值落在-450到400之间,基本可以确定是0.1℃单位;如果正常分布在-50到45之间,才是标准摄氏度。对于0.1℃单位的数据,处理时除以10即可。
提示:不要在看到“-320”时先怀疑极端寒潮,先检查缩放系数。这类错误在全量数据里不会只出现一两个,而是整体偏移,特征非常明显。
2.4 Excel日期列的三种存储形态与解析方法
Excel日期列常见三种形态,处理方式完全不同:
| 存储形态 | 示例 | 处理方法 |
|---|---|---|
| 真日期格式 | 2024-01-15 | 直接转成datetime对象 |
| 字符串格式 | 20240115 | 用format="%Y%m%d"解析 |
| 数字序列 | 45277 | 从1899-12-30起累加天数 |
数字序列在Pandas里不自动识别,读出来是一列整数。解析方法为:
import pandas as pd df["日期"] = pd.to_datetime("1899-12-30") + pd.to_timedelta(df["日期数值"], unit="D")为什么是从1899年12月30日而不是1900年1月1日?这源于历史软件日期序列的起点规则。1980年以后的数据不会碰到1900年闰年bug的影响,所以直接用这个基点转换是安全的。
3. 这套数据在真实业务中值钱的应用方向
3.1 农业:霜冻界定、无霜期和冷害监测
农业生产里,最低气温比平均气温更能定义霜冻风险。比如某些果树在花期遇到-2℃以下的低温就可能造成减产;春季最后一次霜冻日推迟,播种和定植计划就要跟着调整。
用逐日最低气温数据可以计算县域无霜期长度:
df["霜冻日"] = (df["最低气温"] <= 0).astype(int) # 每年最后一次霜冻日 last_frost = df[df["霜冻日"] == 1].groupby(["年份", "行政区划代码"])["日期"].max() # 每年最早一次霜冻日 first_frost = df[df["霜冻日"] == 1].groupby(["年份", "行政区划代码"])["日期"].min()这样的指数比单看某一天的低温值更有业务指导性。搭配产量历史数据,就能评估某个县域霜冻灾害年景。
3.2 能源:供暖度日数的简化估算
能源调度通常关注供暖度日数(HDD),标准定义依赖日平均气温。如果数据包没有平均气温,只有最低气温,可以拿最低气温做简化近似,比如设定一个低温阈值,把低于阈值的部分累加作为冷量强度指标。
我不建议照搬标准公式,因为用最低温代替平均温会系统性低估实际供暖需求。更合理的做法是把最低气温和当地负荷历史数据做回归,自己标定参数。逐日数据的好处就是样本量大,一年365个点,足够你做一个像样的相关性分析,不必依赖别人给你的固定系数。
3.3 公共健康与交通:寒潮持续事件识别
健康风险研究里,极端低温的持续天数比单日最低温更重要。连续三天以上、日最低气温大幅低于当地多年同期均值,往往对应着心血管疾病风险上升和道路结冰事故增加。
用这套数据可以统计每个县级区域每年“低温事件”发生的次数、平均持续时间和最早开始日期。加上行政区划经纬度,还能判断风险的空间集聚特征,为应急预案制定提供基础图层。
4. 数据清洗过程中最容易踩的四个坑
4.1 乱码不是文件损坏,而是DBF编码识别失败
排查链路应该是:现象是属性表中文乱码;第一步检查是否所有字符列都乱码,还是只有部分列;第二步打开GIS数据源编码选项,尝试GBK、GB18030、UTF-8三种;第三步如果仍有问题,检查.cpg文件,或直接用文本工具查看.dbf开头的字符集标记。乱码解决后,建议把数据导出为GeoPackage格式,后续不再受编码困扰。
4.2 数字“0”到底是真零度还是缺测标记
很多气象数据用特殊值标记缺测,常见的有9999、-9999、999999。但更低级的陷阱是“0”既可能是真实的0.0℃,也可能是无效值。我的经验是结合日期和空间位置一起判断:盛夏时节内陆多个县集体出现最低温0℃,基本是异常;冬天某高海拔县出现0℃,可能完全正常。
清洗时可以先把超出合理物理范围的值统一转为NaN,再统计缺失率。比如摄氏度单位下,可以把小于-60℃和大于50℃的过滤掉。
df["最低气温"] = pd.to_numeric(df["最低气温"], errors="coerce") df.loc[~df["最低气温"].between(-60, 50), "最低气温"] = None4.3 按名称合并时行数暴涨或者大量缺失
用县名做关联是非常顺手但非常危险的操作。同一个县名在不同年份可能对应不同代码,不同县也可能有类似名称。合并之后行数异常增多,多半是连接字段存在重复;大量缺失,则可能是代码或名称存在前导零丢失。
规范的字段类型必须一致并统一为六位字符串:
df["行政区划代码"] = df["行政区划代码"].astype(str).str.zfill(6)不要存成数字,否则前导零消失后,代码就错位了。
4.4 行政区划变动导致的历史序列不连续
这里不细说具体某个地方的变化,但从全国尺度看,过去几十年的行政区划调整频率相当高。你手上这份数据的“行政区划代码”对应的可能是2020年或2024年的行政区划版本,而你要研究的年份可能还在旧版本里。
建议做法分三步:第一,对研究期内的行政区划代码做去重和变化检测;第二,如果代码变化频繁,尝试用“年份+区域映射表”做历史版本归并;第三,如果你只需要制图分析,不必强行把历史数据统一到最新边界,可以用“当时的行政区划”出历史图,用“最新边界”出现状图,两个图不要混用。
5. 实操演示:把特定日最低气温挂到县级Shapefile上
5.1 第一步:读取并清洗Excel数据
假设你的文件名是“全国县级逐日最低气温_2024.xlsx”,读取时注意指定代码列是字符串。
import pandas as pd raw = pd.read_excel( "全国县级逐日最低气温_2024.xlsx", dtype={"行政区划代码": str} ) raw["日期"] = pd.to_datetime(raw["日期"].astype(str), format="%Y%m%d", errors="coerce") raw["最低气温"] = pd.to_numeric(raw["最低气温"], errors="coerce") # 过滤不可信极值 raw = raw[raw["最低气温"].between(-60, 50)] # 筛选出你要出图的那一天 target = raw[raw["日期"] == "2024-01-15"].copy() target["行政区划代码"] = target["行政区划代码"].str.zfill(6)如果字段名不同,根据实际文件修改列名即可。读取后别急着合并,先打印target.head()看一眼。
5.2 第二步:用行政区划代码关联空间图层
用GeoPandas读取县级行政区Shapefile,同样把代码列处理成六位字符串。
import geopandas as gpd gdf = gpd.read_file("县级行政区划.shp", encoding="utf-8") gdf["行政区划代码"] = gdf["行政区划代码"].astype(str).str.zfill(6) merged = gdf.merge( target[["行政区划代码", "最低气温"]], on="行政区划代码", how="left" ) print(merged["最低气温"].isna().sum()) # 查看未匹配上的县这一步如果大量缺失,首要检查不是几何问题,而是代码是否都是字符串且位数一致。也可以顺便看看目标文件里是否有重复代码,用target["行政区划代码"].duplicated().sum()检查。
5.3 第三步:导出与渲染
合并完成后,直接导出为GeoPackage,避免再把Shapefile那一堆小文件拷来拷去。
merged = merged.to_crs("EPSG:4326") merged.to_file("2024-01-15全国县级最低气温.gpkg", driver="GPKG")后续在QGIS里打开这个GeoPackage,按“最低气温”字段做分级设色即可。配色建议用蓝色系代表低温、红色系代表高温,这样图面语义更直观。别忘在出图标题里写上具体日期,因为逐日数据一旦混用日期,很容易出“图上日期和文件名不一致”的低级错误。
6. 拿到数据后建议先做的四项验证
6.1 用一场你记得住的强降温过程来复核
不论数据生产方说自己的数据多么准确,我都建议先找一次自己经历过的、印象深刻的强降温过程做复核。选几个你熟悉区域,看那几天最低气温的变化趋势是否符合记忆和常识。如果趋势对,说明数据的时间连续性基本可靠;如果完全对不上,就要怀疑日期列是不是存在整体偏移或时区问题。
6.2 绘制年度最低温序列,识别整体异常
把全国或研究区域每年最低温度取出来画一条折线,正常情况下不会出现毫无原因的骤变。异常的骤升骤降往往对应数据处理错误,比如某一年没有除以10、某一年缺失值没有被正确识别。
检查代码:
year_min = raw.groupby(raw["日期"].dt.year)["最低气温"].min() print(year_min)看到这种汇总图,再回到细节里查找具体日期,比直接全表搜索更高效。
6.3 空间相邻检查:边界两侧应当平滑过渡
把某一天的县级最低气温渲染到地图上,肉眼观察相邻县域之间有没有大跨度跳变。自然情况下,平地区域相邻县域温差通常不会特别夸张;如果一块区域被一条明显“撕裂”的边界分为左右两半,大概率是行政区划代码错位或某个文件字段顺序不对。
这种检查不能只看最高最低值,要看空间格局是否“讲得通”。地形复杂的山区例外,高差会带来明显温差,这时要结合DEM判断。
6.4 缺失率统计与插值决策
逐日数据经历多年整理,几乎不可能零缺失。对每个年份、每个月统计缺失率,会直接影响后续建模时是否要做插值。
raw["月份"] = raw["日期"].dt.month missing = raw[raw["最低气温"].isna()].groupby(["日期"]).size() missing.to_csv("缺失日期统计.csv", encoding="utf-8-sig")如果缺失只集中在少数几天,通常可以直接剔除;如果某个月成片缺失,可能要考虑线性插值、空间插值,或者使用上一级行政区域均值做填充。填不填充没有绝对标准,但一定要在分析报告里写清楚,不然别人复现时会得出完全不同结果。
最后再分享一个小技巧:这类上传分享的数据文件往往没有附带非常详尽的字段说明,花半小时做一个压缩包内文件清点,再按本文的思路做一遍字段和单位检查,远比直接拖进软件跑分析划算。尤其在多人协作时,把“单位是摄氏度还是0.1℃”“日期格式是什么”“行政区划版本是哪一年”这三条写清楚,整个项目的返工率能降下一大截。