简介:这份资源是一篇面向地理信息科学、空间统计与空气质量研究方向的课程论文,主题为应用地理加权回归(GWR)模型和克里金插值法对空气质量指数进行预测,适合具备一定Python与GIS基础、希望掌握空间非平稳性建模与空间插值实操的读者。压缩包内仅含1个docx文档,大小约1.08MB,内容涵盖引言、研究方法、GWR案例分析、回归系数显著性分析、预测评估、克里金案例以及总结讨论,并附有GWR预测、克里格插值预测和地图可视化三部分Python源代码。论文详细推导了GWR模型形式、核函数与空间权重矩阵,介绍了CV和AICc带宽优化方法,并对比GWR与普通线性回归在捕捉局部效应上的差异,同时利用克里金插值生成连续污染分布图。目前已有1256人学习下载,读者可据此复现完整研究流程,理解空间异质性建模思路,并直接参考附录代码完成自己的空气质量空间分析与预测任务。
1. 从一份课程设计说起:GWR 与克里金如何联手预测 AQI
空气质量指数预测这件事,很多人第一反应是上 LSTM、XGBoost 这类时序或树模型。但如果你手头的数据带经纬度坐标,而且不同区域的污染成因明显不一样——北边山区和南边城区对同一个气象因子的响应方向可能完全相反——那全局模型就会翻车。这份课程设计走的是另一条路:用地理加权回归(GWR)做局部参数估计,再用克里金插值补全空间上的空白点,两套方法配合完成 AQI 的预测与制图。项目附带完整的 Python 源代码,覆盖 GWR 模型校准、泛克里金插值、地图可视化三个附录,适合做空间分析方向课程设计或想入门地理加权回归的开发者直接跑通、二次开发。
2. 技术选型与整体架构:为什么是 GWR 加克里金
2.1 两套方法的分工逻辑
这个项目的核心思路不是把 GWR 和克里金硬拼在一起,而是让它们各管一段。GWR 负责回答“哪些因素在哪些位置对 AQI 影响更大”,输出的是每个观测点上的局部回归系数和显著性检验结果;克里金负责回答“没有监测站的地方 AQI 大概是多少”,输出的是连续空间上的插值面。前者是回归建模,后者是空间插值,两者共享同一套坐标数据和自变量体系。
从论文的案例分析看,GWR 模型把 R² 从 OLS 的 0.417 拉到了 0.916,AICc 从 3067.762 降到 2567.705,提升幅度相当明显。这说明 AQI 与气温、气压、湿度、风速、方向距离、高度这些变量之间的关系确实存在强烈的空间非平稳性——同一个自变量在北京不同区县对 AQI 的作用方向甚至可能反转。克里金那边用的是泛克里金(Universal Kriging),因为数据存在未知的漂移成分,普通克里金不够用,需要引入多项式漂移项来吸收大尺度趋势。
2.2 项目目录与模块划分
从论文附录和正文结构推断,代码组织大致如下:
project/ ├── gwr_model.py # GWR 模型校准与预测(附录1) ├── kriging_model.py # 泛克里金插值与变异函数拟合(附录2) ├── visualization.py # 地图可视化与显著性制图(附录3) ├── data/ │ ├── aqi_data.csv # 含坐标、AQI、自变量的原始数据 │ └── grid_points.csv # 待预测的网格点坐标 └── output/ ├── gwr_coefficients.csv ├── kriging_surface.csv └── maps/这种划分方式很直白:一个模型一个文件,可视化单独拎出来。对于课程设计级别的项目来说够用,二次开发时如果想加新的核函数或变异函数模型,改对应文件就行,不会牵一发动全身。
2.3 数据流与依赖关系
整个流程的数据流向是:原始 CSV → 数据预处理(剔除全零变量、坐标标准化)→ GWR 模型校准(带宽优化 + 系数求解)→ 系数显著性检验 → 用第 23 期数据预测 → 克里金变异函数拟合 → 泛克里金插值 → 残差分析 → 地图可视化。
依赖的 Python 库常见做法是numpy做矩阵运算、pandas读数据、scipy做优化和统计检验、matplotlib画图。如果要做更专业的地统计,pykrige或gstools也可以,但从论文附录的代码风格看,大概率是手写核心算法,依赖不会太重。
注意:论文中 mm 变量所有值都是 0,建模前必须剔除,否则矩阵求逆会直接报奇异矩阵错误。这是数据预处理阶段最容易忽略的一步。
3. GWR 模型核心实现:从带宽优化到系数求解
3.1 空间权重矩阵与核函数
GWR 的灵魂在于权重矩阵 W。每个观测点对回归点的影响不是等权的,而是随距离衰减。项目用的是 bi-square 核函数,自适应带宽形式:
import numpy as np def bisquare_weight(distance, bandwidth): """ bi-square 核函数计算空间权重 distance: 观测点到回归点的距离数组 bandwidth: 自适应带宽(最近邻个数或距离阈值) 返回:权重数组,超出带宽的权重为0 """ weights = np.zeros_like(distance, dtype=float) mask = distance < bandwidth # bi-square 核心公式:(1 - (d/b)^2)^2 weights[mask] = (1 - (distance[mask] / bandwidth) ** 2) ** 2 return weights这里bandwidth如果传的是距离值,就是固定带宽;如果传的是最近邻个数对应的距离,就是自适应带宽。论文明确说“通常采用自适应的空间带宽优化方式”,所以实际代码里应该先算每个点到其他点的距离矩阵,再根据最近邻个数确定带宽。
参数说明:distance是欧氏距离或球面距离,取决于坐标系;bandwidth是控制衰减速度的关键参数,太小会导致局部过拟合,太大就退化成全局回归。
3.2 带宽优化:CV 与 AICc 的取舍
带宽选不好,GWR 要么过拟合要么欠拟合。论文给了两条路:CV 值最小化或 AICc 最小化。CV 的计算公式是:
def cv_score(y, X, coords, bandwidth): """ 交叉验证得分,越小越好 y: 因变量 X: 自变量矩阵(含截距列) coords: 坐标数组 bandwidth: 当前带宽 """ n = len(y) residuals = np.zeros(n) for i in range(n): # 对每个点 i,用除 i 以外的点拟合,预测 i dist = np.sqrt(np.sum((coords - coords[i]) ** 2, axis=1)) w = bisquare_weight(dist, bandwidth) w[i] = 0 # 排除自身 W = np.diag(w) # 加权最小二乘求解 XtWX = X.T @ W @ X XtWy = X.T @ W @ y beta = np.linalg.solve(XtWX, XtWy) residuals[i] = y[i] - X[i] @ beta return np.sqrt(np.mean(residuals ** 2))这个循环在观测点多的时候会非常慢,因为每个点都要重新解一次加权最小二乘。论文也提到“这个求解计算过程会比较耗时”。实际跑的时候,如果 n 超过 500,建议用 Golden Section 搜索代替网格搜索,减少调用次数。
AICc 的公式在论文里给了,核心是2n*ln(σ) + n*ln(2π) + n*(n+tr(S))/(n-2-tr(S))。其中tr(S)是帽子矩阵的迹,也就是有效参数个数 ENP。AICc 的好处是不用做 n 次留一交叉验证,算一次帽子矩阵就行,效率高很多。
3.3 局部系数估计与显著性检验
带宽定下来之后,每个回归点的系数估计就是标准加权最小二乘:
def gwr_estimate(y, X, coords, bandwidth): """ 返回每个观测点的局部系数估计和标准误 """ n, k = X.shape betas = np.zeros((n, k)) se = np.zeros((n, k)) for i in range(n): dist = np.sqrt(np.sum((coords - coords[i]) ** 2, axis=1)) w = bisquare_weight(dist, bandwidth) W = np.diag(w) XtWX = X.T @ W @ X XtWX_inv = np.linalg.inv(XtWX) beta = XtWX_inv @ X.T @ W @ y betas[i] = beta # 残差平方和 y_hat = X[i] @ beta resid = y[i] - y_hat sigma2 = resid ** 2 # 系数方差对角元 se[i] = np.sqrt(np.diag(XtWX_inv @ X.T @ W @ W @ X @ XtWX_inv) * sigma2) return betas, se拿到系数和标准误后,t 值就是beta / se。论文里对每个自变量在 α=0.05 水平上做了显著性制图,比如 temp 对 AQI 在朝阳、昌平、顺义影响极显著,hpa 在密云、昌平、房山、大兴、石景山显著性极强。这些结论都是逐点 t 检验出来的。
提示:显著性检验的自由度不是简单的 n-k,GWR 的有效自由度是
n - tr(S),算 t 值时分母要用这个。很多网上抄来的代码在这里直接套普通回归的自由度,结果显著性图全错。
4. 克里金插值实现:变异函数拟合与泛克里金求解
4.1 半变异函数与高斯模型拟合
克里金的第一步是拟合变异函数。论文选用高斯模型,拟合参数为:块金常数 3.88e+01,基台值 2.95e-02,变程 7.59e+00。判断参数 Q1=0.63、Q2=1.12、cR=66.68,论文自己评价“拟合效果一般”。Q1 越接近 0 越好,Q2 越接近 1 越好,cR 越小越好,这组参数确实不算理想。
from scipy.optimize import curve_fit def gaussian_variogram(h, nugget, sill, range_): """ 高斯变异函数模型 h: 滞后距 nugget: 块金常数 sill: 基台值 range_: 变程 """ return nugget + sill * (1 - np.exp(-(h ** 2) / (range_ ** 2))) def fit_variogram(coords, values): """ 实验变异函数计算与模型拟合 """ n = len(values) # 计算所有点对的距离和半方差 dists, semivars = [], [] for i in range(n): for j in range(i+1, n): d = np.sqrt(np.sum((coords[i] - coords[j]) ** 2)) dists.append(d) semivars.append(0.5 * (values[i] - values[j]) ** 2) dists = np.array(dists) semivars = np.array(semivars) # 按距离分箱 bins = np.linspace(0, dists.max(), 15) bin_centers, bin_means = [], [] for k in range(len(bins)-1): mask = (dists >= bins[k]) & (dists < bins[k+1]) if mask.sum() > 0: bin_centers.append((bins[k] + bins[k+1]) / 2) bin_means.append(semivars[mask].mean()) # 非线性最小二乘拟合 popt, _ = curve_fit(gaussian_variogram, bin_centers, bin_means, p0=[1.0, 1.0, 1.0], maxfev=5000) return popt # [nugget, sill, range_]分箱的个数会影响拟合效果,论文里拟合一般可能就跟分箱太粗或初值没选好有关。实际调的时候可以把bins数量加到 20 以上,或者换球状模型试试。
4.2 泛克里金方程组构建与求解
泛克里金比普通克里金多了一组漂移项约束。论文里的方程组是:
def universal_kriging(coords, values, target, vario_params, drift_degree=1): """ 泛克里金插值 coords: 已知点坐标 (n, 2) values: 已知点属性值 (n,) target: 待插值点坐标 (m, 2) vario_params: [nugget, sill, range_] drift_degree: 漂移多项式次数,1 表示线性漂移 """ n = len(values) nugget, sill, range_ = vario_params # 构建变异函数矩阵 Gamma = np.zeros((n, n)) for i in range(n): for j in range(n): h = np.sqrt(np.sum((coords[i] - coords[j]) ** 2)) Gamma[i, j] = nugget + sill * (1 - np.exp(-(h ** 2) / (range_ ** 2))) # 漂移项:线性趋势 [1, x, y] F = np.column_stack([np.ones(n), coords[:, 0], coords[:, 1]]) # 组装泛克里金方程组 # [Gamma F] [lambda] [gamma_0] # [F^T 0] [mu ] = [f_0 ] m = target.shape[0] predictions = np.zeros(m) for t in range(m): h0 = np.sqrt(np.sum((coords - target[t]) ** 2, axis=1)) gamma_0 = nugget + sill * (1 - np.exp(-(h0 ** 2) / (range_ ** 2))) f_0 = np.array([1, target[t, 0], target[t, 1]]) # 构建增广矩阵 A = np.zeros((n + 3, n + 3)) A[:n, :n] = Gamma A[:n, n:] = F A[n:, :n] = F.T b = np.zeros(n + 3) b[:n] = gamma_0 b[n:] = f_0 sol = np.linalg.solve(A, b) predictions[t] = sol[:n] @ values return predictions这个实现里漂移项取的是线性趋势,对应论文说的“通常取为次多项式”中的一次。如果数据趋势更复杂,可以把F扩展到二次项。求解出来的sol[:n]就是权重系数 λ,sol[n:]是拉格朗日乘子 μ。
论文用 time=23 期的坐标点做预测,均方误差 423.29,比 GWR 的 449.44 略好一点。但注意这两个误差不是同一套评估体系,GWR 预测的是 AQI 数值,克里金预测的也是 AQI 数值,但克里金的输入是已知点的 AQI 观测值,GWR 的输入是自变量。所以克里金误差小不代表模型更好,只是插值任务本身比回归任务容易。
4.3 残差分析与模型诊断
论文给了残差图(图 4.1),但没有展开讨论。实际做的时候,残差应该检查三件事:有没有空间自相关(Moran's I)、有没有异方差、有没有离群点。如果残差还存在空间结构,说明变异函数模型没拟合好,或者漂移项次数不够。
def residual_diagnosis(coords, residuals): """ 残差空间诊断:Moran's I 简易计算 """ n = len(residuals) # 构建空间权重(反距离) W = np.zeros((n, n)) for i in range(n): for j in range(n): if i != j: d = np.sqrt(np.sum((coords[i] - coords[j]) ** 2)) W[i, j] = 1.0 / d if d > 0 else 0 # 行标准化 W = W / W.sum(axis=1, keepdims=True) # Moran's I z = residuals - residuals.mean() I = (n / W.sum()) * (z @ W @ z) / (z @ z) return IMoran's I 接近 0 说明残差空间随机,模型合格;显著为正说明还有空间聚集没被解释,需要加变量或改模型。
5. 避坑与常见问题排查
5.1 矩阵奇异报错
现象:跑 GWR 或克里金时np.linalg.solve抛LinAlgError: Singular matrix。
原因:自变量之间存在完全共线性,或者某个变量所有值相同(比如论文里的 mm 变量全为 0),导致X^T W X不可逆。
解决:建模前先算方差膨胀因子(VIF)或简单相关系数矩阵,剔除共线性变量和零方差变量。论文里直接剔除了 mm,这是对的。
5.2 带宽优化结果不合理
现象:CV 曲线单调下降或单调上升,找不到最小值;或者最优带宽等于样本量,模型退化成 OLS。
原因:带宽搜索范围设置不当,或者距离矩阵计算时坐标单位没统一(经纬度和米混用)。
解决:先把坐标投影到平面坐标系(如 UTM),确保距离单位一致;带宽搜索范围从“最近邻 10 个点的距离”到“最近邻 n/2 个点的距离”,不要从 0 开始。
5.3 克里金插值出现负值
现象:AQI 插值结果里出现负数,但 AQI 物理上不可能为负。
原因:泛克里金是无偏最优估计,但不保证值域约束。漂移项外推时可能产生负值。
解决:对插值结果做截断(np.maximum(pred, 0)),或者改用对数变换后的数据做插值再反变换。论文里没提这个问题,但实际跑的时候大概率会遇到。
5.4 显著性图与系数图对不上
现象:某个变量系数很大,但显著性检验不显著。
原因:GWR 的 t 检验用的是局部标准误,系数大不代表标准误小。如果该点附近数据稀疏,标准误会很大,t 值就小。
解决:检查数据点空间分布是否均匀。如果某些区域监测站太少,GWR 在那里的估计本身就不稳定,显著性图会呈现大片不显著区域,这是正常的,不要强行解释。
5.5 预测期数据对不齐
现象:用第 23 期数据预测时,报错说自变量数量不匹配或坐标越界。
原因:训练期和预测期的变量列顺序不一致,或者预测点坐标超出了训练数据的凸包范围。
解决:把训练和预测的列名对齐后再转 numpy 数组;预测点如果超出训练范围,克里金外推要谨慎,GWR 外推基本不可靠。
6. 二次开发与改造思路
这份代码最值得改的地方有三个:核函数、带宽优化策略、以及把 GWR 和克里金串成流水线。
先说核函数。目前用的是 bi-square,你可以换成 Gaussian 核,衰减更平滑,适合数据点分布不均匀的情况。改法很简单,在bisquare_weight旁边加一个gaussian_weight,然后在模型入口加个参数切换。但注意 Gaussian 核的权重永远不会真正为 0,计算量会大一些,n 超过 2000 时建议用截断高斯。
再说带宽优化。论文用的 CV 和 AICc 都是全局单一带宽,但实际空间数据往往需要不同区域用不同带宽。可以改成“局部带宽”,每个回归点独立优化自己的带宽,代价是计算量翻 n 倍。折中方案是分区带宽,按行政区或网格分块,每块内共享一个带宽。
最后是流水线化。目前 GWR 和克里金是分开跑的两个脚本,但实际业务里往往是:GWR 预测出 AQI 后,残差再用克里金插值修正,最后合并输出。这个“回归+残差插值”的混合模型在空间统计里叫回归克里金(Regression Kriging),效果通常比单独用任何一个都好。改造思路是:
# 伪代码示意 gwr_pred = gwr_predict(X_new, coords_new, betas) residuals = y_train - gwr_pred_train kriging_residual = universal_kriging(coords_train, residuals, coords_new, vario_params) final_pred = gwr_pred + kriging_residual这样 GWR 捕捉自变量关系,克里金捕捉残差里的空间结构,两者互补。论文里没做这一步,但这是最自然的扩展方向。
验证方法上,建议用留一法或 k 折交叉验证,不要只看训练集 R²。GWR 的 R² 很容易冲到 0.9 以上,但交叉验证的 RMSE 可能并不好看。我一般会同时看三个指标:交叉验证 RMSE、残差 Moran's I、以及预测值 vs 观测值的散点图斜率。三个都过关才认为模型可用。
提示:改造时保留一份原始代码的副本,每改一个模块就跑一次基准测试。GWR 的带宽优化和克里金的变异函数拟合都是非线性优化,改一处可能影响全局,没有基准对比很容易越改越差。
从那以后我每次做空间回归,都强制先画一张残差的空间分布图,确认没有明显的空间聚集再往下走。这个习惯帮我省了很多事后返工的时间。希望帮到你。
本文还有配套的精品资源,点击获取