Python空间回归实战:用pysal分析美国大选数据(附完整代码)
空间数据分析正在成为社会科学研究的重要工具,特别是在选举预测、房价分析和公共卫生等领域。本文将带你用Python的pysal库,通过2020年美国大选数据,实战演练空间回归分析的全流程。不同于传统回归分析,空间回归考虑了地理单元之间的相互影响,能更准确地捕捉现实世界中的空间依赖现象。
1. 环境准备与数据加载
在开始分析前,我们需要准备Python环境和相关库。推荐使用Anaconda创建独立环境:
conda create -n spatial python=3.8
conda activate spatial
pip install pysal geopandas matplotlib seaborn numpy pandas
数据集包含两个关键部分:县级选举数据(CSV格式)和地理边界数据(GeoJSON格式)。让我们先加载并探索数据:
import pandas as pd
import geopandas as gpd
# 加载选举数据
voting = pd.read_csv('https://raw.github.com/Ziqi-Li/gis5122/master/data/voting_2020.csv')
voting['median_income'] = voting['median_income']/10000 # 收入单位转换为万美元
# 加载地理边界数据
shp = gpd.read_file("https://raw.github.com/Ziqi-Li/gis5122/master/data/us_counties.geojson")
shp_voting = shp.merge(voting, on="county_id")
# 选择分析变量
variable_names = ['sex_ratio', 'pct_black', 'pct_hisp', 'pct_bach', 'median_income','ln_pop_den']
y = shp_voting[['new_pct_dem']].values # 因变量:民主党得票率
X = shp_voting[variable_names].values # 自变量矩阵
数据预处理时需要注意:
- 检查缺失值:
shp_voting.isnull().sum() - 变量标准化:对连续变量考虑Z-score标准化
- 空间权重矩阵的构建方式会影响最终结果
2. 构建空间权重矩阵
空间权重矩阵是空间分析的核心,它定义了地理单元之间的"邻居"关系。pysal提供多种构建方式:
from libpysal.weights import Queen, Rook, KNN
# Queen邻接(共享顶点或边即视为邻居)
w_queen = Queen.from_dataframe(shp_voting)
w_queen.transform = 'R' # 行标准化
# 可视化连接关系
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12,8))
shp_voting.plot(ax=ax, color='white', edgecolor='black')
w_queen.plot(shp_voting, ax=ax, edge_kws=dict(linewidth=0.5, color='red'))
plt.title("Queen Contiguity Spatial Weights")
权重矩阵类型对比:
| 类型 | 定义 | 适用场景 | 优缺点 |
|---|---|---|---|
| Queen | 共享边界或顶点 | 均匀分布区域 | 更密集连接 |
| Rook | 仅共享边界 | 规则网格数据 | 连接较少 |
| KNN | 最近的k个邻居 | 不规则分布点 | 确保每个单元有相同数量邻居 |
| 距离阈值 | 指定距离内为邻居 | 点数据 | 连接数不均 |
提示:实际分析中建议尝试不同权重矩阵,通过Moran's I检验选择最优方案。
3. 基础OLS回归与空间自相关检验
我们先建立普通最小二乘(OLS)模型作为基准:
import spreg
ols = spreg.OLS(y, X, name_y='new_pct_dem', name_x=variable_names)
print(ols.summary)
关键指标解读:
- R-squared: 0.609 - 模型解释约60.9%的方差
- pct_black系数0.483 - 黑人比例每增加1%,民主党得票率增加0.48%
- median_income系数-1.66 - 收入每增加1万美元,得票率下降1.66%
接下来检验残差的空间自相关:
from esda.moran import Moran
from splot.esda import plot_moran
ols_moran = Moran(ols.u, w_queen, permutations=999)
plot_moran(ols_moran, zstandard=True, figsize=(10,4))
print(f"Moran's I: {ols_moran.I:.3f}, p-value: {ols_moran.p_norm:.5f}")
结果显示出强烈的空间自相关(Moran's I=0.6, p<0.001),表明OLS假设被违反,需要考虑空间模型。
4. 空间回归模型实战
4.1 空间误差模型(SEM)
当误差项存在空间依赖时,SEM是合适选择:
sem = spreg.ML_Error(y, X, w=w_queen, name_x=variable_names, name_y='new_pct_dem')
print(sem.summary)
关键发现:
- lambda系数0.87(p<0.001) - 强烈空间误差依赖
- pct_black影响增强到0.75 - 考虑空间因素后效应更大
- AIC=20355 - 优于OLS的23198
验证残差空间自相关:
sem_moran = Moran(sem.e_filtered, w_queen, permutations=999)
print(f"SEM Moran's I: {sem_moran.I:.3f}") # 接近0,空间依赖已消除
4.2 空间滞后模型(SLM)
当因变量存在空间溢出效应时,SLM更合适:
slm = spreg.ML_Lag(y, X, w=w_queen, name_y='new_pct_dem', name_x=variable_names)
print(slm.summary)
模型特点:
- rho系数0.58(p<0.001) - 强烈空间滞后效应
- 教育水平(pct_bach)直接影响0.83
- AIC=21723 - 优于OLS但不及SEM
4.3 模型比较与选择
三种模型关键指标对比:
| 指标 | OLS | SEM | SLM |
|---|---|---|---|
| AIC | 23198 | 20355 | 21723 |
| Moran's I(残差) | 0.60 | -0.08 | 0.15 |
| pct_black系数 | 0.48 | 0.75 | 0.28 |
| 空间参数 | - | lambda=0.87 | rho=0.58 |
根据AIC和残差空间自相关,SEM是最佳选择。但SLM能分析空间溢出效应,适合不同研究目的。
5. 空间溢出效应分析
SLM的一个独特优势是可以量化空间溢出效应。我们以教育水平(pct_bach)为例:
import numpy as np
# 计算总效应矩阵
rho = slm.rho[0][0]
beta_bach = slm.betas[6][0] # pct_bach系数
I = np.identity(w_queen.n)
W = w_queen.full()[0]
total_effects = np.linalg.inv(I - rho * W) * beta_bach
# 分析特定县的影响
leon_idx = shp_voting[shp_voting['NAME_x']=="Leon"].index[0]
shp_voting['leon_effect'] = total_effects[:, leon_idx]
# 可视化佛罗里达州内影响
florida = shp_voting[shp_voting['state']==12]
ax = florida.plot(column='leon_effect', legend=True, figsize=(12,8),
scheme='quantiles', cmap='Reds')
plt.title("Leon县教育水平提升1%对周边县民主党得票率的影响")
分析发现:
- 直接影响:Leon县教育水平提升1%,本地民主党得票率增加0.83%
- 总影响:0.90%(直接+间接)
- 邻近县如Jefferson受影响约0.12%
- 远距离县如Miami-Dade几乎无影响
6. 结果可视化技巧
有效可视化能增强分析说服力。以下是几种专业呈现方式:
空间回归系数图:
# 准备系数数据
models = ['OLS', 'SEM', 'SLM']
coefs = {
'pct_black': [0.483, 0.749, 0.278],
'pct_bach': [0.975, 0.729, 0.835],
'median_income': [-1.660, -2.423, -2.991]
}
# 绘制系数比较
fig, axes = plt.subplots(1, 3, figsize=(18,5))
for ax, (var, vals) in zip(axes, coefs.items()):
ax.bar(models, vals, color=['#4C72B0','#DD8452','#55A868'])
ax.set_title(f'{var}系数比较')
ax.axhline(0, color='black', linewidth=0.8)
plt.tight_layout()
残差空间分布图:
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20,8))
# OLS残差
divider = make_axes_locatable(ax1)
cax = divider.append_axes("right", size="5%", pad=0.1)
shp_voting.plot(column=ols.u.reshape(-1), ax=ax1, legend=True, cax=cax,
scheme='quantiles', cmap='RdBu')
ax1.set_title("OLS残差空间分布", fontsize=14)
# SEM过滤残差
divider = make_axes_locatable(ax2)
cax = divider.append_axes("right", size="5%", pad=0.1)
shp_voting.plot(column=sem.e_filtered.reshape(-1), ax=ax2, legend=True, cax=cax,
scheme='quantiles', cmap='RdBu')
ax2.set_title("SEM过滤残差空间分布", fontsize=14)
在实际项目中,空间回归往往需要多次迭代调试。一个常见问题是权重矩阵选择——我曾遇到使用Queen邻接导致模型不收敛的情况,改用KNN权重后解决。另一个经验是,当空间参数(rho/lambda)接近1时,可能需要检查是否存在空间单元定义错误。
817



被折叠的 条评论
为什么被折叠?



