Python快速计算大量距离

时间:2016-02-09 16:19:55

标签: python numpy distance haversine geohashing

我输入了36,742个点,这意味着如果我想计算距离矩阵的下三角形(使用vincenty近似),我需要生成36,742 * 36,741 * 0.5 = 1,349,974,563个距离。

我想保持彼此相距50公里的对组合。我目前的设置如下

shops= [[id,lat,lon]...]

def lower_triangle_mat(points):
    for i in range(len(shops)-1):
        for j in range(i+1,len(shops)):
            yield [shops[i],shops[j]]

def return_stores_cutoff(points,cutoff_km=0):
    below_cut = []
    counter = 0
    for x in lower_triangle_mat(points):
        dist_km = vincenty(x[0][1:3],x[1][1:3]).km
        counter += 1
        if counter % 1000000 == 0:
            print("%d out of %d" % (counter,(len(shops)*len(shops)-1*0.5)))
        if dist_km <= cutoff_km:
            below_cut.append([x[0][0],x[1][0],dist_km])
    return below_cut

start = time.clock()
stores = return_stores_cutoff(points=shops,cutoff_km=50)
print(time.clock() - start)

这显然需要数小时和数小时。我想到的一些可能性:

  • 使用numpy 矢量化这些计算而不是循环
  • 使用某种哈希来快速粗略(100公里内的所有商店),然后只计算这些商店之间的准确距离
  • 不是将点存储在列表中,而是使用类似四叉树的东西,但我认为这只会对关闭点的排名而不是实际距离有所帮助 - &gt;所以我想某种地理数据库
  • 我显然可以尝试 hasrsine 或项目并使用欧几里德距离,但我有兴趣使用最准确的衡量指标
  • 使用并行处理(但是我遇到一些困难,如何削减列表仍然可以获得所有相关对)。

修改:我认为这里肯定需要进行地理分类 - 例如from

from geoindex import GeoGridIndex, GeoPoint

geo_index = GeoGridIndex()
for _ in range(10000):
    lat = random.random()*180 - 90
    lng = random.random()*360 - 180
    index.add_point(GeoPoint(lat, lng))

center_point = GeoPoint(37.7772448, -122.3955118)
for distance, point in index.get_nearest_points(center_point, 10, 'km'):
    print("We found {0} in {1} km".format(point, distance))

但是,我还想对地理哈希返回的商店进行矢量化(而不是循环)。

编辑2:Pouria Hadjibagheri - 我尝试使用lambda和地图:

# [B]: Mapping approach           
lwr_tr_mat = ((shops[i],shops[j]) for i in range(len(shops)-1) for j in range(i+1,len(shops)))

func = lambda x: (x[0][0],x[1][0],vincenty(x[0],x[1]).km)
# Trying to see if conditional statements slow this down
func_cond = lambda x: (x[0][0],x[1][0],vincenty(x[0],x[1]).km) if vincenty(x[0],x[1]).km <= 50 else None

start = time.clock()
out_dist = list(map(func,lwr_tr_mat))
print(time.clock() - start)

start = time.clock()
out_dist = list(map(func_cond,lwr_tr_mat))
print(time.clock() - start)

他们都在 61秒(我限制商店的数量从32,000到2000)。也许我错误地使用了地图?

4 个答案:

答案 0 :(得分:5)

这听起来像是k-D trees的经典用例。

如果您首先将您的点转换为欧几里德空间,那么您可以使用scipy.spatial.cKDTreequery_pairs方法:

from scipy.spatial import cKDTree

tree = cKDTree(data)
# where data is (nshops, ndim) containing the Euclidean coordinates of each shop
# in units of km

pairs = tree.query_pairs(50, p=2)   # 50km radius, L2 (Euclidean) norm

pairs将是set(i, j)个元组,对应于彼此相距≤50公里的商店对的行索引。

tree.sparse_distance_matrix的输出为scipy.sparse.dok_matrix。由于矩阵是对称的,并且您只对唯一的行/列对感兴趣,因此您可以使用scipy.sparse.tril将上三角形清零,为您提供scipy.sparse.coo_matrix。从那里,您可以通过.row.col.data属性访问非零行和列索引及其相应的距离值:

from scipy import sparse

tree_dist = tree.sparse_distance_matrix(tree, max_distance=10000, p=2)
udist = sparse.tril(tree_dist, k=-1)    # zero the main diagonal
ridx = udist.row    # row indices
cidx = udist.col    # column indices
dist = udist.data   # distance values

答案 1 :(得分:0)

您是否尝试过映射整个数组和函数而不是迭代它们?一个例子如下:

from numpy.random import rand

my_array = rand(int(5e7), 1)  # An array of 50,000,000 random numbers in double.

现在通常做的是:

squared_list_iter = [value**2 for value in my_array]

当然有效,但最佳无效。

另一种方法是使用函数映射数组。这样做如下:

func = lambda x: x**2  # Here is what I want to do on my array.

squared_list_map = map(func, test)  # Here I am doing it!

现在,有人可能会问,这有什么不同,甚至更好?从现在开始我们也添加了对函数的调用!这是你的答案:

对于前一种解决方案(通过迭代):

1 loop: 1.11 minutes.

与后一种解决方案(映射)相比:

500 loop, on average 560 ns. 

map()同时将list(map(my_list))转换为列表会使时间增加10倍,大约为500 ms

你选择!

答案 2 :(得分:0)

“使用某种哈希来快速粗略切断(100公里内的所有商店),然后只计算这些商店之间的准确距离” 我认为这可能更好地称为网格化。因此,首先制作一个字典,用一组坐标作为钥匙,并将每个商店放在靠近该点的50公里的桶中。那么当你计算距离时,你只能看到附近的水桶,而不是遍历整个宇宙中的每个商店

答案 3 :(得分:0)

谢谢大家的帮助。我想我已经通过纳入所有建议来解决这个问题。

我使用numpy导入地理坐标,然后使用“France Lambert - 93”投影它们。这让我用scipy.spatial.cKDTree填充这些点,然后通过指定50km的截止值来计算sparse_distance_matrix(我的预测点以米为单位)。然后,我将提取下三角形提取为CSV。

import numpy as np
import csv
import time
from pyproj import Proj, transform

#http://epsg.io/2154 (accuracy: 1.0m)
fr = '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 \
+x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 \
+units=m +no_defs'

#http://epsg.io/27700-5339 (accuracy: 1.0m)
uk = '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 \
+x_0=400000 +y_0=-100000 +ellps=airy \
+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs'

path_to_csv = '.../raw_in.csv'
out_csv = '.../out.csv'

def proj_arr(points):
    inproj = Proj(init='epsg:4326')
    outproj = Proj(uk)
    # origin|destination|lon|lat
    func = lambda x: transform(inproj,outproj,x[2],x[1])
    return np.array(list(map(func, points)))

tstart = time.time()

# Import points as geographic coordinates
# ID|lat|lon
#Sample to try and replicate
#points = np.array([
#        [39007,46.585012,5.5857829],
#        [88086,48.192370,6.7296289],
#        [62627,50.309155,3.0218611],
#        [14020,49.133972,-0.15851507],
#        [1091, 42.981765,2.0104902]])
#
points = np.genfromtxt(path_to_csv,
                       delimiter=',',
                       skip_header=1)

print("Total points: %d" % len(points))
print("Triangular matrix contains: %d" % (len(points)*((len(points))-1)*0.5))
# Get projected co-ordinates
proj_pnts = proj_arr(points)

# Fill quad-tree
from scipy.spatial import cKDTree
tree = cKDTree(proj_pnts)
cut_off_metres = 1600
tree_dist = tree.sparse_distance_matrix(tree,
                                        max_distance=cut_off_metres,
                                        p=2) 

# Extract triangle
from scipy import sparse
udist = sparse.tril(tree_dist, k=-1)    # zero the main diagonal
print("Distances after quad-tree cut-off: %d " % len(udist.data))

# Export CSV
import csv
f = open(out_csv, 'w', newline='') 
w = csv.writer(f, delimiter=",", )
w.writerow(['id_a','lat_a','lon_a','id_b','lat_b','lon_b','metres'])
w.writerows(np.column_stack((points[udist.row ],
                             points[udist.col],
                             udist.data)))
f.close()

"""
Get ID labels
"""
id_to_csv = '...id.csv'
id_labels = np.genfromtxt(id_to_csv,
                       delimiter=',',
                       skip_header=1,
                       dtype='U')

"""
Try vincenty on the un-projected co-ordinates
"""
from geopy.distance import vincenty
vout_csv = '.../out_vin.csv'
test_vin = np.column_stack((points[udist.row].T[1:3].T,
                            points[udist.col].T[1:3].T))

func = lambda x: vincenty(x[0:2],x[2:4]).m
output = list(map(func,test_vin))

# Export CSV
f = open(vout_csv, 'w', newline='')
w = csv.writer(f, delimiter=",", )
w.writerow(['id_a','id_a2', 'lat_a','lon_a',
            'id_b','id_b2', 'lat_b','lon_b',
            'proj_metres','vincenty_metres'])
w.writerows(np.column_stack((list(id_labels[udist.row]),
                             points[udist.row ],
                             list(id_labels[udist.col]),
                             points[udist.col],
                             udist.data,
                             output,
                             )))

f.close()    
print("Finished in %.0f seconds" % (time.time()-tstart)

此方法需要164秒才能生成(距离为5,306,434) - 相比之下为9 - 并且还需要大约90秒才能保存到磁盘。

然后,我比较了vincenty距离和斜边距离(在投影坐标上)的差异。

米的平均差异为2.7,平均差异/米为0.0073% - 看起来很棒。