Pandas统计DataFrame坐标行n英里范围内其他行数量的方案优化
优化方案
原代码的性能瓶颈主要有两点:
- 采用
itertuples+apply的双重Python层循环,时间复杂度为O(n²),数据量稍大就会极度耗时 - 调用
geopy.geodesic做逐行距离计算,高精度接口的计算开销远高于批量向量化运算
方案1:向量化哈弗辛距离计算(适合中小数据量,n<5000)
直接用numpy实现哈弗辛公式,批量计算所有点对的球面距离,一次矩阵运算得到全量距离后批量统计,完全避免Python层循环,性能比原代码提升百倍以上。
import numpy as np import pandas as pd # 哈弗辛公式计算球面距离,返回单位为英里 def haversine_matrix(lat, lon): # 经纬度转弧度 lat_rad = np.radians(lat) lon_rad = np.radians(lon) # 广播计算所有点对的经纬度差值 dlat = lat_rad[:, None] - lat_rad dlon = lon_rad[:, None] - lon_rad # 哈弗辛公式计算球面距离 a = np.sin(dlat / 2)**2 + np.cos(lat_rad[:, None]) * np.cos(lat_rad) * np.sin(dlon / 2)**2 c = 2 * np.arcsin(np.sqrt(a)) # 地球半径取3958.8英里 miles = 3958.8 * c return miles # 示例输入 df = pd.DataFrame({'LATITUDE':[38.9547, 38.9404, 38.9032, 38.864, 38.8639, 38.9017, 38.947783, 38.8629, 38.9478, 38.9017, 38.9491, 38.8643, 38.8643, 38.9464, 38.903], 'LONGITUDE':[-77.0463, -77.048, -77.1417, -77.156, -77.1631, -77.131, -77.03983, -77.1558, -77.0439, -77.131, -77.0461, -77.1539, -77.1539, -77.0385, -77.1411]}) # 计算全量点对距离矩阵 dist_matrix = haversine_matrix(df['LATITUDE'].values, df['LONGITUDE'].values) # 统计各距离区间的其他点数量,排除自身(距离为0) df['1_mile'] = np.sum((dist_matrix <= 1) & (dist_matrix > 0), axis=1) df['5_miles'] = np.sum((dist_matrix <= 5) & (dist_matrix > 0), axis=1) df['10_miles'] = np.sum((dist_matrix <= 10) & (dist_matrix > 0), axis=1)
该方案输出和原代码完全一致,若数据量超过5000条,全量距离矩阵会占用较多内存,可使用下面的BallTree方案。
方案2:BallTree半径查询(适合大数据量,n>5000)
使用sklearn的BallTree,基于球面距离度量做半径近邻查询,时间复杂度为O(n log n),内存占用极低,适合大数量级场景:
import numpy as np import pandas as pd from sklearn.neighbors import BallTree # 示例输入 df = pd.DataFrame({'LATITUDE':[38.9547, 38.9404, 38.9032, 38.864, 38.8639, 38.9017, 38.947783, 38.8629, 38.9478, 38.9017, 38.9491, 38.8643, 38.8643, 38.9464, 38.903], 'LONGITUDE':[-77.0463, -77.048, -77.1417, -77.156, -77.1631, -77.131, -77.03983, -77.1558, -77.0439, -77.131, -77.0461, -77.1539, -77.1539, -77.0385, -77.1411]}) # BallTree的haversine度量要求输入为弧度格式的[纬度, 经度] coords = np.radians(df[['LATITUDE', 'LONGITUDE']].values) # 构建BallTree tree = BallTree(coords, metric='haversine') # 英里转弧度:查询半径/地球半径(3958.8英里) radii = np.array([1, 5, 10]) / 3958.8 # 批量查询每个点对应半径内的点数量,减1排除自身 counts = tree.query_radius(coords, r=radii.reshape(-1,1), count_only=True) - 1 df['1_mile'], df['5_miles'], df['10_miles'] = counts[0], counts[1], counts[2]
该方案在10万条数据量级下也能在几秒内跑完,远快于原逐行迭代方案。
内容的提问来源于stack exchange,提问作者amance
相关产品推荐
相关产品推荐

