[英]How to make the loop more efficient in my case? (numpy)
我正在运行 python 代码来计算某些坐标之间的距离。 原始数据如下:
a = np.array([[1,40,70],[2,41,71],[3,42,73]]) #id, latitude, longitude
我期待得到每对之间的距离,结果应该如下所示:
[1, 2, 100(km)]
[1, 3, 200(km)].
[2, 1, 100(km)]
[2, 3, 300(km)]
[3, 1, 200(km)]
[3, 2, 300(km)]
结果应包含 pair(m,n) 和 pair(n,m)
实际数据有39000列,所以我对代码效率有很大的要求。 目前我正在使用一个非常愚蠢的双循环:
line = 0
result = np.zeros((6,3))
for i in a:
for j in a:
dis = getDistance(i[1],i[2],j[1],j[2]) # this is the function i made to calculate distance between two coordinates
result[line] = [i[0],j[0],dis]
line += 1
谁能帮我改进代码?
如果你的距离 function 很简单,可以尝试找到distance_matrix
并将其转换为类似于np.enumerate(distance_matrix)
的 output 的数据结构:
def get_dist(X, Y):
return 100*np.hypot(X-X[:,None], Y-Y[:,None])
M = np.array([[1,40,70],[2,41,71],[3,42,73]])
names, X, Y = np.transpose(M)
distance_matrix = get_dist(X, Y)
>>> list(np.enumerate(distance_matrix))
[((0, 0), 0.0),
((0, 1), 141.4213562373095),
((0, 2), 360.5551275463989),
((1, 0), 141.4213562373095),
((1, 1), 0.0),
((1, 2), 223.60679774997897),
((2, 0), 360.5551275463989),
((2, 1), 223.60679774997897),
((2, 2), 0.0)]
请注意,我们需要为索引添加不同的名称。 此外,这些名称和距离的类型不同,因此我们不能将它们都保存在一个(非结构化)数组中。 您可能希望避免迭代np.ndeumerate
并以不同的方式查找名称:
x,y = np.indices([len(M), len(M)])
>>> names[x].ravel(), names[y].ravel(), distance_matrix.ravel()
(array([1, 1, 1, 2, 2, 2, 3, 3, 3]),
array([1, 2, 3, 1, 2, 3, 1, 2, 3]),
array([ 0. , 141.42135624, 360.55512755, 141.42135624,
0. , 223.60679775, 360.55512755, 223.60679775,
0. ]))
或者:
>>> np.transpose([names[x].ravel(), names[y].ravel(), dist_matrix.ravel()])
array([[ 1. , 1. , 0. ],
[ 1. , 2. , 141.42135624],
[ 1. , 3. , 360.55512755],
[ 2. , 1. , 141.42135624],
[ 2. , 2. , 0. ],
[ 2. , 3. , 223.60679775],
[ 3. , 1. , 360.55512755],
[ 3. , 2. , 223.60679775],
[ 3. , 3. , 0. ]])
首先,您要进行两次计算。 你的代码:
for i in a:
for j in a:
计算从i
到j
和从j
到i
的距离,即使它们是相同的距离。 您只需执行以下操作即可将时间减半
for i in range(len(a)):
for j in range(i+1, len(a)):
D[i,j] = distance_calc(i,j)
即使您需要两个方向的距离,我也不会计算两次,只需在两个位置分配值即可。 但是如果你可以矢量化你的代码,它可能会加速
for i in range(len(a)):
D[i,i+1:] = distance_calc(i)
为您的问题举一个例子,任何计算都将以弧度进行,所以我想首先将您的位置转换为弧度而不是度数。 即便如此,我们也可以加快速度。 我在这里假设您正在使用 Haversine estiamte 来计算球形地球上的距离。 我的算法基于您对问题的评论中引用的这篇文章中的信息。 首先,我将考虑N=1000
点而不是你的全套。
import numpy as np
import time
r_Earth = 6371
N = 1000
def haversine_one_element(data1, data2):
# calculates the Haversine distance one element at a time
lat1 = data1[0]
lng1 = data1[1]
lat2 = data2[0]
lng2 = data2[1]
diff_lat = lat1 - lat2
diff_lng = lng1 - lng2
d = np.sin(diff_lat/2)**2 + np.cos(lat1)*np.cos(lat2) * np.sin(diff_lng/2)**2
return 2 * r_Earth * np.arcsin(np.sqrt(d))
def haversine_slow(a, N, do_all = True):
D = np.zeros((N,N))
for i in range(N):
if do_all:
for j in range(N):
D[i,j] = haversine_one_element(a[i,1:], a[j,1:])
else:
for j in range(i+1,N): # note that D[i,i] = 0 so we can skip it
D[i,j] = D[j,i] = haversine_one_element(a[i,1:], a[j,1:])
a = np.array([np.arange(N).ravel(),
np.random.random(N) * 360 - 180,
np.arcsin(np.random.random(N) * 2 - 1) * 180 / np.pi]).transpose()
a[:,1:] = a[:,1:] * np.pi / 180
# Doing the entire array
start = time.time()
haversine_slow(a,N)
print(time.time() - start) # 8.777195453643799
# Doing only the upper half
start = time.time()
haversine_slow(a,N,do_all = False)
print(time.time() - start) # 4.547634840011597
但是,我们可以重写我们的 Haversine 公式以一次获取大量值,以便我们可以一次计算从一个点到所有其他点的距离。
def haversine(data1, data2):
# data1, data2 are the data arrays with 2 cols and they hold
# lat., lng. values in those cols respectively
lat1 = data1[0]
lng1 = data1[1]
lat2 = data2[0,:]
lng2 = data2[1,:]
diff_lat = lat1 - lat2
diff_lng = lng1 - lng2
d = np.sin(diff_lat/2)**2 + np.cos(lat1)*np.cos(lat2) * np.sin(diff_lng/2)**2
return 2 * r_Earth * np.arcsin(np.sqrt(d))
def haversine_d(a,N):
D = np.zeros((N,N))
for i in range(N):
d1 = a[i,1:]
d2 = a[i+1:,1:].transpose()
D[i,i+1:] = haversine(d1, d2)
D[i+1:,i] = D[i, i+1:].transpose()
# only return flattened upper triangular part of matirx as
return(D)
start = time.time()
d1 = haversine_d(a,N)
print(time.time() - start) # 0.03420424461364746
因此,对于我这里的情况,计算时间又减少了 100 倍,我认为对于像你所说的那样更长的向量,它会减少更多。 (受内存限制问题的影响,见下文。)
然而,我认为我们还可以做一件事,如果我们用线性代数而不是三角函数来看待这个问题。 如果你学过线性代数,你应该知道点积和余弦之间的关系,我们可以通过将位置描述为(x,y,z)
中的向量而不是经度和纬度来利用这些信息来发挥我们的优势:
def get_xyz(a):
x = np.cos(a[:,1]) * np.cos(a[:,2])
y = np.cos(a[:,1]) * np.sin(a[:,2])
z = np.sin(a[:,1])
v = np.stack([x, y, z]).transpose()
return(np.hstack([a[:,:1], v]))
def distance_dot(a,N):
b = get_xyz(a)
D = np.zeros((N,N))
for i in range(N):
D[i,i+1:] = np.arccos(b[i,1] * b[i+1:,1] + \
b[i,2] * b[i+1:,2] + \
b[i,3] * b[i+1:,3]) * r_Earth
D[i+1:,i] = D[i, i+1:].transpose()
return(D)
start = time.time()
distance_dot(a,N) # 0.019799470901489258
print(time.time() - start)
这种代数方法应该在浮点计算的截断限制内给出相同的答案; 如果您尝试使用==
比较 Haversine 和线性代数的测试,它将失败,但如果您执行<= 10**-10
之类的操作,它将通过。
这些方法在我的 1000 点数据集上将计算时间减少了大约 400 倍。 对于更大的数据集,我没有在N=40000
的情况下运行前两个,但对于后两个,Haversine 和线性代数测试几乎同时出现(290 秒),但这可能是因为问题最终需要所有我笔记本电脑上的 memory。 如果 memory 不是问题,我预计问题会大致按N**2
缩放,因此 Haversine 大约为 60 s,线性代数为 30 s。
如果您开始遇到 memory 问题,可能有一种方法可以通过计算数组的块并像 go 一样保存到磁盘来加速它,但暂时需要解决这个问题,除非您需要生成生产级代码或一遍又一遍地这样做我可能不会打扰。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.