Pandas 中基于条件将数据填充到空矩阵:构建地理流动流量的二维汇总表
本文介绍如何使用 pandas 的 pivot_table 和 categorical 配合 fill_value 参数,将原始流动数据(含 origin-destination 地理编码和流量值)高效转换为指定地理单元集合的 n×n 流量矩阵,缺失组合自动补零。
本文介绍如何使用 pandas 的 pivot_table 和 categorical 配合 fill_value 参数,将原始流动数据(含 origin-destination 地理编码和流量值)高效转换为指定地理单元集合的 n×n 流量矩阵,缺失组合自动补零。
在人口迁移、通勤流或区域间经济联系分析中,常需将原始的 OD(Origin-Destination)记录(如 geoid_o, geoid_d, pop_flows)转化为结构化的方阵形式——即行与列均为同一组地理单元(如县 FIPS 编码),矩阵元素表示从行单元到列单元的流量值;未观测到的 OD 对应位置应填入 0。
以下是一个完整、可复现的实现方案:
首先准备示例数据与目标地理单元列表:
import pandas as pd
test_mob_df = pd.DataFrame({
"geoid_o": [10002, 18039, 18039, 18182, 10006, 18111, 18005, 17001],
"geoid_d": [10005, 18039, 18111, 18182, 18005, 17004, 18050, 15001],
"pop_flows": [20, 10, 9, 15, 2, 1, 6, 30]
})
state_county_fip = [18182, 18111, 18005, 18039, 18050, 18001] # 注意:原问题中该列表含 18001,但原始数据无对应记录,仍会保留为行列索引
核心步骤是:确保行列索引覆盖全部目标地理单元(包括无数据的单元),并用 pivot_table 实现稠密矩阵构造。关键在于:
- 使用 pd.Categorical 显式声明 geoid_o 和 geoid_d 的合法取值范围(即 sorted(state_county_fip)),避免 pivot 过程中自动省略缺失类别;
- pivot_table(..., fill_value=0, dropna=False) 确保所有 (o,d) 组合均出现在结果中,缺失项填 0;
- .assign(p = ...) 将流量列重命名为聚合键,提升可读性。
完整代码如下:
result = (
test_mob_df[['geoid_o', 'geoid_d']]
.apply(pd.Categorical, categories=sorted(state_county_fip))
.assign(p=test_mob_df['pop_flows'])
.pivot_table('p', 'geoid_o', 'geoid_d', fill_value=0, dropna=False)
)
print(result)
输出结果为标准的 DataFrame,行列索引均为升序排列的 state_county_fip,且严格包含全部 6×6 组合:
geoid_d 18001 18005 18039 18050 18111 18182 geoid_o 18001 0 0 0 0 0 0 18005 0 0 0 6 0 0 18039 0 0 10 0 9 0 18050 0 0 0 0 0 0 18111 0 0 0 0 0 0 18182 0 0 0 0 0 15
⚠️ 注意事项:
- 若原始数据中存在 geoid_o 或 geoid_d 不在 state_county_fip 中的记录,Categorical 会将其转为 NaN,进而被 pivot_table 忽略(因 dropna=False 仅影响 NaN 值是否参与分组,不恢复非法类别)。因此建议前置过滤:
mask = test_mob_df['geoid_o'].isin(state_county_fip) & test_mob_df['geoid_d'].isin(state_county_fip) filtered_df = test_mob_df[mask].copy()
- 如需返回 NumPy 数组,可直接调用 result.values;
- 若需对角线置零(排除自流向),可在后续执行 np.fill_diagonal(result.values, 0)(注意:需先确保行列索引完全一致且顺序相同)。
该方法简洁、向量化、可扩展,是构建 OD 流量矩阵的标准实践。