【问题标题】:Efficient computation of minimum of Haversine distances有效计算最小 Haversine 距离
【发布时间】:2017-11-24 16:42:11
【问题描述】:

我有一个 数据框,其中有 >2.7MM 坐标,还有一个单独的 列表,包含 ~2,000 个坐标。我试图返回 每个单独的行 中的坐标与 列表中的每个坐标 相比的最小距离。以下代码适用于小规模(具有 200 行的数据帧),但当计算超过 2.7MM 行时,它似乎永远运行。

from haversine import haversine

df
Latitude   Longitude
39.989    -89.980
39.923    -89.901
39.990    -89.987
39.884    -89.943
39.030    -89.931

end_coords_list = [(41.342,-90.423),(40.349,-91.394),(38.928,-89.323)]

for row in df.itertuples():
    def min_distance(row):
        beg_coord = (row.Latitude, row.Longitude)
        return min(haversine(beg_coord, end_coord) for end_coord in end_coords_list)
    df['Min_Distance'] = df.apply(min_distance, axis=1)

我知道问题在于正在发生的大量计算(5.7MM * 2,000 = ~11.4BN),而且运行这么多循环的效率非常低。

根据我的研究,向量化的 NumPy 函数似乎是一种更好的方法,但我是 Python 和 NumPy 的新手,所以我不太确定如何在这种特殊情况下实现它。

理想输出:

df
Latitude   Longitude  Min_Distance
39.989    -89.980     3.7
39.923    -89.901     4.1
39.990    -89.987     4.2
39.884    -89.943     5.9
39.030    -89.931     3.1

提前致谢!

【问题讨论】:

  • 告诉我们这个harversine。它接受哪些输入?真正的vectorization 通常需要减少numpy 在编译代码中处理的基本数学计算。我们不能vectorize 黑盒子。
  • haversine 接受两个输入:“开始”坐标和“结束”坐标,并计算两者之间的距离(以公里为单位)。
  • 是来自here 吗?如果是这样,请在问题中链接。
  • 刚刚更新。让我知道这是否提供了您想要的清晰度。
  • 我们需要该软件包的源代码信息。再次发布以确认这是否是链接 - github.com/mapado/haversine/blob/master/haversine/__init__.py?不要假设我们已经安装了所有的包。

标签: python pandas numpy vectorization haversine


【解决方案1】:

haversine func本质上是:

# convert all latitudes/longitudes from decimal degrees to radians
lat1, lng1, lat2, lng2 = map(radians, (lat1, lng1, lat2, lng2))

# calculate haversine
lat = lat2 - lat1
lng = lng2 - lng1

d = sin(lat * 0.5) ** 2 + cos(lat1) * cos(lat2) * sin(lng * 0.5) ** 2
h = 2 * AVG_EARTH_RADIUS * asin(sqrt(d))

这是一种矢量化方法,利用强大的 NumPy broadcastingNumPy ufuncs 来替换那些数学模块函数,以便我们一次性对整个数组进行操作 -

# Get array data; convert to radians to simulate 'map(radians,...)' part    
coords_arr = np.deg2rad(coords_list)
a = np.deg2rad(df.values)

# Get the differentiations
lat = coords_arr[:,0] - a[:,0,None]
lng = coords_arr[:,1] - a[:,1,None]

# Compute the "cos(lat1) * cos(lat2) * sin(lng * 0.5) ** 2" part.
# Add into "sin(lat * 0.5) ** 2" part.
add0 = np.cos(a[:,0,None])*np.cos(coords_arr[:,0])* np.sin(lng * 0.5) ** 2
d = np.sin(lat * 0.5) ** 2 +  add0

# Get h and assign into dataframe
h = 2 * AVG_EARTH_RADIUS * np.arcsin(np.sqrt(d))
df['Min_Distance'] = h.min(1)

为了进一步提升性能,我们可以使用numexpr module 来替换先验函数。


运行时测试和验证

方法-

def loopy_app(df, coords_list):
    for row in df.itertuples():
        df['Min_Distance1'] = df.apply(min_distance, axis=1)

def vectorized_app(df, coords_list):   
    coords_arr = np.deg2rad(coords_list)
    a = np.deg2rad(df.values)

    lat = coords_arr[:,0] - a[:,0,None]
    lng = coords_arr[:,1] - a[:,1,None]

    add0 = np.cos(a[:,0,None])*np.cos(coords_arr[:,0])* np.sin(lng * 0.5) ** 2
    d = np.sin(lat * 0.5) ** 2 +  add0

    h = 2 * AVG_EARTH_RADIUS * np.arcsin(np.sqrt(d))
    df['Min_Distance2'] = h.min(1)

验证 -

In [158]: df
Out[158]: 
   Latitude  Longitude
0    39.989    -89.980
1    39.923    -89.901
2    39.990    -89.987
3    39.884    -89.943
4    39.030    -89.931

In [159]: loopy_app(df, coords_list)

In [160]: vectorized_app(df, coords_list)

In [161]: df
Out[161]: 
   Latitude  Longitude  Min_Distance1  Min_Distance2
0    39.989    -89.980     126.637607     126.637607
1    39.923    -89.901     121.266241     121.266241
2    39.990    -89.987     126.037388     126.037388
3    39.884    -89.943     118.901195     118.901195
4    39.030    -89.931      53.765506      53.765506

时间安排 -

In [163]: df
Out[163]: 
   Latitude  Longitude
0    39.989    -89.980
1    39.923    -89.901
2    39.990    -89.987
3    39.884    -89.943
4    39.030    -89.931

In [164]: %timeit loopy_app(df, coords_list)
100 loops, best of 3: 2.41 ms per loop

In [165]: %timeit vectorized_app(df, coords_list)
10000 loops, best of 3: 96.8 µs per loop

【讨论】:

  • 这真是太棒了。感谢您演示如何将 NumPy 与 Pandas 一起使用。在非常大的数据帧上运行时出现内存错误。你认为 'numexpr' 能解决这个问题吗?
  • @WaltReed 不,numexpr 无济于事。只需将数据帧分成块,例如一次抓取 10000 行,使用建议的代码进行处理并分配到输出 col,然后分配到下一个 10000 行,重复等等。
猜你喜欢
  • 2016-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-02
  • 2020-01-01
  • 2016-10-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多