如何加快DataFrame逐行循环筛选、匹配最近邻地块的运行速度
核心性能瓶颈
你的代码跑几个小时完全是可预见的,核心问题出在这几个地方:
- 纯Python for循环逐行处理数万条主数据,循环本身的开销就非常高
- 每次循环内多次执行
SecondayLatLong.loc[SecondayLatLong['Index'] == xxx],这是全表扫描匹配,单次复杂度就是O(次数据集行数),叠加循环后时间复杂度直接爆炸 - 所有筛选、排序、取值操作都是单条处理,完全没有利用pandas/numpy的向量化加速能力
最优优化方案(推荐,耗时可降到分钟甚至秒级)
不要提前生成完整的距离矩阵(数万x数十万的矩阵本身就要占几十GB内存,计算也极慢),直接用空间索引做批量K近邻查询:
import numpy as np import pandas as pd from sklearn.neighbors import BallTree # 1. 提前做好次数据集的ID映射,后续取值O(1) secondary = SecondayLatLong.set_index('Index')[['AssessedValue', 'YearHomeBuilt']] # 2. 经纬度转弧度,适配BallTree的haversine距离计算 secondary_coords = np.radians(SecondayLatLong[['纬度列名', '经度列名']].values) primary_coords = np.radians(primaryLatLong[['纬度列名', '经度列名']].values) # 3. 对次数据集坐标建BallTree空间索引 tree = BallTree(secondary_coords, metric='haversine') # 4. 距离阈值转弧度:地球半径约3958.8英里,所以rangez(单位英里)转弧度为 rangez/3958.8 radius = rangez / 3958.8 # 5. 批量查询所有主数据点的、在阈值范围内的最近邻居,自动按距离排序 distances, secondary_indices = tree.query_radius( primary_coords, r=radius, return_distance=True, sort_results=True ) # 6. 批量生成结果 result = [] for idx in range(len(primaryLatLong)): cur_dists = distances[idx] cur_indices = secondary_indices[idx] # 过滤掉距离小于0.001英里的 valid_mask = cur_dists * 3958.8 > 0.001 cur_dists = cur_dists[valid_mask] cur_indices = cur_indices[valid_mask] # 取前5个,不足补0 row = [primaryLatLong['Index'][idx]] for n in range(5): if n < len(cur_dists): sec_idx = SecondayLatLong['Index'].iloc[cur_indices[n]] dist = cur_dists[n] * 3958.8 val = secondary.loc[sec_idx, 'AssessedValue'] year = secondary.loc[sec_idx, 'YearHomeBuilt'] row.extend([ sec_idx, f"{dist:.4f} Miles", f"${int(val):,}", int(year) ]) else: row.extend([0,0,0,0]) result.append(row) # 7. 一次性转DataFrame final_df = pd.DataFrame(result, columns=threeclosest.columns)
这个方案的核心是用BallTree的空间索引把近邻查询的复杂度从O(n*m)降到了O(n log m),数万主数据、数十万次数据的场景下,全程耗时一般不会超过1分钟。
如果你必须用已生成的距离矩阵优化
如果距离矩阵已经生成好不想重新计算,可以按下面的方式优化,也能提速10倍以上:
- 先提前做好次数据集的ID索引映射,和上面方案的第一步一样
- 把Dmatrix中不符合阈值(>rangez 或 <=0.001)的位置批量设置为无穷大
- 用
np.argpartition批量取每行最小的5个值的索引,比逐行sort快数倍 - 批量取值拼接结果,不要逐行append Series
额外优化提示
- 不要用裸
except,最好指定具体的异常类型,避免隐藏实际运行错误 - 永远不要逐行append到DataFrame,先把所有结果存在列表里最后一次性转DataFrame,能节省大量IO开销
内容的提问来源于stack exchange,提问作者Benzo
相关产品推荐
相关产品推荐

