Pandas数据帧中的每一行都包含2点的经纬度/lng坐标。使用下面的Python代码,计算许多(数百万)行的这两个点之间的距离需要很长时间!
考虑到这两个点相距不到50英里,精度并不是很重要,有没有可能让计算更快?
from math import radians, cos, sin, asin, sqrt
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
km = 6367 * c
return km
for index, row in df.iterrows():
df.loc[index, 'distance'] = haversine(row['a_longitude'], row['a_latitude'], row['b_longitude'], row['b_latitude'])发布于 2015-04-10 03:03:50
下面是同一函数的矢量化numpy版本:
import numpy as np
def haversine_np(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
All args must be of equal length.
"""
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
dlon = lon2 - lon1
dlat = lat2 - lat1
a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2
c = 2 * np.arcsin(np.sqrt(a))
km = 6367 * c
return km输入都是值的数组,它应该能够立即完成数百万个点。要求输入是ndarray,但pandas表中的列可以正常工作。
例如,使用随机生成的值:
>>> import numpy as np
>>> import pandas
>>> lon1, lon2, lat1, lat2 = np.random.randn(4, 1000000)
>>> df = pandas.DataFrame(data={'lon1':lon1,'lon2':lon2,'lat1':lat1,'lat2':lat2})
>>> km = haversine_np(df['lon1'],df['lat1'],df['lon2'],df['lat2'])或者,如果您想要创建另一个列:
>>> df['distance'] = haversine_np(df['lon1'],df['lat1'],df['lon2'],df['lat2'])在python中,遍历数据数组的速度非常慢。Numpy提供了对整个数据数组进行操作的函数,这使您可以避免循环并极大地提高性能。
这是vectorization的一个例子。
发布于 2015-04-10 04:28:25
纯粹为了举例说明,我从@ballsdotballs获取了答案中的numpy版本,并通过ctypes调用了一个配套的C实现。由于numpy是一个高度优化的工具,所以我的C代码几乎不可能如此高效,但它应该在某种程度上接近它。这里最大的优点是,通过运行一个使用C类型的示例,它可以帮助您了解如何在没有太多开销的情况下将您自己的个人C函数连接到Python。当您只想通过在一些C源代码而不是Python中编写一小部分来优化较大计算的一小部分时,这一点特别好。在大多数情况下,只需使用numpy就可以解决问题,但是对于不需要所有numpy并且不想在某些代码中添加耦合以要求在某些代码中使用numpy数据类型的情况下,了解如何使用内置的ctypes库并自己动手做是非常方便的。
首先,让我们创建名为haversine.c的C源文件
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int haversine(size_t n,
double *lon1,
double *lat1,
double *lon2,
double *lat2,
double *kms){
if ( lon1 == NULL
|| lon2 == NULL
|| lat1 == NULL
|| lat2 == NULL
|| kms == NULL){
return -1;
}
double km, dlon, dlat;
double iter_lon1, iter_lon2, iter_lat1, iter_lat2;
double km_conversion = 2.0 * 6367.0;
double degrees2radians = 3.14159/180.0;
int i;
for(i=0; i < n; i++){
iter_lon1 = lon1[i] * degrees2radians;
iter_lat1 = lat1[i] * degrees2radians;
iter_lon2 = lon2[i] * degrees2radians;
iter_lat2 = lat2[i] * degrees2radians;
dlon = iter_lon2 - iter_lon1;
dlat = iter_lat2 - iter_lat1;
km = pow(sin(dlat/2.0), 2.0)
+ cos(iter_lat1) * cos(iter_lat2) * pow(sin(dlon/2.0), 2.0);
kms[i] = km_conversion * asin(sqrt(km));
}
return 0;
}
// main function for testing
int main(void) {
double lat1[2] = {16.8, 27.4};
double lon1[2] = {8.44, 1.23};
double lat2[2] = {33.5, 20.07};
double lon2[2] = {14.88, 3.05};
double kms[2] = {0.0, 0.0};
size_t arr_size = 2;
int res;
res = haversine(arr_size, lon1, lat1, lon2, lat2, kms);
printf("%d\n", res);
int i;
for (i=0; i < arr_size; i++){
printf("%3.3f, ", kms[i]);
}
printf("\n");
}请注意,我们正在尝试遵循C语言的约定。通过引用显式地传递数据参数,使用size_t作为大小变量,并期望我们的haversine函数通过改变传递的输入之一来工作,以便在退出时包含预期的数据。该函数实际上返回一个整数,这是一个成功/失败标志,可供该函数的其他C级使用者使用。
我们需要找到一种方法来处理Python中所有这些C语言特有的小问题。
接下来,让我们将函数的numpy版本以及一些导入和一些测试数据放入一个名为haversine.py的文件中
import time
import ctypes
import numpy as np
from math import radians, cos, sin, asin, sqrt
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (np.sin(dlat/2)**2
+ np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2)
c = 2 * np.arcsin(np.sqrt(a))
km = 6367 * c
return km
if __name__ == "__main__":
lat1 = 50.0 * np.random.rand(1000000)
lon1 = 50.0 * np.random.rand(1000000)
lat2 = 50.0 * np.random.rand(1000000)
lon2 = 50.0 * np.random.rand(1000000)
t0 = time.time()
r1 = haversine(lon1, lat1, lon2, lat2)
t1 = time.time()
print t1-t0, r1我选择了在0到50之间随机选择的纬度和纬度(以度为单位),但对于这种解释来说,这并不重要。
我们需要做的下一件事是编译我们的C模块,使其可以由Python动态加载。我使用的是Linux系统(你可以很容易地在Google上找到其他系统的例子),所以我的目标是将haversine.c编译成一个共享对象,如下所示:
gcc -shared -o haversine.so -fPIC haversine.c -lm我们还可以编译成可执行文件并运行它,以查看C程序的main函数显示了什么:
> gcc haversine.c -o haversine -lm
> ./haversine
0
1964.322, 835.278, 现在我们已经编译了共享对象haversine.so,我们可以使用ctypes在Python中加载它,我们需要提供文件的路径来完成此操作:
lib_path = "/path/to/haversine.so" # Obviously use your real path here.
haversine_lib = ctypes.CDLL(lib_path)现在haversine_lib.haversine的行为非常像Python函数,除了我们可能需要进行一些手动类型封送处理以确保输入和输出被正确解释。
numpy实际上为此提供了一些很好的工具,我将在这里使用numpy.ctypeslib。我们将构建一个指针类型,它允许我们将numpy.ndarrays传递给这些ctypes-loaded函数,就像它们是指针一样。代码如下:
arr_1d_double = np.ctypeslib.ndpointer(dtype=np.double,
ndim=1,
flags='CONTIGUOUS')
haversine_lib.haversine.restype = ctypes.c_int
haversine_lib.haversine.argtypes = [ctypes.c_size_t,
arr_1d_double,
arr_1d_double,
arr_1d_double,
arr_1d_double,
arr_1d_double] 注意,我们告诉haversine_lib.haversine函数代理根据我们想要的类型来解释它的参数。
现在,要从Python中测试它,剩下的就是创建一个size变量,以及一个将被改变(就像在C代码中一样)以包含结果数据的数组,然后我们可以调用它:
size = len(lat1)
output = np.empty(size, dtype=np.double)
print "====="
print output
t2 = time.time()
res = haversine_lib.haversine(size, lon1, lat1, lon2, lat2, output)
t3 = time.time()
print t3 - t2, res
print type(output), output将所有这些放在haversine.py的__main__块中,整个文件现在看起来如下所示:
import time
import ctypes
import numpy as np
from math import radians, cos, sin, asin, sqrt
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (np.sin(dlat/2)**2
+ np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2)
c = 2 * np.arcsin(np.sqrt(a))
km = 6367 * c
return km
if __name__ == "__main__":
lat1 = 50.0 * np.random.rand(1000000)
lon1 = 50.0 * np.random.rand(1000000)
lat2 = 50.0 * np.random.rand(1000000)
lon2 = 50.0 * np.random.rand(1000000)
t0 = time.time()
r1 = haversine(lon1, lat1, lon2, lat2)
t1 = time.time()
print t1-t0, r1
lib_path = "/home/ely/programming/python/numpy_ctypes/haversine.so"
haversine_lib = ctypes.CDLL(lib_path)
arr_1d_double = np.ctypeslib.ndpointer(dtype=np.double,
ndim=1,
flags='CONTIGUOUS')
haversine_lib.haversine.restype = ctypes.c_int
haversine_lib.haversine.argtypes = [ctypes.c_size_t,
arr_1d_double,
arr_1d_double,
arr_1d_double,
arr_1d_double,
arr_1d_double]
size = len(lat1)
output = np.empty(size, dtype=np.double)
print "====="
print output
t2 = time.time()
res = haversine_lib.haversine(size, lon1, lat1, lon2, lat2, output)
t3 = time.time()
print t3 - t2, res
print type(output), output要运行它,它将分别运行Python和ctypes版本,并打印一些结果,我们可以这样做
python haversine.py这将显示:
0.111340045929 [ 231.53695005 3042.84915093 169.5158946 ..., 1359.2656769
2686.87895954 3728.54788207]
=====
[ 6.92017600e-310 2.97780954e-316 2.97780954e-316 ...,
3.20676686e-001 1.31978329e-001 5.15819721e-001]
0.148446083069 0
<type 'numpy.ndarray'> [ 231.53675618 3042.84723579 169.51575588 ..., 1359.26453029
2686.87709456 3728.54493339]正如预期的那样,numpy版本稍微快一些(长度为100,000的向量需要0.11秒),但我们又快又脏的ctypes版本并不逊色:在相同的数据上只有0.148秒。
让我们将其与Python中的一个简单的for循环解决方案进行比较:
from math import radians, cos, sin, asin, sqrt
def slow_haversine(lon1, lat1, lon2, lat2):
n = len(lon1)
kms = np.empty(n, dtype=np.double)
for i in range(n):
lon1_v, lat1_v, lon2_v, lat2_v = map(
radians,
[lon1[i], lat1[i], lon2[i], lat2[i]]
)
dlon = lon2_v - lon1_v
dlat = lat2_v - lat1_v
a = (sin(dlat/2)**2
+ cos(lat1_v) * cos(lat2_v) * sin(dlon/2)**2)
c = 2 * asin(sqrt(a))
kms[i] = 6367 * c
return kms当我将它放入与其他文件相同的Python文件中,并对相同的百万元素数据进行计时时,我在机器上始终看到大约2.65秒的时间。
因此,通过快速切换到ctypes,我们将速度提高了大约18倍。对于许多可以从访问裸的、连续的数据中受益的计算,您经常会看到比这更高的收益。
非常清楚,我并不认为这是一个比仅仅使用numpy更好的选择。这正是numpy要解决的问题,因此,无论何时(a)将numpy数据类型合并到应用程序中是有意义的,(b)有一种简单的方法可以将代码映射到numpy等效项,那么自制您自己的ctypes代码并不是非常有效。
但是,当您更喜欢用C编写代码而在Python中调用它时,或者在依赖numpy并不实用的情况下(例如,在无法安装numpy的嵌入式系统中),知道如何做到这一点仍然是非常有帮助的。
发布于 2017-01-26 01:53:43
如果允许使用scikit-learn,我会给以下几个机会:
from sklearn.neighbors import DistanceMetric
dist = DistanceMetric.get_metric('haversine')
# example data
lat1, lon1 = 36.4256345, -5.1510261
lat2, lon2 = 40.4165, -3.7026
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
X = [[lat1, lon1],
[lat2, lon2]]
kms = 6367
print(kms * dist.pairwise(X))https://stackoverflow.com/questions/29545704
复制相似问题