Znajdowanie indeksu najbliższego punktu w numpy tablicach współrzędnych x i y


83

Mam dwie tablice numpy 2d: x_array zawiera informacje o położeniu w kierunku x, y_array zawiera pozycje w kierunku y.

Mam wtedy długą listę punktów x, y.

Dla każdego punktu na liście muszę znaleźć indeks tablicy lokalizacji (określonej w tablicach), która jest najbliższa temu punktowi.

Naiwnie stworzyłem kod, który działa, w oparciu o następujące pytanie: Znajdź najbliższą wartość w tablicy numpy

to znaczy

import time
import numpy

def find_index_of_nearest_xy(y_array, x_array, y_point, x_point):
    distance = (y_array-y_point)**2 + (x_array-x_point)**2
    idy,idx = numpy.where(distance==distance.min())
    return idy[0],idx[0]

def do_all(y_array, x_array, points):
    store = []
    for i in xrange(points.shape[1]):
        store.append(find_index_of_nearest_xy(y_array,x_array,points[0,i],points[1,i]))
    return store


# Create some dummy data
y_array = numpy.random.random(10000).reshape(100,100)
x_array = numpy.random.random(10000).reshape(100,100)

points = numpy.random.random(10000).reshape(2,5000)

# Time how long it takes to run
start = time.time()
results = do_all(y_array, x_array, points)
end = time.time()
print 'Completed in: ',end-start

Robię to na dużym zbiorze danych i naprawdę chciałbym to trochę przyspieszyć. Czy ktoś może to zoptymalizować?

Dzięki.


AKTUALIZACJA: ROZWIĄZANIE po sugestiach @silvado i @justin (poniżej)

# Shoe-horn existing data for entry into KDTree routines
combined_x_y_arrays = numpy.dstack([y_array.ravel(),x_array.ravel()])[0]
points_list = list(points.transpose())


def do_kdtree(combined_x_y_arrays,points):
    mytree = scipy.spatial.cKDTree(combined_x_y_arrays)
    dist, indexes = mytree.query(points)
    return indexes

start = time.time()
results2 = do_kdtree(combined_x_y_arrays,points_list)
end = time.time()
print 'Completed in: ',end-start

Powyższy kod przyspieszył mój kod (wyszukiwanie 5000 punktów w macierzach 100x100) 100 razy. Co ciekawe, użycie scipy.spatial.KDTree (zamiast scipy.spatial.cKDTree ) dało porównywalne czasy do mojego naiwnego rozwiązania, więc zdecydowanie warto skorzystać z wersji cKDTree ...


1
Zgaduję, ale może pomogłoby drzewo kd. Nie wiem, czy Python ma implementację.
Justin

Nie ma potrzeby tworzenia listy i transpozycji „punktów”. Zamiast tego użyj tablicy i wyczyść indeksy.
Théo Simier

Odpowiedzi:


48

scipy.spatial ma również implementację drzewa kd: scipy.spatial.KDTree .

Podejście polega na tym, że najpierw używa się danych punktów do zbudowania drzewa kd. Złożoność obliczeniowa tego jest rzędu N log N, gdzie N to liczba punktów danych. Zapytania o zasięg i wyszukiwanie najbliższych sąsiadów można następnie przeprowadzać ze złożonością dziennika N. Jest to o wiele bardziej wydajne niż zwykła jazda na rowerze przez wszystkie punkty (złożoność N).

Tak więc, jeśli masz powtarzające się zapytania dotyczące zasięgu lub najbliższego sąsiada, wysoce zalecane jest drzewo kd.


1
To wygląda bardzo obiecująco. Zacznę o tym czytać i zobaczę, czy coś mi się uda ...
Pete W

1
Wciąż testuję swój kod, ale wczesne oznaki wskazują, że używanie scipy.spatial.cKDTree jest około 100 razy szybsze niż moje naiwne podejście. Kiedy jutro będę miał więcej czasu, opublikuję swój ostateczny kod i najprawdopodobniej zaakceptuję tę odpowiedź (chyba że wcześniej pojawi się szybsza metoda!). Dzięki za pomoc.
Pete W

OK, wydaje się, że najlepszym rozwiązaniem jest użycie scipy.spatial.cKDTree. Testy z moimi danymi testowymi wykazały, że standardowy scipy.spatial.KDTree nie daje wiele / żadnej poprawy w stosunku do mojego naiwnego rozwiązania.
Pete W

75

Oto scipy.spatial.KDTreeprzykład

In [1]: from scipy import spatial

In [2]: import numpy as np

In [3]: A = np.random.random((10,2))*100

In [4]: A
Out[4]:
array([[ 68.83402637,  38.07632221],
       [ 76.84704074,  24.9395109 ],
       [ 16.26715795,  98.52763827],
       [ 70.99411985,  67.31740151],
       [ 71.72452181,  24.13516764],
       [ 17.22707611,  20.65425362],
       [ 43.85122458,  21.50624882],
       [ 76.71987125,  44.95031274],
       [ 63.77341073,  78.87417774],
       [  8.45828909,  30.18426696]])

In [5]: pt = [6, 30]  # <-- the point to find

In [6]: A[spatial.KDTree(A).query(pt)[1]] # <-- the nearest point 
Out[6]: array([  8.45828909,  30.18426696])

#how it works!
In [7]: distance,index = spatial.KDTree(A).query(pt)

In [8]: distance # <-- The distances to the nearest neighbors
Out[8]: 2.4651855048258393

In [9]: index # <-- The locations of the neighbors
Out[9]: 9

#then 
In [10]: A[index]
Out[10]: array([  8.45828909,  30.18426696])

5
Dziękuję za pełną odpowiedź z działającym (prostym) przykładem, doceniam!
johndodo

@lostCrotchet Myślę, że tak .. Używam go również z więcej niż parą danych. np. (x, y, z, i)
efirvida

5

Jeśli możesz wmasować swoje dane w odpowiedni format, najszybszym sposobem jest użycie metod w scipy.spatial.distance:

http://docs.scipy.org/doc/scipy/reference/spatial.distance.html

W szczególności pdist i cdistzapewniają szybkie sposoby obliczania odległości parami.


Nazywam to też masowaniem, w dużym stopniu opisuje to, co robimy z danymi. : D
Lorinc Nyitrai

1
Scipy.spatil.distance to świetne narzędzie, ale pamiętaj, że jeśli masz dużo odległości do obliczenia cKdtree, jest o wiele szybsze niż cdist.
Losbaltica

1
Jeśli nie jestem źle zrozumiany, użycie cdist () lub innej metody Numpy jest pokazane w tej odpowiedzi codereview.stackexchange.com/a/134918/156228
Alex F
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.