【问题标题】:Vectorizing Haversine distance calculation in PythonPython中的向量化Haversine距离计算
【发布时间】:2016-04-02 19:38:23
【问题描述】:

我正在尝试使用 Haversine 公式计算由纬度和经度标识的一长串位置的距离矩阵,该公式采用两个坐标对元组来产生距离:

def haversine(point1, point2, miles=False):
    """ Calculate the great-circle distance bewteen two points on the Earth surface.

    :input: two 2-tuples, containing the latitude and longitude of each point
    in decimal degrees.

    Example: haversine((45.7597, 4.8422), (48.8567, 2.3508))

    :output: Returns the distance bewteen the two points.
    The default unit is kilometers. Miles can be returned
    if the ``miles`` parameter is set to True.

    """

我可以使用嵌套的for循环计算所有点之间的距离,如下所示:

data.head()

   id                      coordinates
0   1   (16.3457688674, 6.30354512503)
1   2    (12.494749307, 28.6263955635)
2   3    (27.794615136, 60.0324947881)
3   4   (44.4269923769, 110.114216113)
4   5  (-69.8540884125, 87.9468778773)

使用一个简单的函数:

distance = {}
def haver_loop(df):
    for i, point1 in df.iterrows():
        distance[i] = []
        for j, point2 in df.iterrows():
            distance[i].append(haversine(point1.coordinates, point2.coordinates))

    return pd.DataFrame.from_dict(distance, orient='index')

但是考虑到时间复杂度,这需要相当长的时间,大约 20 秒运行 500 分,而且我的列表要长得多。这让我看到了矢量化,我遇到了numpy.vectorize(docs),但不知道如何在这种情况下应用它。

【问题讨论】:

标签: python performance numpy pandas vectorization


【解决方案1】:

来自haversine's function definition,它看起来很漂亮可并行。因此,使用 NumPy aka broadcasting 进行矢量化的最佳工具之一,并用 NumPy 等效项 ufuncs 替换数学函数,这是一个矢量化解决方案 -

# Get data as a Nx2 shaped NumPy array
data = np.array(df['coordinates'].tolist())

# Convert to radians
data = np.deg2rad(data)                     

# Extract col-1 and 2 as latitudes and longitudes
lat = data[:,0]                     
lng = data[:,1]         

# Elementwise differentiations for lattitudes & longitudes
diff_lat = lat[:,None] - lat
diff_lng = lng[:,None] - lng

# Finally Calculate haversine
d = np.sin(diff_lat/2)**2 + np.cos(lat[:,None])*np.cos(lat) * np.sin(diff_lng/2)**2
return 2 * 6371 * np.arcsin(np.sqrt(d))

运行时测试 -

另一个np.vectorize based solution 已经显示出比原始代码性能改进的一些积极承诺,因此本节将比较发布的基于广播的方法和那个。

函数定义-

def vectotized_based(df):
    haver_vec = np.vectorize(haversine, otypes=[np.int16])
    return df.groupby('id').apply(lambda x: pd.Series(haver_vec(df.coordinates, x.coordinates)))

def broadcasting_based(df):
    data = np.array(df['coordinates'].tolist())
    data = np.deg2rad(data)                     
    lat = data[:,0]                     
    lng = data[:,1]         
    diff_lat = lat[:,None] - lat
    diff_lng = lng[:,None] - lng
    d = np.sin(diff_lat/2)**2 + np.cos(lat[:,None])*np.cos(lat) * np.sin(diff_lng/2)**2
    return 2 * 6371 * np.arcsin(np.sqrt(d))

时间安排 -

In [123]: # Input
     ...: length = 500
     ...: d1 = np.random.uniform(-90, 90, length)
     ...: d2 = np.random.uniform(-180, 180, length)
     ...: coords = tuple(zip(d1, d2))
     ...: df = pd.DataFrame({'id':np.arange(length), 'coordinates':coords})
     ...: 

In [124]: %timeit vectotized_based(df)
1 loops, best of 3: 1.12 s per loop

In [125]: %timeit broadcasting_based(df)
10 loops, best of 3: 68.7 ms per loop

【讨论】:

    【解决方案2】:

    您可以将您的函数作为参数提供给np.vectorize(),然后可以将其用作pandas.groupby.apply 的参数,如下所示:

    haver_vec = np.vectorize(haversine, otypes=[np.int16])
    distance = df.groupby('id').apply(lambda x: pd.Series(haver_vec(df.coordinates, x.coordinates)))
    

    例如,样本数据如下:

    length = 500
    df = pd.DataFrame({'id':np.arange(length), 'coordinates':tuple(zip(np.random.uniform(-90, 90, length), np.random.uniform(-180, 180, length)))})
    

    比较 500 分:

    def haver_vect(data):
        distance = data.groupby('id').apply(lambda x: pd.Series(haver_vec(data.coordinates, x.coordinates)))
        return distance
    
    %timeit haver_loop(df): 1 loops, best of 3: 35.5 s per loop
    
    %timeit haver_vect(df): 1 loops, best of 3: 593 ms per loop
    

    【讨论】:

    • vectorize 实际上只是为了方便,据我所知,它通常不会提供任何加速(至少任何有意义的加速)
    • 谢谢,我需要查看一个实现示例,对这一切仍然很陌生,无法从文档中弄清楚...
    【解决方案3】:

    首先使用itertools.product获取所有组合

     results= [(p1,p2,haversine(p1,p2))for p1,p2 in itertools.product(points,repeat=2)]
    

    那是说我不确定它会有多快这看起来可能是 Python: speeding up geographic comparison 的副本

    【讨论】:

      猜你喜欢
      • 2016-04-06
      • 1970-01-01
      • 1970-01-01
      • 2017-11-24
      • 1970-01-01
      • 1970-01-01
      • 2018-08-23
      • 2016-10-18
      • 1970-01-01
      相关资源
      最近更新 更多