第19章:聚合与融合(Dissolve)
融合(Dissolve)是地理空间分析中的重要操作,它将具有相同属性值的相邻或不相邻要素合并为一个要素,同时对其他属性进行聚合计算。本章将全面介绍 GeoPandas 中 dissolve() 方法的使用,包括基础用法、聚合函数、多属性融合及性能优化等内容。
19.1 聚合与融合概述
19.1.1 什么是融合
融合(Dissolve)是将多个几何要素根据某个属性字段的值进行分组,然后将同组要素的几何合并(Union),并对其他数值属性进行聚合运算(如求和、求平均等)的操作。
例如:
- 将县级行政区按省份融合,生成省级边界
- 将地块按土地利用类型融合,生成土地利用分区图
- 将建筑按所属社区融合,统计各社区建筑面积
19.1.2 融合的工作流程
原始数据(多个要素) 融合结果(合并后)
+---+---+---+ +---------+
| A | A | B | | A |B|
+---+---+---+ dissolve +---------+-+
| B | A | A | ==========> |B| A |
+---+---+---+ by='类型' +-+---------+
| A | B | B | |A| B |
+---+---+---+ +-+---------+
19.1.3 融合与 pandas groupby 的关系
dissolve() 在概念上等价于 pandas 的 groupby() + 几何合并:
| 操作 | pandas groupby | GeoDataFrame dissolve |
|---|---|---|
| 分组依据 | 列值 | 列值 |
| 数值处理 | 聚合函数 | 聚合函数 |
| 几何处理 | 不涉及 | 几何合并(Union) |
| 结果类型 | DataFrame | GeoDataFrame |
import geopandas as gpd
from shapely.geometry import Polygon
# 示例数据
gdf = gpd.GeoDataFrame({
'region': ['东', '东', '西', '西'],
'type': ['住宅', '商业', '住宅', '商业'],
'population': [5000, 2000, 3000, 1500],
'area_km2': [10, 5, 8, 4],
'geometry': [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
Polygon([(0, 2), (2, 2), (2, 4), (0, 4)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)])
]
})
print("原始数据:")
print(gdf)
19.2 dissolve() 基础用法
19.2.1 基本语法
GeoDataFrame.dissolve(
by=None,
aggfunc='first',
as_index=True,
level=None,
sort=True,
observed=False,
dropna=True
)
19.2.2 参数说明
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
by |
str / list | 分组字段 | None(全局融合) |
aggfunc |
str / dict / func | 聚合函数 | 'first' |
as_index |
bool | 分组字段是否作为索引 | True |
sort |
bool | 是否对分组键排序 | True |
dropna |
bool | 是否丢弃分组键为 NaN 的组 | True |
19.2.3 最简单的融合
# 按 region 融合
dissolved = gdf.dissolve(by='region')
print("融合结果:")
print(dissolved)
print(f"\n原始要素数: {len(gdf)}")
print(f"融合后要素数: {len(dissolved)}")
19.2.4 融合结果的结构
dissolved = gdf.dissolve(by='region')
# 索引变为分组字段
print("索引:", dissolved.index.tolist())
# 几何被合并
for region, row in dissolved.iterrows():
print(f"{region}: 几何类型={row.geometry.geom_type}, 面积={row.geometry.area:.1f}")
# 非几何属性使用默认聚合函数 'first'
print("\n属性值(默认取第一个):")
print(dissolved[['type', 'population', 'area_km2']])
19.3 按属性融合
19.3.1 单属性融合
import geopandas as gpd
from shapely.geometry import Polygon
# 县级行政区数据
counties = gpd.GeoDataFrame({
'county': ['甲县', '乙县', '丙县', '丁县', '戊县', '己县'],
'province': ['A省', 'A省', 'A省', 'B省', 'B省', 'B省'],
'population': [50, 80, 60, 70, 40, 90],
'gdp': [100, 200, 150, 180, 90, 250],
'geometry': [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
Polygon([(0, 2), (2, 2), (2, 4), (0, 4)]),
Polygon([(4, 0), (6, 0), (6, 2), (4, 2)]),
Polygon([(4, 2), (6, 2), (6, 4), (4, 4)]),
Polygon([(6, 0), (8, 0), (8, 4), (6, 4)])
]
})
# 按省份融合
provinces = counties.dissolve(by='province', aggfunc='sum')
print("省级数据:")
print(provinces[['population', 'gdp']])
# 检查几何合并结果
for prov, row in provinces.iterrows():
print(f"\n{prov}:")
print(f" 几何类型: {row.geometry.geom_type}")
print(f" 面积: {row.geometry.area:.1f}")
print(f" 总人口: {row['population']} 万")
print(f" GDP: {row['gdp']} 亿")
19.3.2 融合后的几何类型
融合将同组的几何进行 Union 操作。如果同组的几何相邻,结果为 Polygon;如果不相邻,结果为 MultiPolygon:
import geopandas as gpd
from shapely.geometry import Polygon
# 相邻的两个多边形
adjacent = gpd.GeoDataFrame({
'group': ['X', 'X'],
'geometry': [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)])
]
})
# 不相邻的两个多边形
separate = gpd.GeoDataFrame({
'group': ['Y', 'Y'],
'geometry': [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(3, 3), (4, 3), (4, 4), (3, 4)])
]
})
dissolved_adj = adjacent.dissolve(by='group')
dissolved_sep = separate.dissolve(by='group')
print(f"相邻融合: {dissolved_adj.geometry.iloc[0].geom_type}")
# 输出: Polygon
print(f"不相邻融合: {dissolved_sep.geometry.iloc[0].geom_type}")
# 输出: MultiPolygon
19.3.3 as_index 参数
# 默认: 分组字段作为索引
dissolved_idx = counties.dissolve(by='province', aggfunc='sum')
print("作为索引:")
print(dissolved_idx.index.tolist())
# as_index=False: 分组字段保留为普通列
dissolved_col = counties.dissolve(by='province', aggfunc='sum', as_index=False)
print("\n作为列:")
print(dissolved_col.columns.tolist())
print(dissolved_col[['province', 'population']])
19.4 聚合函数(aggfunc 参数)
19.4.1 内置聚合函数
aggfunc 参数支持多种内置聚合函数:
| 聚合函数 | 说明 | 示例输出 |
|---|---|---|
'first' |
组内第一个值(默认) | 第一条记录的值 |
'last' |
组内最后一个值 | 最后一条记录的值 |
'sum' |
求和 | 人口总数 |
'mean' |
求平均 | 平均 GDP |
'min' |
最小值 | 最低温度 |
'max' |
最大值 | 最高海拔 |
'count' |
计数 | 要素个数 |
'median' |
中位数 | 中位收入 |
'std' |
标准差 | 值的离散程度 |
import geopandas as gpd
from shapely.geometry import Polygon
data = gpd.GeoDataFrame({
'district': ['东区', '东区', '东区', '西区', '西区'],
'land_type': ['住宅', '商业', '住宅', '商业', '住宅'],
'value': [100, 200, 150, 180, 120],
'count': [10, 5, 8, 6, 12],
'geometry': [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
Polygon([(0, 1), (1, 1), (1, 2), (0, 2)]),
Polygon([(2, 0), (3, 0), (3, 1), (2, 1)]),
Polygon([(2, 1), (3, 1), (3, 2), (2, 2)])
]
})
# 不同聚合函数的效果
for func in ['first', 'sum', 'mean', 'min', 'max']:
result = data.dissolve(by='district', aggfunc=func)
east_val = result.loc['东区', 'value']
print(f"aggfunc='{func}': 东区 value = {east_val}")
19.4.2 字典形式指定不同列的聚合方式
可以为不同的列指定不同的聚合函数:
# 对不同列使用不同的聚合函数
dissolved = data.dissolve(
by='district',
aggfunc={
'value': 'sum', # value 列求和
'count': 'sum', # count 列求和
'land_type': 'first' # land_type 取第一个
}
)
print("字典形式聚合:")
print(dissolved[['value', 'count', 'land_type']])
19.4.3 自定义聚合函数
import numpy as np
# 使用 numpy 函数
dissolved_np = data.dissolve(by='district', aggfunc=np.sum)
print("numpy.sum 聚合:")
print(dissolved_np[['value', 'count']])
# 使用 lambda 函数
dissolved_range = data.dissolve(
by='district',
aggfunc=lambda x: x.max() - x.min()
)
print("\n极差(max - min)聚合:")
print(dissolved_range[['value']])
19.4.4 多函数聚合
结合 pandas 的能力,可以对同一列应用多个聚合函数:
import geopandas as gpd
import pandas as pd
# 先融合几何
dissolved_geom = data.dissolve(by='district')
# 再用 groupby 计算详细统计
stats = data.groupby('district').agg({
'value': ['sum', 'mean', 'min', 'max', 'std'],
'count': ['sum', 'mean']
})
stats.columns = ['_'.join(col) for col in stats.columns]
# 合并几何和统计结果
result = dissolved_geom[['geometry']].join(stats)
print("多函数聚合结果:")
print(result.drop(columns='geometry'))
19.5 多属性融合
19.5.1 按多个字段分组
by 参数可以接受列表,实现多层次的分组融合:
import geopandas as gpd
from shapely.geometry import Polygon
# 详细的土地利用数据
parcels = gpd.GeoDataFrame({
'district': ['A区', 'A区', 'A区', 'A区', 'B区', 'B区', 'B区', 'B区'],
'land_use': ['住宅', '住宅', '商业', '商业', '住宅', '住宅', '商业', '商业'],
'area_m2': [1000, 1200, 800, 900, 1100, 950, 750, 850],
'value': [500, 600, 1000, 1100, 450, 550, 900, 1050],
'geometry': [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
Polygon([(0, 1), (1, 1), (1, 2), (0, 2)]),
Polygon([(1, 1), (2, 1), (2, 2), (1, 2)]),
Polygon([(3, 0), (4, 0), (4, 1), (3, 1)]),
Polygon([(4, 0), (5, 0), (5, 1), (4, 1)]),
Polygon([(3, 1), (4, 1), (4, 2), (3, 2)]),
Polygon([(4, 1), (5, 1), (5, 2), (4, 2)])
]
})
# 按区和用地类型融合
dissolved_multi = parcels.dissolve(
by=['district', 'land_use'],
aggfunc={'area_m2': 'sum', 'value': 'sum'}
)
print("多属性融合结果:")
print(dissolved_multi[['area_m2', 'value']])
19.5.2 层次化融合
可以先做细粒度融合,再做粗粒度融合:
# 第一层:按区+用地类型融合
level1 = parcels.dissolve(
by=['district', 'land_use'],
aggfunc='sum'
)
print("第一层融合(区+用地类型):")
print(level1[['area_m2', 'value']])
# 第二层:在第一层基础上按区融合
level2 = level1.reset_index().dissolve(
by='district',
aggfunc='sum'
)
print("\n第二层融合(仅按区):")
print(level2[['area_m2', 'value']])
19.5.3 融合后重新设置索引
# 多属性融合后索引为 MultiIndex
dissolved = parcels.dissolve(by=['district', 'land_use'], aggfunc='sum')
print("MultiIndex:")
print(dissolved.index)
# 重置索引为普通列
dissolved_flat = dissolved.reset_index()
print("\n重置索引后:")
print(dissolved_flat[['district', 'land_use', 'area_m2', 'value']])
19.6 全局融合(dissolve all)
19.6.1 不指定 by 参数
当不指定 by 参数时,所有要素融合为一个:
import geopandas as gpd
from shapely.geometry import Polygon
gdf = gpd.GeoDataFrame({
'name': ['A', 'B', 'C'],
'population': [100, 200, 150],
'geometry': [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
Polygon([(1, 2), (3, 2), (3, 4), (1, 4)])
]
})
# 全局融合
total = gdf.dissolve(aggfunc='sum')
print(f"融合后要素数: {len(total)}")
print(f"总人口: {total['population'].iloc[0]}")
print(f"几何类型: {total.geometry.iloc[0].geom_type}")
print(f"总面积: {total.geometry.area.iloc[0]:.2f}")
19.6.2 生成外轮廓
全局融合常用于生成数据集的外部边界:
import geopandas as gpd
from shapely.geometry import Polygon
# 多个地块
plots = gpd.GeoDataFrame({
'plot_id': range(6),
'geometry': [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
Polygon([(4, 0), (6, 0), (6, 2), (4, 2)]),
Polygon([(0, 2), (2, 2), (2, 4), (0, 4)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
Polygon([(4, 2), (6, 2), (6, 4), (4, 4)])
]
})
# 全局融合生成外轮廓
outline = plots.dissolve()
print(f"地块数: {len(plots)}")
print(f"外轮廓: {outline.geometry.iloc[0].geom_type}")
print(f"外轮廓面积: {outline.geometry.area.iloc[0]:.1f}")
# 获取边界线
boundary = outline.boundary
print(f"边界长度: {boundary.length.iloc[0]:.2f}")
19.6.3 全局融合与 union_all 的区别
# dissolve() 返回 GeoDataFrame,保留属性聚合
dissolved = gdf.dissolve(aggfunc='sum')
print(f"dissolve 类型: {type(dissolved)}")
print(f"dissolve 列: {dissolved.columns.tolist()}")
# union_all() 返回单个 Geometry 对象
unioned = gdf.union_all()
print(f"\nunion_all 类型: {type(unioned)}")
# 不包含任何属性信息
19.7 融合与 pandas groupby 的关系
19.7.1 概念对比
import geopandas as gpd
import pandas as pd
from shapely.geometry import Polygon
data = gpd.GeoDataFrame({
'category': ['A', 'A', 'B', 'B'],
'value': [10, 20, 30, 40],
'geometry': [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
Polygon([(0, 1), (1, 1), (1, 2), (0, 2)]),
Polygon([(1, 1), (2, 1), (2, 2), (1, 2)])
]
})
# pandas groupby - 只聚合属性
grouped = data.groupby('category').agg({'value': 'sum'})
print("groupby 结果:")
print(grouped)
print(f"类型: {type(grouped)}")
# dissolve - 聚合属性 + 合并几何
dissolved = data.dissolve(by='category', aggfunc='sum')
print("\ndissolve 结果:")
print(dissolved[['value']])
print(f"类型: {type(dissolved)}")
print(f"包含几何: {dissolved.geometry is not None}")
19.7.2 等价操作
dissolve() 大致等价于以下步骤:
import geopandas as gpd
from shapely.ops import unary_union
# 手动实现 dissolve
def manual_dissolve(gdf, by, aggfunc='sum'):
# 按属性分组
grouped = gdf.groupby(by)
results = []
for name, group in grouped:
# 合并几何
merged_geom = group.union_all()
# 聚合属性
numeric_cols = group.select_dtypes(include='number').columns
if aggfunc == 'sum':
agg_values = group[numeric_cols].sum()
elif aggfunc == 'mean':
agg_values = group[numeric_cols].mean()
row = {by: name, 'geometry': merged_geom}
row.update(agg_values.to_dict())
results.append(row)
return gpd.GeoDataFrame(results).set_index(by)
# 对比结果
manual = manual_dissolve(data, 'category', 'sum')
builtin = data.dissolve(by='category', aggfunc='sum')
print("手动实现结果:")
print(manual[['value']])
print("\n内置 dissolve 结果:")
print(builtin[['value']])
19.7.3 结合 groupby 实现复杂聚合
import geopandas as gpd
import numpy as np
from shapely.geometry import Polygon
sales_data = gpd.GeoDataFrame({
'store': ['店铺A', '店铺B', '店铺C', '店铺D', '店铺E', '店铺F'],
'region': ['北区', '北区', '北区', '南区', '南区', '南区'],
'revenue': [100, 150, 80, 200, 120, 160],
'employees': [5, 8, 4, 10, 6, 7],
'geometry': [
Polygon([(0, 4), (1, 4), (1, 5), (0, 5)]),
Polygon([(1, 4), (2, 4), (2, 5), (1, 5)]),
Polygon([(2, 4), (3, 4), (3, 5), (2, 5)]),
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
Polygon([(2, 0), (3, 0), (3, 1), (2, 1)])
]
})
# 融合几何
dissolved_geom = sales_data.dissolve(by='region')
# 使用 groupby 计算丰富的统计
stats = sales_data.groupby('region').agg(
总营收=('revenue', 'sum'),
平均营收=('revenue', 'mean'),
最高营收=('revenue', 'max'),
总员工=('employees', 'sum'),
店铺数=('store', 'count'),
人均营收=('revenue', lambda x: x.sum() / sales_data.loc[x.index, 'employees'].sum())
).round(1)
# 合并几何和统计
result = dissolved_geom[['geometry']].join(stats)
print("区域综合统计:")
print(result.drop(columns='geometry').to_string())
19.8 性能优化
19.8.1 大数据集融合
对于大数据集,融合的性能瓶颈通常在几何合并操作上:
import geopandas as gpd
from shapely.geometry import Polygon
import numpy as np
import time
np.random.seed(42)
# 创建大规模数据
n = 10000
gdf_large = gpd.GeoDataFrame({
'category': np.random.choice(['A', 'B', 'C', 'D', 'E'], n),
'value': np.random.uniform(0, 100, n),
'geometry': [
Polygon([
(x, y), (x+0.01, y), (x+0.01, y+0.01), (x, y+0.01)
])
for x, y in zip(
np.random.uniform(0, 100, n),
np.random.uniform(0, 100, n)
)
]
})
start = time.time()
dissolved = gdf_large.dissolve(by='category', aggfunc='sum')
elapsed = time.time() - start
print(f"融合 {n} 个要素 -> {len(dissolved)} 个要素")
print(f"耗时: {elapsed:.3f} 秒")
19.8.2 优化策略
import geopandas as gpd
import numpy as np
import time
# 策略一:预排序数据
start = time.time()
gdf_sorted = gdf_large.sort_values('category')
dissolved_1 = gdf_sorted.dissolve(by='category', aggfunc='sum')
print(f"预排序后融合: {time.time() - start:.3f} 秒")
# 策略二:先简化几何再融合
start = time.time()
gdf_simple = gdf_large.copy()
gdf_simple['geometry'] = gdf_simple.geometry.simplify(tolerance=0.001)
dissolved_2 = gdf_simple.dissolve(by='category', aggfunc='sum')
print(f"简化后融合: {time.time() - start:.3f} 秒")
# 策略三:分步处理 - 先聚合属性,后合并几何
start = time.time()
attr_agg = gdf_large.drop(columns='geometry').groupby('category').sum()
geom_agg = gdf_large[['category', 'geometry']].dissolve(by='category')
dissolved_3 = geom_agg.join(attr_agg)
print(f"分步处理: {time.time() - start:.3f} 秒")
19.8.3 内存优化
# 融合前删除不需要的列
cols_needed = ['category', 'value', 'geometry']
gdf_lean = gdf_large[cols_needed].copy()
# 设置合适的数据类型
gdf_lean['value'] = gdf_lean['value'].astype('float32')
dissolved = gdf_lean.dissolve(by='category', aggfunc='sum')
print(f"精简后融合结果: {len(dissolved)} 个要素")
19.8.4 性能对比
| 优化策略 | 适用场景 | 效果 |
|---|---|---|
| 预排序 | 数据未按分组字段排序 | 小幅加速 |
| 简化几何 | 高精度但不需要细节 | 显著加速 |
| 分步处理 | 属性聚合复杂 | 灵活性更高 |
| 减少列数 | 有很多不需要的列 | 降低内存 |
| 优化数据类型 | 数值精度过高 | 降低内存 |
19.9 实际应用案例
19.9.1 案例一:行政区划合并
import geopandas as gpd
from shapely.geometry import Polygon
# 乡镇级行政区
townships = gpd.GeoDataFrame({
'township': ['镇A', '镇B', '镇C', '镇D', '镇E', '镇F'],
'county': ['X县', 'X县', 'X县', 'Y县', 'Y县', 'Y县'],
'city': ['M市', 'M市', 'M市', 'M市', 'M市', 'M市'],
'population': [20000, 35000, 15000, 45000, 28000, 32000],
'area_km2': [50, 80, 40, 100, 60, 75],
'geometry': [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
Polygon([(0, 2), (4, 2), (4, 4), (0, 4)]),
Polygon([(4, 0), (7, 0), (7, 2), (4, 2)]),
Polygon([(4, 2), (7, 2), (7, 4), (4, 4)]),
Polygon([(7, 0), (10, 0), (10, 4), (7, 4)])
]
})
# 融合为县级
county_level = townships.dissolve(
by='county',
aggfunc={
'population': 'sum',
'area_km2': 'sum',
'township': 'count'
}
).rename(columns={'township': 'township_count'})
county_level['pop_density'] = county_level['population'] / county_level['area_km2']
print("县级统计:")
print(county_level[['population', 'area_km2', 'township_count', 'pop_density']].round(1))
# 进一步融合为市级
city_level = townships.dissolve(
by='city',
aggfunc={
'population': 'sum',
'area_km2': 'sum',
'township': 'count'
}
)
print(f"\n市级总人口: {city_level['population'].iloc[0]:,}")
print(f"市级总面积: {city_level['area_km2'].iloc[0]} km²")
19.9.2 案例二:土地利用类型合并
import geopandas as gpd
from shapely.geometry import Polygon
import numpy as np
np.random.seed(42)
# 细分类土地利用
detailed_lu = gpd.GeoDataFrame({
'lu_code': ['011', '012', '021', '022', '031', '032', '041', '042'],
'lu_name': ['水田', '旱地', '有林地', '灌木林', '天然草地', '人工草地', '商服用地', '住宅用地'],
'lu_class': ['耕地', '耕地', '林地', '林地', '草地', '草地', '建设用地', '建设用地'],
'area_ha': [200, 150, 100, 80, 60, 40, 50, 120],
'geometry': [
Polygon([(0, 0), (3, 0), (3, 2), (0, 2)]),
Polygon([(3, 0), (5, 0), (5, 2), (3, 2)]),
Polygon([(5, 0), (7, 0), (7, 2), (5, 2)]),
Polygon([(7, 0), (9, 0), (9, 2), (7, 2)]),
Polygon([(0, 2), (2, 2), (2, 4), (0, 4)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
Polygon([(4, 2), (6, 2), (6, 4), (4, 4)]),
Polygon([(6, 2), (9, 2), (9, 4), (6, 4)])
]
})
# 按一级分类融合
class_level = detailed_lu.dissolve(
by='lu_class',
aggfunc={
'area_ha': 'sum',
'lu_code': 'count'
}
).rename(columns={'lu_code': 'sub_types'})
# 计算占比
total_area = class_level['area_ha'].sum()
class_level['proportion'] = (class_level['area_ha'] / total_area * 100).round(1)
print("一级分类土地利用统计:")
print(class_level[['area_ha', 'sub_types', 'proportion']])
print(f"\n总面积: {total_area} 公顷")
19.9.3 案例三:服务区域合并分析
import geopandas as gpd
from shapely.geometry import Point, Polygon
# 连锁门店数据
stores = gpd.GeoDataFrame({
'store_id': range(1, 9),
'brand': ['品牌A', '品牌A', '品牌A', '品牌A',
'品牌B', '品牌B', '品牌B', '品牌B'],
'monthly_revenue': [50, 80, 60, 70, 90, 40, 55, 65],
'geometry': [
Point(1, 1), Point(3, 1), Point(1, 3), Point(3, 3),
Point(6, 1), Point(8, 1), Point(6, 3), Point(8, 3)
]
})
# 生成服务半径缓冲区
stores_buffer = stores.copy()
stores_buffer['geometry'] = stores.geometry.buffer(1.5)
# 按品牌融合服务区域
brand_coverage = stores_buffer.dissolve(
by='brand',
aggfunc={
'monthly_revenue': 'sum',
'store_id': 'count'
}
).rename(columns={'store_id': 'store_count'})
# 计算覆盖面积
brand_coverage['coverage_area'] = brand_coverage.geometry.area
print("品牌服务区域分析:")
print(brand_coverage[['store_count', 'monthly_revenue', 'coverage_area']].round(2))
# 计算服务区域重叠率
for brand, row in brand_coverage.iterrows():
individual_area = stores_buffer[stores_buffer['brand'] == brand].geometry.area.sum()
overlap = individual_area - row['coverage_area']
overlap_rate = overlap / individual_area * 100
print(f"\n{brand}: 重叠面积={overlap:.2f}, 重叠率={overlap_rate:.1f}%")
19.9.4 案例四:时序数据的空间融合
import geopandas as gpd
from shapely.geometry import Polygon
import pandas as pd
# 多年土地利用变化数据
lu_changes = gpd.GeoDataFrame({
'parcel_id': ['P1', 'P2', 'P3', 'P4', 'P5', 'P6'],
'lu_2020': ['耕地', '耕地', '林地', '林地', '建设用地', '建设用地'],
'lu_2023': ['耕地', '建设用地', '林地', '建设用地', '建设用地', '建设用地'],
'area_ha': [10, 8, 12, 6, 5, 9],
'geometry': [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 0), (4, 0), (4, 2), (2, 2)]),
Polygon([(0, 2), (2, 2), (2, 4), (0, 4)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
Polygon([(4, 0), (6, 0), (6, 2), (4, 2)]),
Polygon([(4, 2), (6, 2), (6, 4), (4, 4)])
]
})
# 识别变化类型
lu_changes['change'] = lu_changes.apply(
lambda r: '未变化' if r['lu_2020'] == r['lu_2023']
else f"{r['lu_2020']}→{r['lu_2023']}", axis=1
)
# 按变化类型融合
change_summary = lu_changes.dissolve(
by='change',
aggfunc={'area_ha': 'sum', 'parcel_id': 'count'}
).rename(columns={'parcel_id': 'parcel_count'})
print("土地利用变化统计:")
print(change_summary[['area_ha', 'parcel_count']])
# 转移矩阵
transfer = lu_changes.groupby(['lu_2020', 'lu_2023'])['area_ha'].sum().unstack(fill_value=0)
print("\n土地利用转移矩阵(公顷):")
print(transfer)
19.10 本章小结
本章全面介绍了 GeoPandas 中的聚合与融合操作。主要内容回顾:
核心方法
| 方法 | 说明 |
|---|---|
gdf.dissolve(by=col) |
按属性融合几何并聚合属性 |
gdf.dissolve() |
全局融合(合并所有几何) |
关键参数
| 参数 | 说明 | 默认值 |
|---|---|---|
by |
分组字段 | None(全局融合) |
aggfunc |
聚合函数 | 'first' |
as_index |
分组字段作为索引 | True |
sort |
对分组键排序 | True |
dropna |
丢弃 NaN 分组 | True |
常用聚合函数
| 函数 | 说明 |
|---|---|
'first' / 'last' |
取第一个/最后一个值 |
'sum' |
求和 |
'mean' |
求平均 |
'min' / 'max' |
最小/最大值 |
'count' |
计数 |
dict |
不同列使用不同函数 |
使用建议
- 选择合适的聚合函数:根据属性含义选择,面积/人口用
sum,密度/比率用mean - 注意几何合并结果:不相邻要素融合后为 MultiPolygon
- 大数据优化:先简化几何、删除多余列,再执行融合
- 属性重新计算:融合后某些派生属性(如密度)需要重新计算
- 结合 groupby:需要复杂统计时,先融合几何,再用 groupby 计算统计
至此,我们学习了空间连接、叠加分析、裁剪掩膜和融合聚合四大空间分析操作。这些操作构成了 GeoPandas 矢量空间分析的核心工具集,能够满足绝大多数地理空间数据处理与分析需求。