匹配两个Pandas DataFrame的GPS坐标新增列,双循环过慢有更优方案吗?
性能优化方案
核心问题分析
- 原实现使用两层
iterrows()遍历,单是iterrows()遍历效率就比向量化操作低1~2个数量级,叠加O(n*m)的时间复杂度(n是GPS点数量、m是待匹配地点数量),数据量稍大就会出现明显卡顿。
方案1:KD树最近邻搜索(最优推荐,时间复杂度O(n*logm))
利用KD树做空间索引批量查询最近邻,是大规模数据下的最优选择,性能提升可达上千倍:
新增依赖
import numpy as np from scipy.spatial import KDTree
实现代码
# 1. 预处理坐标,转为弧度制(适配Haversine球面距离计算) # 提取待匹配地点的坐标数组(纬度、经度顺序,转弧度) loc_arr = np.radians([[p.x, p.y] for p in df_locations['gps']]) # 构建KD树,使用Haversine球面距离 tree = KDTree(loc_arr, metric='haversine') # 提取GPS点的坐标数组,转弧度 gps_arr = np.radians([[p.x, p.y] for p in df_gps['points']]) # 2. 批量查询最近邻,k=1表示只取最近的1个点 distances, indices = tree.query(gps_arr, k=1) # 3. 匹配地点名称,距离转英里(地球半径约3956英里) df_gps['closest_city'] = df_locations['location'].iloc[indices].values df_gps['distance_miles'] = distances * 3956
方案2:向量化广播计算(适合中小规模数据)
如果两个表的数据量都不大(总组合数不超过100万),可以用广播实现全量距离计算,无需额外依赖:
# 1. 拆分坐标为单独列 df_gps[['lat', 'lon']] = df_gps['points'].apply(lambda p: pd.Series([p.x, p.y])) df_locations[['loc_lat', 'loc_lon']] = df_locations['gps'].apply(lambda p: pd.Series([p.x, p.y])) # 2. 向量化计算所有GPS点到所有地点的距离(Haversine公式向量化实现) lat1, lon1 = np.radians(df_gps['lat'].values), np.radians(df_gps['lon'].values) lat2, lon2 = np.radians(df_locations['loc_lat'].values), np.radians(df_locations['loc_lon'].values) # 广播扩展维度计算差值 dlat = lat2[np.newaxis, :] - lat1[:, np.newaxis] dlon = lon2[np.newaxis, :] - lon1[:, np.newaxis] a = np.sin(dlat / 2)**2 + np.cos(lat1[:, np.newaxis]) * np.cos(lat2[np.newaxis, :]) * np.sin(dlon / 2)**2 c = 2 * np.arcsin(np.sqrt(a)) distance_matrix = c * 3956 # 转换为英里单位 # 3. 取距离最小的对应地点 min_indices = distance_matrix.argmin(axis=1) df_gps['closest_city'] = df_locations['location'].iloc[min_indices].values
性能对比
以1万条GPS点、1千个待匹配地点为例:
- 原双循环实现:约需30秒以上
- 向量化广播实现:约需0.1秒
- KD树实现:约需0.01秒
内容的提问来源于stack exchange,提问作者kdbaseball8
相关产品推荐
相关产品推荐

