查找数据集中所有点的距离中最近的点-Python

hai*_*men 2 python python-2.7 python-3.x

我有一个数据集如下

Id     Latitude      longitude
1      25.42         55.47
2      25.39         55.47
3      24.48         54.38
4      24.51         54.54
Run Code Online (Sandbox Code Playgroud)

我想找到数据集每个点的最近距离。我在互联网上发现了以下距离功能,

from math import radians, cos, sin, asin, sqrt
def distance(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
Run Code Online (Sandbox Code Playgroud)

我正在使用以下功能,

shortest_distance = []
for i in range(1,len(data)):
    distance1 = []
    for j in range(1,len(data)):
        distance1.append(distance(data['Longitude'][i], data['Latitude'][i], data['Longitude'][j], data['Latitude'][j]))
    shortest_distance.append(min(distance1))
Run Code Online (Sandbox Code Playgroud)

但是此代码为每个条目循环两次,并返回n ^ 2次迭代,因此速度非常慢。我的数据集包含近一百万条记录,每次遍历所有元素两次都变得非常昂贵。

我想找到一种更好的方法来找出每一行的最近点。有人可以帮助我找到一种在python中解决此问题的方法吗?

谢谢

Jon*_*ler 7

你可以通过调用一个为此实现智能算法的库来非常有效地做到这一点,一个例子是 sklearn,它有一种NearestNeighbors方法可以做到这一点。

为执行此操作而修改的代码示例:

from sklearn.neighbors import NearestNeighbors
import numpy as np

def distance(p1, p2):
    """
    Calculate the great circle distance between two points
    on the earth (specified in decimal degrees)
    """
    lon1, lat1 = p1
    lon2, lat2 = p2
    # 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

points = [[25.42, 55.47],
          [25.39, 55.47],
          [24.48, 54.38],
          [24.51, 54.54]]

nbrs = NearestNeighbors(n_neighbors=2, metric=distance).fit(points)

distances, indices = nbrs.kneighbors(points)

result = distances[:, 1]
Run Code Online (Sandbox Code Playgroud)

这使

>>> result
array([  1.889697  ,   1.889697  ,  17.88530556,  17.88530556])
Run Code Online (Sandbox Code Playgroud)


unu*_*tbu 7

查找与N给定点最接近的点的蛮力方法是O(N)-您必须检查每个点。相反,如果这些N点存储在KD树中,则平均找到最近的点O(log(N))。构建KD树还需要一笔额外的一次性费用,这需要O(N)时间。

如果您需要重复此处理N时间,则蛮力方法为O(N**2),而kd-tree方法为O(N*log(N))。因此,如果足够大N,KD树将击败蛮力法。

有关最近邻居算法(包括KD-tree)的更多信息,请参见此处


下面(在函数中using_kdtree)是一种使用来计算最近邻居的大圆弧长度的方法scipy.spatial.kdtree

scipy.spatial.kdtree使用点之间的欧几里得距离,但是有一个公式可以将球体上各点之间的欧几里得弦距离转换为大圆弧长度(给定球体的半径)。因此,其想法是将纬度/经度数据转换为笛卡尔坐标,使用a KDTree查找最近的邻居,然后应用大圆距离公式获得所需结果。


这是一些基准。使用N = 100using_kdtreeorig(强力)方法快39倍。

In [180]: %timeit using_kdtree(data)
100 loops, best of 3: 18.6 ms per loop

In [181]: %timeit using_sklearn(data)
1 loop, best of 3: 214 ms per loop

In [179]: %timeit orig(data)
1 loop, best of 3: 728 ms per loop
Run Code Online (Sandbox Code Playgroud)

对于N = 10000

In [5]: %timeit using_kdtree(data)
1 loop, best of 3: 2.78 s per loop

In [6]: %timeit using_sklearn(data)
1 loop, best of 3: 1min 15s per loop

In [7]: %timeit orig(data)
# untested; too slow
Run Code Online (Sandbox Code Playgroud)

由于using_kdtreeis O(N log(N))origis O(N**2),所以随着长度的增长, using_kdtreeorig增长快的因素。Ndata


import numpy as np
import scipy.spatial as spatial
import pandas as pd
import sklearn.neighbors as neighbors
from math import radians, cos, sin, asin, sqrt

R = 6367

def using_kdtree(data):
    "Based on /sf/ask/3011464361/"
    def dist_to_arclength(chord_length):
        """
        https://en.wikipedia.org/wiki/Great-circle_distance
        Convert Euclidean chord length to great circle arc length
        """
        central_angle = 2*np.arcsin(chord_length/(2.0*R)) 
        arclength = R*central_angle
        return arclength

    phi = np.deg2rad(data['Latitude'])
    theta = np.deg2rad(data['Longitude'])
    data['x'] = R * np.cos(phi) * np.cos(theta)
    data['y'] = R * np.cos(phi) * np.sin(theta)
    data['z'] = R * np.sin(phi)
    tree = spatial.KDTree(data[['x', 'y','z']])
    distance, index = tree.query(data[['x', 'y','z']], k=2)
    return dist_to_arclength(distance[:, 1])

def orig(data):
    def distance(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.0)**2 + cos(lat1) * cos(lat2) * sin(dlon/2.0)**2
        c = 2 * asin(sqrt(a)) 
        km = R * c
        return km

    shortest_distance = []
    for i in range(len(data)):
        distance1 = []
        for j in range(len(data)):
            if i == j: continue
            distance1.append(distance(data['Longitude'][i], data['Latitude'][i], 
                                      data['Longitude'][j], data['Latitude'][j]))
        shortest_distance.append(min(distance1))
    return shortest_distance


def using_sklearn(data):
    """
    Based on /sf/answers/3158907531/ (Jonas Adler)
    """
    def distance(p1, p2):
        """
        Calculate the great circle distance between two points
        on the earth (specified in decimal degrees)
        """
        lon1, lat1 = p1
        lon2, lat2 = p2
        # 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 = R * c
        return km
    points = data[['Longitude', 'Latitude']]
    nbrs = neighbors.NearestNeighbors(n_neighbors=2, metric=distance).fit(points)
    distances, indices = nbrs.kneighbors(points)
    result = distances[:, 1]
    return result

np.random.seed(2017)
N = 1000
data = pd.DataFrame({'Latitude':np.random.uniform(-90,90,size=N), 
                     'Longitude':np.random.uniform(0,360,size=N)})

expected = orig(data)
for func in [using_kdtree, using_sklearn]:
    result = func(data)
    assert np.allclose(expected, result)
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

5219 次

最近记录:

8 年,4 月 前