在另一个数据框中找到最近的点(有很多数据)

6
问题很简单,我有两个数据帧:
  • 一个有90,000套公寓及其纬度/经度的数据帧

  • 一个有3,000家药店及其纬度/经度的数据帧

我希望为所有公寓创建一个新变量:“最近药店的距离”。
为此,我尝试了两种方法花费太多时间

第一种方法:我创建了一个矩阵,我的公寓在行中,我的药店在列中,在它们之间的交汇处是距离,之后我只需取最小值以获得90,000个值的列向量。

我只用numpy中的双重for循环:

m,n=len(result['latitude']),len(pharma['lat'])
M = np.ones((m,n))
for i in range(m):
     for j in range(n):
        if (result['Code departement'][i]==pharma['departement'][j]):
            M[i,j] =(pharma['lat'][j]-result['latitude'][i])**2+(pharma['lng'][j]-result['longitude'] [i])**2

备注:我知道纬度/经度的公式有误,但公寓位于同一地区,这是一个很好的近似。

第二种方法:我使用了这个主题的解决方案(虽然数据较少,但问题类似)。 https://gis.stackexchange.com/questions/222315/geopandas-find-nearest-point-in-other-dataframe

我使用了GeoPandas和最近的方法:

from shapely.ops import nearest_points
pts3 = pharma.geometry.unary_union


def near(point, pts=pts3):
     nearest = pharma.geometry == nearest_points(point, pts)[1]
     return pharma[nearest].geometry.get_values()[0]

appart['Nearest'] = appart.apply(lambda row: near(row.geometry), axis=1)

正如我所说的,这两种方法都花费了太多时间,在运行1个小时后,我的电脑/笔记本死机了且失败了。

我的最终问题: 你是否有一个优化的方法可以更快地进行?这是可能的吗?如果已经优化,我将购买另一台电脑,但要寻找哪些标准才能拥有一个能够进行如此快速计算的PC呢?


我认为你应该遵循你所提到的问题的第二个答案,即使用空间索引来避免距离的全局计算。 - High Performance Mark
1
你有例子吗?因为我有这样的印象,即在第二种解决方案中使用了geopandas中的空间索引,但对所花费的时间没有产生任何影响。 - Arnaud Hureaux
那么我误解了你的代码,之前的评论是错误的。 - High Performance Mark
只是为了澄清,基于shapely的第二个选项不使用空间索引。 - martinfleis
1
不,肯定是我没有理解什么是空间索引。你有例子吗?或者链接? - Arnaud Hureaux
你可以从这里开始 https://geoffboeing.com/2016/10/r-tree-spatial-index-python/,但请记住这是用于交叉点的。我在这里实现了类似的东西 https://docs.momepy.org/en/stable/_modules/momepy/elements.html#get_network_id 。希望能有所帮助。 - martinfleis
1个回答

11

我想Ball Tree是这个任务的适当结构。

您可以使用scikit-learn实现,下面的代码是针对您的情况进行调整的示例:

import numpy as np
import geopandas as gpd
from shapely.geometry import Point
from sklearn.neighbors import BallTree

## Create the two GeoDataFrame to replicate your dataset
appart = gpd.GeoDataFrame({
        'geometry': Point(a, b),
        'x': a,
        'y': b,
    } for a, b in zip(np.random.rand(100000), np.random.rand(100000))
])

pharma = gpd.GeoDataFrame([{
        'geometry': Point(a, b),
        'x': a,
        'y': b,
    } for a, b in zip(np.random.rand(3000), np.random.rand(3000))
])

# Create a BallTree 
tree = BallTree(pharma[['x', 'y']].values, leaf_size=2)

# Query the BallTree on each feature from 'appart' to find the distance
# to the nearest 'pharma' and its id
appart['distance_nearest'], appart['id_nearest'] = tree.query(
    appart[['x', 'y']].values, # The input array for the query
    k=1, # The number of nearest neighbors
)


使用此方法,您可以相当快速地解决问题(如上面的示例,在我的计算机上,在100000个点的输入数据集上查找最近点的索引,只需不到一秒钟就能完成对3000个点的查找)。
默认情况下,BallTree的query方法返回最近邻的距离和其ID。如果您想要禁用返回最近邻的距离,则可以将return_distance参数设置为False。如果您真正只关心距离,您只能保存这个值:
appart['distance_nearest'], _ = tree.query(appart[['x', 'y']].values, k=1)

1
哦,谢谢你,它非常高效和快速 :o 这是一个非常好的消息 :)我试图通过区域进行分割以减少计算量,这也起作用了,但不如BallTree最后一个问题: 如何在纬度和经度上使用balltree获得公里距离?因为这里有一个距离,但我不知道它真正代表什么(如果它有意义)。 - Arnaud Hureaux
1
我认为你应该将你的 pharmaappart 地理数据框转换为使用投影坐标系(例如法国的 'epsg:2154' 或欧洲的 'epsg:3035')通过执行 appart.to_crs(epsg=2154, inplace=True)(对于 pharma 也是如此)。然后,通过执行 appart['x'] = appart.geometry.xappart['y'] = appart.geometry.y(对于 pharma 也是如此),创建 x 和 y 列。然后,您可以像我的答案中所述使用 ballTree,返回的距离将以米为单位。 - mgc
1
在注释中它是完美的,但是按照你的要求我刚刚将度量改为了Balltree: tree = BallTree(pharma[['lat_r', 'lng_r']].values, leaf_size=2, metric='haversine')然后我用以下代码将我的度数转换为弧度: appart['latitude_r']=pd.DataFrame(np.deg2rad(appart['latitude'].values)) appart['longitude_r']=pd.DataFrame(np.deg2rad(appart['longitude'].values)) pharma['lat_r']=pd.DataFrame(np.deg2rad(pharma['lat'].values)) pharma['lng_r']=pd.DataFrame(np.deg2rad(pharma['lng'].values))虽然还不够好,但这只是一个小问题,我认为很快就会解决 :) - Arnaud Hureaux
1
哦,评论中有字符限制,我确实不明白什么是坐标系。如果你有相关的文档,并且可以解决我的问题,欢迎分享给我 ;) - Arnaud Hureaux
太好了,我忘记了“haversine”度量!请注意,在这种情况下输出也将以弧度为单位。 - mgc
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接