Python:为大量位置生成距离矩阵

Posted

技术标签:

【中文标题】Python:为大量位置生成距离矩阵【英文标题】:Python: generate distance matrix for large number of locations 【发布时间】:2019-10-09 15:10:21 【问题描述】:

我想根据 500 个位置的纬度和经度,使用 Haversine 公式生成一个距离矩阵 500X500。

这是 10 个位置的示例数据“coordinate.csv”:

Name,Latitude,Longitude
depot1,35.492807,139.6681689
depot2,33.6625572,130.4096027
depot3,35.6159881,139.7805445
customer1,35.622632,139.732631
customer2,35.857287,139.821461
customer3,35.955313,139.615387
customer4,35.16073,136.926239
customer5,36.118163,139.509548
customer6,35.937351,139.909783
customer7,35.949508,139.676462

得到距离矩阵后,我想根据距离矩阵找到离每个客户最近的仓库,然后将输出(从每个客户到壁橱仓库的距离和最近仓库的名称)保存到 Pandas DataFrame。

预期输出:

// Distance matrix
[ [..],[..],[..],[..],[..],[..],[..],[..],[..],[..] ]

// Closet depot to each customer (just an example)
Name,Latitude,Longitude,Distance_to_closest_depot,Closest_depot
depot1,35.492807,139.6681689,,
depot2,33.6625572,130.4096027,,
depot3,35.6159881,139.7805445,,
customer1,35.622632,139.732631,10,depot1
customer2,35.857287,139.821461,20,depot3
customer3,35.955313,139.615387,15,depot2
customer4,35.16073,136.926239,12,depot3
customer5,36.118163,139.509548,25,depot1
customer6,35.937351,139.909783,22,depot2
customer7,35.949508,139.676462,15,depot1

【问题讨论】:

所以你已经解释了你想要做什么。请注意,Stack Overflow 不是一个关于人们计划做什么的博客。但是,如果您有任何问题,请咨询How to ask a good question,因为目前没有问题。不要忘记发布您拥有的代码,指定您卡在哪里。 【参考方案1】:

有几个库函数可以帮助您:

来自scipycdist 可用于使用您喜欢的任何距离度量生成距离矩阵。 还有一个haversine 函数可以传递给cdist

之后,只需从距离矩阵中找到逐行最小值并将它们添加到您的 DataFrame 中。完整代码如下:

import pandas as pd
from scipy.spatial.distance import cdist
from haversine import haversine


df = pd.read_clipboard(sep=',')
df.set_index('Name', inplace=True)
customers = df[df.index.str.startswith('customer')]
depots = df[df.index.str.startswith('depot')]

dm = cdist(customers, depots, metric=haversine)
closest = dm.argmin(axis=1)
distances = dm.min(axis=1)

customers['Closest Depot'] = depots.index[closest]
customers['Distance'] = distances

结果:

            Latitude   Longitude Closest Depot    Distance
Name                                                      
customer1  35.622632  139.732631        depot3    4.393506
customer2  35.857287  139.821461        depot3   27.084212
customer3  35.955313  139.615387        depot3   40.565820
customer4  35.160730  136.926239        depot1  251.466152
customer5  36.118163  139.509548        depot3   60.945377
customer6  35.937351  139.909783        depot3   37.587862
customer7  35.949508  139.676462        depot3   38.255776

根据评论,我创建了一个替代解决方案,它使用平方距离矩阵。我认为原始解决方案更好,因为问题表明我们只想为每个客户找到最近的站点,因此无需计算客户之间和站点之间的距离。但是,如果您出于其他目的需要平方距离矩阵,请按照以下方式创建它:

import pandas as pd
import numpy as np
from scipy.spatial.distance import squareform, pdist
from haversine import haversine


df = pd.read_clipboard(sep=',')
df.set_index('Name', inplace=True)

dm = pd.DataFrame(squareform(pdist(df, metric=haversine)), index=df.index, columns=df.index)
np.fill_diagonal(dm.values, np.inf)  # Makes it easier to find minimums

customers = df[df.index.str.startswith('customer')]
depots = df[df.index.str.startswith('depot')]
customers['Closest Depot'] = dm.loc[depots.index, customers.index].idxmin()
customers['Distance'] = dm.loc[depots.index, customers.index].min()

最终的结果和之前一样,只是你现在有了一个平方距离矩阵。如果您愿意,可以在提取最小值后将 0 放回对角线上:

np.fill_diagonal(dm.values, 0)

【讨论】:

感谢您的回答。我可以使用 pd.read_csv("coordinate.csv") 代替 pd.read_clipboard 吗? 是的,pd.read_clipboard 只是我将您的数据读入 DataFrame 的方式,但 pd.read_csv("coordinate.csv") 应该适合您。 我希望 dm 是一个平方距离矩阵格式dm = [ [ ],[ ],...] 与 0 值对角线,因为从 A 到 A 的距离为 0。我该如何做到这一点并用你的其他代码来实现它同样的输出? @belle - 我已经编辑了我的答案以提供第二种方法。 对不起,因为我的距离矩阵是来自谷歌地图距离矩阵api的数组(600x600),所以我想知道如何通过从该数组中读取数据来获取输出。你能再帮帮忙吗?非常感谢您的帮助。【参考方案2】:

如果您需要一个非常大的矩阵并且可以使用带有 CUDA 的 NVIDIA GPU,您可以使用这个 numba 函数:

from numba import cuda
import math

@cuda.jit
def haversine_gpu_distance_matrix(p, G):
  i,j = cuda.grid(2)
  if i < p.shape[0] == G.shape[0] and j < p.shape[0] == G.shape[1]:
    if i == j:
      G[i][j] = 0
    else:
      longit_a = math.radians(p[i][0])
      latit_a = math.radians(p[i][1])
      longit_b = math.radians(p[j][0])
      latit_b =  math.radians(p[j][1])
      dist_longit_add = longit_b - longit_a
      dist_latit_sub = latit_b - latit_a
      dist_latit_add = latit_b + latit_a
      pre_comp = math.sin(dist_latit_sub/2)**2
      area = pre_comp + ((1 - pre_comp - math.sin(dist_latit_add/2)**2) * math.sin(dist_longit_add/2)**2)
      central_angle = 2 * math.asin(math.sqrt(area))
      radius = 3958
      G[i][j] = math.fabs(central_angle * radius)

您可以使用以下命令调用此函数:

# 10k [lon, lat] elements, replace this with your [lon, lat] array
# if your data is in a Pandas DataFrame, please convert it to a numpy array
geo_array = np.ones((10000, 2)) 
# allocate an empty distance matrix to fill when the function is called
dm_global_mem = cuda.device_array((geo_array.shape[0], geo_array.shape[0]))
# move the data in geo_array onto the GPU
geo_array_global_mem = cuda.to_device(geo_array)

# specify kernel dimensions, this can/should be further optimized for your hardware
threadsperblock = (16, 16)
blockspergrid_x = math.ceil(geo_array.shape[0] / threadsperblock[0])
blockspergrid_y = math.ceil(geo_array.shape[1] / threadsperblock[1])
blockspergrid = (blockspergrid_x, blockspergrid_y)

# run the function, which will transform dm_global_mem inplace
haversine_gpu_distance_matrix[blockspergrid, threadsperblock](geo_array_global_mem, dm_global_mem)

请注意,这可以针对您的硬件进一步优化。在 10k 个地理坐标对(即 100M 距离测量)上的 g4dn.xlarge 实例上的运行时间在编译后不到 0.01 秒。半径值设置为距离矩阵以英里为单位,您可以将其更改为 6371 以表示米。

【讨论】:

以上是关于Python:为大量位置生成距离矩阵的主要内容,如果未能解决你的问题,请参考以下文章

Python地理位置信息库geopy的使用:根据中心点坐标,方向,距离计算坐标

HihoCoder - 1478 水陆距离

hihocoder-Weekly236-水路距离

CH2501 矩阵距离 解题报告

如何将距离(以公里为单位)添加到以度和分钟为单位的位置坐标中,以获得 Java 中的新位置坐标?

数据可视化应用IDW插值计算实战案例(附Python和R语言代码)