如何在Pandas中高效聚合多列,快速生成点集凸包
问题描述
我有一个存储对象ID及对应单个X、Y坐标的DataFrame,结构如下:
| ID | X | Y |
|---|---|---|
| 1 | 0 | 0 |
| 1 | 1 | 3 |
| 1 | 2 | 5 |
| 2 | 7 | 1 |
| 2 | 8 | 5 |
| 2 | 9 | 7 |
无法保证ID或X/Y的顺序,也无法在上游调整该关联。最终目标是获取这些点的凸包(convex hull),当前做法是先将X/Y分组聚合为列表,再打包为元组列表,转换为Shapely的MultiPoint后计算凸包,代码如下:
import shapely.geometry as shGeom sf = df.groupby("ID").agg({"X": list, "Y": list}) # 我希望后续保留该坐标集合,存储为MultiPoint也可以。 # 测试发现,由于内存压力,存储MultiPoint作为中间步骤比存储元组列表更慢 sf["coordinates"] = shapeFrame[["Y", "X"]].apply(lambda x: [(a,b) for a, b in zip(x[0], x[1])], axis= 1) # 下面的"hull"列是最终目标列 sf["hull"] = sf["coordinates"].apply(lambda x: shGeom.MultiPoint(x).convex_hull)
但该方法需对百万行级DataFrame多次遍历,尤其是打包步骤速度极慢。请问是否有减少数据遍历次数的优化方案?后续会使用GeoPandas,但在将X/Y转换为Point或MultiPolygon前无可用几何列,无法避开该慢步骤。
优化方案
方案1:用Numpy向量化聚合,跳过Python循环打包
利用Numpy数组的C级操作替代Python列表推导,彻底避免低效的坐标打包循环:
import numpy as np import shapely.geometry as shGeom # 按ID分组,直接聚合为Numpy数组(比Python列表更高效) sf = df.groupby("ID").agg({"X": np.array, "Y": np.array}) # 直接拼接X/Y数组为坐标矩阵,生成MultiPoint并计算凸包 sf["hull"] = sf.apply( lambda row: shGeom.MultiPoint(np.column_stack((row["X"], row["Y"]))).convex_hull, axis=1 ) # 若需保留原始坐标集合,可存储Numpy数组或MultiPoint sf["coordinates"] = sf.apply(lambda row: np.column_stack((row["X"], row["Y"])), axis=1) # sf["multi_point"] = sf.apply(lambda row: shGeom.MultiPoint(np.column_stack((row["X"], row["Y"]))), axis=1)
核心优势:Numpy数组操作是底层C实现,比Python级别的zip+列表推导快数倍,大幅减少遍历耗时。
方案2:GeoPandas向量化生成Point,直接分组求凸包
提前用GeoPandas的向量化API生成单个Point几何列,再按ID聚合为MultiPoint计算凸包,全程避免手动打包坐标:
import geopandas as gpd from shapely.geometry import Point # 向量化生成Point列(速度远快于循环apply) df["geometry"] = gpd.points_from_xy(df["X"], df["Y"]) # 按ID分组,直接聚合为MultiPoint并计算凸包 sf = df.groupby("ID").agg( hull=("geometry", lambda points: shGeom.MultiPoint(points).convex_hull), multi_point=("geometry", lambda points: shGeom.MultiPoint(points)) )
核心优势:gpd.points_from_xy是GeoPandas专门优化的向量化方法,生成几何列的效率极高;后续分组聚合直接基于几何对象操作,完全跳过手动坐标打包步骤。
方案3:并行计算加速(超大规模数据场景)
如果数据量达到千万级,可结合swifter库自动实现并行化apply,进一步利用多核CPU资源:
import swifter # 基于方案1的代码,用swifter加速apply操作 sf["hull"] = sf.swifter.apply( lambda row: shGeom.MultiPoint(np.column_stack((row["X"], row["Y"]))).convex_hull, axis=1 )
注意:并行计算会占用更多CPU资源,适合在多核服务器上使用。
内容的提问来源于stack exchange,提问作者Philip Kahn
相关产品推荐
相关产品推荐

