☰
Kriging插值绘制等值线图:从变异函数到Python实践全解析
2026/9/29 17:45:37 网站建设 项目流程

简介:Kriging 插值绘制等值线图源码包,面向 GIS、地质勘探及空间数据分析人员,解决如何用统计学插值方法把离散观测数据转化为连续等值线图的问题。包内共 84 个文件,以 C++ 头文件与实现文件(27 个 .h、16 个 .cpp)为主,配套 Visual C++ 6.0 工程文件(.dsp/.dsw/.rc)以及可执行程序、测试数据文档,整体压缩后约 1.77MB,便于快速查看工程结构并直接编译运行。源码覆盖半方差函数建模、普通/泛克里金类型选择、矩阵求解预测值和等值线渲染等关键环节,并提供 3D 表面视图与二维等值图两种可视化模块。已有 1214 人学习下载,适合想深入理解 Kriging 算法实现、或需要在 MFC 框架中集成空间插值展示的学习者参考。

1. Kriging画等值线图:从插值原理到出图的一次完整落地

做地质、气象、环境监测或土壤调查的工程师,十有八九遇到过同一个尴尬:手里是一堆离散采样点,领导要的是一张平滑闭合的等值线图。插值方法选得随意,出来的图要么在数据密集区挤成一团,要么在数据稀疏区画出根本不可能存在的“孤岛”。Kriging之所以被当成这类任务的首选,不是因为它名字高级,而是它和其他插值方法最大的区别:它不只给你一个预测值,还同时算出了这个预测值的误差方差。换句话说,Kriging告诉你“哪里可信、哪里只是猜”。这篇文章要把从变异函数拟合到等值线出图的完整链路拆开讲,包括每个参数的实际意义、代码怎么写、以及那些让你翻车的隐性坑。适合手里有采样数据、想用Python出图但不想止步于调包的人。

2. 先搞懂Kriging在算什么:变异函数与权重分配的逻辑

2.1 为什么普通插值法不够用

常见的反距离加权法(IDW)有个天然缺陷:权重只跟距离有关,完全不看数据在空间上的分布结构。假设一片区域里东侧数据密集、西侧稀疏,IDW依然按纯距离给权重,结果就是密集区被自己的邻居数据反复平均,稀疏区的插值结果则对远处的单个异常点极度敏感。Kriging的思路完全不同,它先用变异函数描述“两个点之间的差异随距离怎么变化”,再基于这个结构去求解每个已知点对未知点贡献的最优权重。这个权重不是拍脑袋定的,而是在“无偏”和“估计方差最小”两个约束下解出来的。

理解Kriging时最容易卡住的点在这里:它本质上是一个线性方程组求解问题,而不是简单的加权平均。每一个待估点,都要针对周围的已知点构建协方差矩阵,然后求权重向量。这意味着Kriging的计算量天然比IDW大一个量级,但在数据空间结构明显时,它的结果在统计意义上确实更可靠,而且能给出克里金方差——这是后续画置信区间、判断数据是否够密的依据。

2.2 变异函数是Kriging的灵魂,拟合不好全是白搭

变异函数(Variogram)描述的是区域化变量在空间上的相关性随距离衰减的规律。理论上现场采样时就应该先做变异函数分析,再决定采样间距是否合理。但在实际项目里,多数人都是拿到数据后直接调库出图,直到结果不对劲才回头检查半变异函数。半变异函数的计算公式是:对任意距离间隔h,计算所有相距h的点对的属性值之差的平方和的一半。这个值随h增大会先上升,然后趋于平稳——平稳时的值叫基台值(Sill),开始平稳的距离叫变程(Range),在h趋近0时的截距叫块金值(Nugget)。

在Python里做变异函数拟合通常走两步:先用gwestimate计算实验半变异函数,再用fit_variogram_model去匹配理论模型。这里存在一个经常被忽略的关键选择——用linear、power还是spherical做理论模型。对大多数自然采样数据,spherical球状模型在变程内上升平缓、变程外保持平稳,是最常用的默认选择;exponential指数模型收敛更慢,适合数据空间连续性强的场景;linear线性模型几乎没有平台期,只适合数据量极少、不足以看清空间结构的探索性分析。选错模型的表现是拟合曲线严重偏离实验点,后续等值线图会出现条带状伪影,而不是平滑过渡。

2.3 选Kriging变体:Ordinary、Universal还是Simple

确定使用Kriging后,还要在三种变体里做选择。Ordinary Kriging(普通克里金)假设区域化变量的均值未知但恒定,这是绝大多数等值线图任务最稳妥的起点,也是各语言库默认实现最多的类型。Universal Kriging(泛克里金)额外考虑属性值随坐标存在趋势项,比如污染物浓度整体随离工厂距离递减,这时把坐标的一次项或二次项放进模型,可以显著改善边缘区域的插值表现。Simple Kriging则要求均值已知,在真实项目中几乎没有适用场景,可以忽略。

选型建议很简单:先跑Ordinary,看交叉验证结果的均方误差(MSE)。如果误差在数据密集区域依然大且残差有明显空间趋势,再换成Universal并在模型里加入坐标趋势项。不要在第一步就上最复杂的模型,因为你还没有证据证明数据存在趋势——过早引入趋势项,会把随机波动当成信号拟合进去,造成“过拟合式等值线”,在空白区域画出夸张的隆起。

3. 在Python里从数据到等值线图:完整工作流与参数详解

3.1 数据准备:你的数据格式决定后面顺不顺

Kriging对输入数据格式有硬性要求:至少三列——经度(或X坐标)、纬度(或Y坐标)、属性值。坐标和值都必须是数值类型,不能有缺失或文本混入。数据量上,少于50个有效点很难拟合出稳定的变异函数,等值线图基本只能反映采样分布,参考意义有限;50到200个点属于常见范围,但需要检查是否有过于密集的重复采样区域——这种区域会人为拉低块金值,让变程看起来偏小。

数据预处理阶段最影响出图质量的三个操作是:剔除明显异常值(如负浓度的物理不可能值)、坐标标准化(将经纬度投影到平面坐标,如UTM)、以及检查空间分布是否覆盖整个目标区域。在实际项目中,我一般会先把散点图按属性值做颜色映射画出来,肉眼确认一遍数据空间趋势和离群点,再做Kriging——这一步能避免很多后面排查问题的精力。

import numpy as np import pandas as pd import matplotlib.pyplot as plt from pykrige.ok import OrdinaryKriging # 读取采样数据:至少包含 x, y, value 三列 df = pd.read_csv('sampling_data.csv') x = df['x'].values y = df['y'].values z = df['value'].values # 先画散点图检查分布与异常值 sc = plt.scatter(x, y, c=z, cmap='Spectral', s=30) plt.colorbar(sc) plt.xlabel('X (m)') plt.ylabel('Y (m)') plt.title('Sampling points and observed values') plt.show()

这段代码只做一件事:在插值前可视化原始采样点。cmap选了Spectral是为了让高低值对比清晰,方便看到空间上的渐变趋势和异常点。s=30是散点大小,数据量大时可以调小到10左右,避免点与点完全重叠。如果这一步发现数据存在明显离群点,比如周边都是几十,突然有一个上千的值,先回去处理数据而不是直接进入插值流程。

3.2 核心插值流程:OK组件、网格生成与结果解读

pykrige库的OrdinaryKriging是核心入口。它的构造参数中,variogram_model指定理论模型,nlags控制实验半变异函数的距离分组数量,weight选择拟合时是否用点数加权。nlags的默认值是6,这个值对大部分数据够用,但如果数据空间范围大、点对数量分布不均,需要适当增加到10到15,避免分组间隔太粗导致变异函数形状失真。weight='linear'在拟合时对远距离点对给予线性递减权重,适合点对数量随距离明显减少的情况。

from pykrige.ok import OrdinaryKriging import numpy as np # 创建Kriging模型并拟合变异函数 OK = OrdinaryKriging( x, y, z, variogram_model='spherical', nlags=12, weight=True, verbose=True ) # 在某一点上进行插值预测 point_x = 500.0 point_y = 600.0 predicted, variance = OK.execute('points', point_x, point_y) print(f"Predicted value: {predicted[0]:.2f}") print(f"Kriging variance: {variance[0]:.2f}")

execute('points', ...)只输出单点的预测值和克里金方差。这里的variance不是置信区间,而是估计误差方差——数值越大说明这个位置周围数据支撑越弱,插值结果越不可信。在实际工作中,我会用这个方差值做一个掩膜:方差超过某个阈值的区域,在等值线图上以半透明或虚线表示,提醒看图人“这块是外推区,别当真”。verbose=True会打印变异函数拟合的中间参数,建议初学时打开看一次,了解块金值和基台值的实际数值范围。

3.3 生成网格并绘制等值线图

单点预测只是验证,真正的出图需要在一张完整网格上执行插值。这里需要用np.linspace生成覆盖数据范围的网格坐标,再用execute('grid', grid_x, grid_y)一次性算出整个网格的预测值和方差。网格分辨率的选择直接影响出图效率和美观度:100乘100的网格是性价比最高的起点;数据点密集区域可以局部加密到200乘200,但网格太细不仅计算时间翻倍,还会让变异函数在短距离上的波动被过度放大,产生细碎的伪等值线。

import numpy as np import matplotlib.pyplot as plt from matplotlib import cm from pykrige.ok import OrdinaryKriging # 已在前面完成 OK = OrdinaryKriging(...) # 生成网格:范围略大于数据范围,避免边缘截断 grid_x = np.linspace(x.min() - 50, x.max() + 50, 150) grid_y = np.linspace(y.min() - 50, y.max() + 50, 150) # 执行网格插值,返回 (预测值矩阵, 方差矩阵) z_pred, z_var = OK.execute('grid', grid_x, grid_y) # 绘制等值线图 fig, ax = plt.subplots(figsize=(10, 8)) cf = ax.contourf(grid_x, grid_y, z_pred, levels=20, cmap='Spectral') cs = ax.contour(grid_x, grid_y, z_pred, levels=20, colors='k', linewidths=0.3) ax.scatter(x, y, c='k', s=5, alpha=0.5) plt.colorbar(cf, label='Predicted value') plt.xlabel('X (m)') plt.ylabel('Y (m)') plt.title('Kriging Interpolation Contour Map') plt.show()

levels=20表示等值线分20级,这是出图美观度与信息量的平衡点。低于10级会丢失细节,高于30级在数据稀疏区会出现大量密集平行线,看图人根本区分不出真实变化趋势。contourf填充色到时候,底层用的还是contourf的填充结果,所以两者必须用相同的levels值,否则会有填充区域和线条对不上的问题。contour的黑线是为了让每个级别的边界清晰,线宽0.3在打印时不会喧宾夺主。这里的x.min() - 50和x.max() + 50是给网格留出边界余量,避免最外侧的采样点正好卡在网格边缘导致外推异常值。

4. 避坑与排查:这些坑不避开,Kriging图就是废图

4.1 坐标系统不统一导致“飞点”

现象:等值线图上的异常孤岛,散点图看起来坐标没问题,但插值结果完全不对。

原因:数据表格里混用了经纬度和平面坐标,或者部分数据的经纬度被错误地当成了米制坐标。Kriging对距离的计算极度敏感,经纬度1度和1000米的距离尺度完全不同,算出来的变异函数变程会直接失调。

解决:进入Kriging流程前,先将经纬度统一投影到平面坐标(如UTM分区坐标或高斯-克吕格坐标),并确认所有数据在同一坐标系下。我一般在读数据后就加一步:

import pyproj transformer = pyproj.Transformer.from_crs( "EPSG:4326", # WGS84 经纬度 "EPSG:32650", # UTM 50N,按实际区域选择 always_xy=True ) x_new, y_new = transformer.transform(df['lon'].values, df['lat'].values)

always_xy=True保证输入顺序是经度、纬度,而不是纬度、经度。这一步做错,后面的图直接镜像翻转,而且很多时候肉眼不容易察觉。选择UTM分区时要注意,跨两个分区的数据需要用EPSG:326XX系列之外的自定义投影,或者用区域的中心经度算一个局部横轴墨卡托投影。

4.2 变异函数拟合完全脱离实验点

现象:打印拟合参数时看到块金值、基台值与实验半变异函数的散点图明显不在同一尺度上。

原因:nlags设置不当导致实验半变异函数计算失真。当nlags过小时,近距离的点对被平均进同一组,变异函数在短距离上的上升被抹平;当nlags过大时,远距离分组内只有少量点对,计算出的半变异值波动极大,拟合算法被这些噪声带偏。

解决:先用程序打印实验变异函数的点对数量分布,确保每个距离分组内至少有30到50个点对。在pykrige中,可以这样检查:

OK = OrdinaryKriging(x, y, z, variogram_model='spherical', nlags=15) # 查看实验半变异函数值和对应的分组的点对数量 for i in range(len(OK.lags)): print(f"lag={OK.lags[i]:.2f}, semivariance={OK.variogram_y[i]:.2f}, pairs={OK.n_bins[i]}")

如果某个距离分组点对数量少于30,就把nlags调小,或者改用weight=True让拟合时对点对少的分组降权。这里还要注意数据本身的空间分布:如果采样点沿公路呈线状分布,而不是面状覆盖,远距离的分组里点对数量会天然不足,这时增加nlags是南辕北辙,应该做的是补充采样或接受外推区域变大的现实。

4.3 等值线图边缘出现夸张隆起

现象:图幅边缘出现大面积的异常高值或低值区,形态像一个喇叭口向外扩展。

原因:Kriging外推能力的边界所限。越靠近网格边缘,能参与计算的已知点越少,权重被少数的点主导,预测值容易向极端方向漂移。这不是你的代码写错了,而是Kriging对边缘区域的数据支撑不足的必然表现。实际中的采样范围如果小于出图范围,边缘部分的等值线基本是“看图师的艺术加工”。

解决:网格范围不要超出数据范围太多,超出数据范围的部分在图面上加边缘掩膜或注明“外推区”。做法是用matplotlib的pcolormesh配合自定义掩膜,或者直接在图上叠加一个半透明多边形标明数据覆盖范围。还有一种补救方式:在数据范围外追加少量虚拟边界点,将这些点的属性值设为数据均值——这能让边缘区域的插值结果更平滑,但会把“均值外推”的假设强加给图面,必须显式标注,不能默认所有人都知道你这么干过。

4.4 异常值把整个色标拉扯变形

现象:99%区域的插值结果呈平滑渐变,但色标被一两个极端值拉成大部分区域都是同一个颜色。

原因:数据中存在极端异常值,Kriging基于空间相关性的插值会把异常值向周围扩散,同时色标的最大最小值被异常值撑开,正常区域的颜色分辨率被压缩到近乎为零。

解决:在插值前对数据进行分位数截断:

lower = np.percentile(z, 1) upper = np.percentile(z, 99) z_clean = np.clip(z, lower, upper)

把上下1%分位以外的值压到分位边界,然后用z_clean做Kriging。这种做法会在极端值区域引入局部偏差,但对于可视化而言,它比让色标失效要合理得多。如果极端值本身是重要的地质异常体(比如矿化点),不要做截断,改用对数色标或分段色标来处理,而不是让线性色标跟着极端值走。

4.5 等值线在数据稀疏区出现“牛眼”状闭合圈

现象:明明只有一个采样点,周围却画出了完整的多层闭合等值线,像靶子一样。

原因:局部邻域内的插值权重被单个点主导,Kriging在这个位置退化为“以该点为中心的高斯状隆起”。这在块金值很小(数据空间连续性强)时特别明显,因为变异函数在短距离上几乎没有随机噪声分量,模型会尽可能贴合每个已知点。

解决:单纯降级到IDW不能解决问题,IDW的牛眼更严重。正确做法是检查块金值——如果块金值接近0,说明模型认为数据在空间上是完全连续的,每个采样点都会被严格经过,这在自然采样数据中几乎不存在。用set_variogram_model手动调整块金值,给模型一个合理的随机噪声底线:

OK.set_variogram_model({'nugget': 0.01 * np.var(z)})

将块金值设为数据方差的1%到5%,可以有效抑制孤立点周围的过度拟合。具体值需要根据交叉验证结果调整,数据噪声大就往上调,噪声小就往下压。交叉验证时,把每个点依次移出后重新插值预测,看预测值和实际值的差——如果差值在孤立的点上特别大,块金值很可能设小了。

5. 进阶用法:交叉验证量化插值可靠性,让Kriging图经得起质疑

Kriging的好处在于它自带不确定性度量,但如果不出交叉验证报告,别人就无从判断你的图是真实趋势还是插值出来的幻觉。交叉验证的基本做法是留一法(LOOCV):每次移除一个已知点,用其余点做Kriging预测该点的值,然后计算所有点的预测误差。这能告诉你三个关键指标:平均误差(ME)是否接近0,反映无偏性是否满足;均方根误差(RMSE)是否在可接受范围,反映整体预测精度;标准化均方根误差(RMSSE)是否接近1,反映方差估计是否可信。

from pykrige.ok import OrdinaryKriging OK = OrdinaryKriging(x, y, z, variogram_model='spherical') predicted, variance = OK.execute('points', x, y) # 计算交叉验证指标 residuals = z - predicted me = np.mean(residuals) # 平均误差,接近0说明无偏 rmse = np.sqrt(np.mean(residuals**2)) rmsee = rmse / np.sqrt(np.mean(variance)) # 标准化均方根误差,接近1最好 print(f"ME: {me:.3f}") print(f"RMSE: {rmse:.3f}") print(f"RMSSE: {rmsee:.3f}")

execute('points', x, y)传的是所有已知点本身,pykrige会自动逐个执行留一交叉验证,返回的predicted和variance是所有点的预测结果。RMSSE的意义值得展开:如果它远大于1,说明模型预测的方差过小,模型过度自信——等值线图上可能显得很平滑,但真实误差远大于图面暗示的不确定性;如果它远小于1,说明模型预测的方差过大,插值结果被过度保守化,图面看起来大面积不可信,但这其实是在提醒你需要更多采样点来缩小方差,而不是模型本身有问题。

交叉验证结果不理想时,逐项排查的顺序是:先看ME是否明显偏离0——如果偏离,说明存在系统偏差,最常见的是数据本身有趋势没被建模,这时换Universal Kriging加趋势项;再看RMSSE是否在0.8到1.2之间——如果超出,优先调整变异函数模型和块金值,而不是急着换Kriging变体。这个排查顺序能避免浪费时间在错误的参数上调优。

做完交叉验证后,我习惯把所有采样点的预测误差按空间位置画成一张误差散点图——如果误差大的点集中在某个区域,说明那个区域采样不足或存在局部非平稳性,需要在报告里单独说明。这个习惯救过我很多次:在项目评审时,有人指着一张等值线图的局部区域问“这里的值可靠吗”,能拿出误差空间分布图来回答,说明你真正理解了Kriging不只是画图工具,而是一种空间不确定性管理手段。

最后一个使用习惯是:每次出图时,把变异函数拟合的参数(块金值、基台值、变程)和交叉验证指标(ME、RMSE、RMSSE)一起输出保存。同一个数据集换参数后,对比这些数值比对比图面直观得多。我自己就吃过亏——调了一次参数后觉得图看起来更平滑了,差点交付,但交叉验证RMSE其实从12涨到了19,图面美观掩盖了预测精度下降。Kriging这个工具,图是给人看的,数字才是用来决策的。希望这份工作流能帮你少走我之前走过的弯路,也欢迎你在自己的数据上跑一遍,看看数字和图面之间的真实关系。

本文还有配套的精品资源,点击获取

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

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

立即咨询