znlgis 博客

GIS开发与技术分享

第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 不同列使用不同函数

使用建议

  1. 选择合适的聚合函数:根据属性含义选择,面积/人口用 sum,密度/比率用 mean
  2. 注意几何合并结果:不相邻要素融合后为 MultiPolygon
  3. 大数据优化:先简化几何、删除多余列,再执行融合
  4. 属性重新计算:融合后某些派生属性(如密度)需要重新计算
  5. 结合 groupby:需要复杂统计时,先融合几何,再用 groupby 计算统计

至此,我们学习了空间连接、叠加分析、裁剪掩膜和融合聚合四大空间分析操作。这些操作构成了 GeoPandas 矢量空间分析的核心工具集,能够满足绝大多数地理空间数据处理与分析需求。