Pandas数据帧中的每一行包含2个点的lat / lng坐标 . 使用下面的Python代码,计算许多(数百万)行的这两个点之间的距离需要很长时间!
考虑到2点相距不到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'])
5 回答
这是相同功能的矢量化numpy版本:
输入都是值的数组,它应该能够立即完成数百万个点 . 要求是输入是ndarray但是pandas表的列将起作用 .
例如,随机生成的值:
在python中循环遍历数据数组非常慢 . Numpy提供了对整个数据数组进行操作的函数,可以避免循环并大幅提高性能 .
这是vectorization的一个例子 .
纯粹为了一个说明性的例子,我从@ballsdotballs的答案中获取了
numpy
版本,并且还通过ctypes
调用了一个伴随的C实现 . 由于numpy
是一个高度优化的工具,因此我的C代码几乎不可能有效,但它应该有点接近 . 这里的一大优势是通过运行C类型的示例,它可以帮助您了解如何将自己的个人C函数连接到Python而不会产生太多开销 . 当你只想通过在一些C源而不是Python中编写那个小块来优化一小块更大的计算时,这是特别好的 . 简单地使用numpy
将在大多数情况下解决问题,但是对于那些你真的不需要全部numpy
并且你不想在一些代码中添加耦合以要求使用numpy
数据类型的情况,它非常方便知道如何下载到内置的ctypes
库并自己动手 .首先让我们创建一个名为
haversine.c
的C源文件:请注意,我们正在努力遵守C约定 . 通过引用显式传递数据参数,使用
size_t
作为大小变量,并期望我们的haversine
函数通过改变其中一个传递的输入来工作,这样它将在退出时包含预期的数据 . 该函数实际上返回一个整数,这是一个成功/失败标志,可供该函数的其他C级使用者使用 .我们需要找到一种方法来处理Python内部所有这些特定于C的小问题 .
接下来让我们将函数的
numpy
版本以及一些导入和一些测试数据放入名为haversine.py
的文件中:我选择制作在0到50之间随机选择的lats和lons(以度为单位),但这对于这个解释并不重要 .
接下来我们需要做的是以可以由Python动态加载的方式编译我们的C模块 . 我正在使用Linux系统(您可以在Google上轻松找到其他系统的示例),因此我的目标是将
haversine.c
编译成共享对象,如下所示:我们还可以编译成可执行文件并运行它以查看C程序的
main
函数显示的内容:现在我们已经编译了共享对象
haversine.so
,我们可以使用ctypes
在Python中加载它,我们需要提供文件的路径来执行此操作:现在
haversine_lib.haversine
几乎就像一个Python函数,除了我们可能需要做一些手动类型封送以确保正确解释输入和输出 .numpy
实际上为此提供了一些很好的工具,我将在这里使用的是numpy.ctypeslib
. 我们将构建一个指针类型,它允许我们将numpy.ndarrays
传递给这些ctypes
加载的函数,因为它们是指针 . 这是代码:请注意,我们告诉
haversine_lib.haversine
函数代理根据我们想要的类型解释其参数 .现在,要从Python中测试它剩下的只是创建一个大小变量,以及一个将被变异的数组(就像在C代码中一样)来包含结果数据,然后我们可以调用它:
将所有内容放在
haversine.py
的__main__
块中,整个文件现在看起来像这样:要运行它,它将分别运行和计算Python和
ctypes
版本并打印一些结果,我们可以这样做显示:
正如所料,
numpy
版本是稍微快一点(对于长度为100万的向量,为0.11秒),但我们快速而又脏的ctypes
版本并不懈怠:相同数据上的0.148秒可观 .让我们将它与Python中的一个天真的for循环解决方案进行比较:
当我把它放在与其他文件相同的Python文件中并将它计入相同的百万元素数据时,我一直在机器上看到大约2.65秒的时间 .
因此,通过快速切换到
ctypes
,我们将速度提高了大约18倍 . 对于许多可以从访问裸,连续数据中获益的计算,您经常会看到比这更高的收益 .只是要非常清楚,我并不认为这是一个比使用
numpy
更好的选择 . 这正是numpy
为解决而构建的问题,因此,只要它们(a)在您的应用程序中合并numpy
数据类型是有意义的,并且(b)存在一种将代码映射到一个简单方法的简单方法,那么自己编写自己的ctypes
代码 . 相当于numpy
,效率不高 .但是,如果你喜欢用C编写一些东西但是用Python调用它,或者依赖于
numpy
不实用的情况(在无法安装numpy
的嵌入式系统中,对于那些情况)知道如何执行此操作仍然非常有用 . 例) .如果允许使用scikit-learn,我会给出以下机会:
其中一些答案是地球的半径 . 如果您针对其他距离计算器(例如geopy)检查这些,则这些功能将关闭 .
如果您想要以英里为单位的答案,您可以为下面的转换常量切换
R=3959.87433
.如果你想要公里,请使用
R= 6372.8
.对@derricw's vectorised solution的简单扩展,您可以使用numba将性能提高~2倍,几乎不会对代码进行任何更改 . 对于纯数值计算,这可能应该用于基准测试/测试与可能更有效的解决方案 .
基准测试与熊猫功能:
完整的基准测试代码: