Найти ближайший индекс в одном кадре данных к другому

Я новичок в python и его библиотеках. Перерыл все форумы, но подходящего решения не нашел. Это первый раз, когда я публикую вопрос здесь. Извините, если я сделал что-то не так.

Итак, у меня есть два кадра данных, как показано ниже, содержащие координаты X Y Z (UTM) и другие функции.

In [2]: a = {
   ...:     'X': [1, 2, 5, 7, 10, 5, 2, 3, 24, 21],
   ...:     'Y': [3, 4, 8, 15, 20, 12, 23, 22, 14, 7],
   ...:     'Z': [12, 4, 9, 16, 13, 1, 8, 17, 11, 19],
   ...: }
   ...:
In [3]: b = {
   ...:     'X': [1, 8, 20, 7, 32],
   ...:     'Y': [6, 4, 17, 45, 32],
   ...:     'Z': [52, 12, 6, 8, 31],
   ...: }

In [4]: df1 = pd.DataFrame(data=a)
In [5]: df2 = pd.DataFrame(data=b)
In [6]: print(df1)
    X   Y   Z
0   1   3  12
1   2   4   4
2   5   8   9
3   7  15  16
4  10  20  13
5   5  12   1
6   2  23   8
7   3  22  17
8  24  14  11
9  21   7  19

In [7]: print(df2)
    X   Y   Z
0   1   6  52
1   8   4  12
2  20  17   6
3   7  45   8
4  32  32  31

Мне нужно найти ближайшую точку (расстояние) в df1 до каждой точки df2 и создать новый DataFrame.

Поэтому я написал приведенный ниже код и фактически нашел ближайшую точку (расстояние) до df2.iloc[0].

In [8]: x = (
   ...:     np.sqrt(
   ...:         ((df1['X'].sub(df2["X"].iloc[0]))**2)
   ...:         .add(((df1['Y'].sub(df2["Y"].iloc[0]))**2))
   ...:         .add(((df1['Z'].sub(df2["Z"].iloc[0]))**2))
   ...:     )
   ...: ).idxmin()

In [9]: x1 = df1.iloc[[x]]
In[10]: print(x1)
   X   Y   Z
3  7  15  16

Итак, я думаю, мне нужен цикл для перебора df2 и применения приведенного выше кода к каждой строке. В результате мне нужен новый обновленный df1, содержащий все ближайшие точки к каждой точке df2. Но не успел. Пожалуйста, порекомендуйте.


person Mirzali Jafarov    schedule 20.06.2020    source источник


Ответы (2)


На самом деле это отличный пример случая, когда правила вещания numpy имеют явные преимущества перед pandas.

Вручную выровняв координаты df1 в виде векторов-столбцов (ссылаясь на df1[[col]].to_numpy()) и координаты df2 в виде векторов-строк (df2[col].to_numpy()), мы можем очень быстро получить расстояние от каждого элемента в каждом кадре данных до каждого элемента в другом с помощью автоматической трансляции:

In [26]: dists = np.sqrt(
    ...:     (df1[['X']].to_numpy() - df2['X'].to_numpy()) ** 2
    ...:     + (df1[['Y']].to_numpy() - df2['Y'].to_numpy()) ** 2
    ...:     + (df1[['Z']].to_numpy() - df2['Z'].to_numpy()) ** 2
    ...: )

In [27]: dists
Out[27]:
array([[40.11234224,  7.07106781, 24.35159132, 42.61455151, 46.50806382],
       [48.05205511, 10.        , 22.29349681, 41.49698784, 49.12229636],
       [43.23193264,  5.83095189, 17.74823935, 37.06750599, 42.29657197],
       [37.58989226, 11.74734012, 16.52271164, 31.04834939, 33.74907406],
       [42.40283009, 16.15549442, 12.56980509, 25.67099531, 30.85449724],
       [51.50728104, 13.92838828, 16.58312395, 33.7934905 , 45.04442252],
       [47.18050445, 20.32240143, 19.07878403, 22.56102835, 38.85871846],
       [38.53569774, 19.33907961, 20.85665361, 25.01999201, 33.7194306 ],
       [47.68647607, 18.89444363,  7.07106781, 35.48239   , 28.0713377 ],
       [38.60051813, 15.06651917, 16.43167673, 41.96427052, 29.83286778]])

Argmin теперь даст вам правильный вектор позиционных индексов:

In [28]: dists.argmin(axis=0)
Out[28]: array([3, 2, 8, 6, 8])

Или, чтобы выбрать соответствующие значения из df1:

In [29]: df1.iloc[dists.argmin(axis=0)]
Out[29]:
    X   Y   Z
3   7  15  16
2   5   8   9
8  24  14  11
6   2  23   8
8  24  14  11

Редактировать

Ответ появился сразу после моего, а затем был удален со ссылкой на scipy.spatial.distance_matrix, вычисление dists с помощью:

distance_matrix(df1[list('XYZ')].to_numpy(), df2[list('XYZ')].to_numpy())

Не уверен, почему этот ответ был удален, но это кажется действительно хорошим, чистым подходом к получению массива, который я создал вручную выше!

Примечание о производительности

Обратите внимание, что если вы просто пытаетесь получить ближайшее значение, нет необходимости извлекать квадратный корень, так как это дорогостоящая операция по сравнению со сложением, вычитанием и степенями, а сортировка по dist**2 по-прежнему действительна.

person Michael Delgado    schedule 20.06.2020
comment
Большое спасибо за вашу помощь. Оба подхода решили мою проблему. - person Mirzali Jafarov; 20.06.2020

Во-первых, вы определяете функцию, которая возвращает ближайшую точку, используя numpy.where. Затем вы используете функцию применения для запуска через df2.

import pandas as pd
import numpy as np
a = {
   'X': [1, 2, 5, 7, 10, 5, 2, 3, 24, 21],
   'Y': [3, 4, 8, 15, 20, 12, 23, 22, 14, 7],
   'Z': [12, 4, 9, 16, 13, 1, 8, 17, 11, 19]
 }
b = {
   'X': [1, 8, 20, 7, 32],
   'Y': [6, 4, 17, 45, 32],
   'Z': [52, 12, 6, 8, 31]
 }
df1 = pd.DataFrame(a)
df2 = pd.DataFrame(b)

dist = lambda dx,dy,dz: np.sqrt(dx**2+dy**2+dz**2)

def closest(row):
    darr = dist(df1['X']-row['X'], df1['Y']-row['Y'], df1['Z']-row['Z'])
    idx = np.where(darr == np.amin(darr))[0][0]
    return df1['X'][idx], df1['Y'][idx], df1['Z'][idx]

df2['closest'] = df2.apply(closest, axis=1)

print(df2)

Выход:

    X   Y   Z       closest
0   1   6  52   (7, 15, 16)
1   8   4  12     (5, 8, 9)
2  20  17   6  (24, 14, 11)
3   7  45   8    (2, 23, 8)
4  32  32  31  (24, 14, 11)
person LevB    schedule 20.06.2020