Найти ближайший индекс в одном фрейме данных к другому - PullRequest
0 голосов
/ 20 июня 2020

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

Итак, у меня есть два DataFrame, как показано ниже, содержащих координаты XYZ (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

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

Ответы [ 2 ]

0 голосов
/ 20 июня 2020

Сначала вы определяете функцию, которая возвращает ближайшую точку, используя numpy .where. Затем вы используете функцию apply для запуска через 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)
0 голосов
/ 20 июня 2020

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

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

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

Edit

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

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

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

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

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

...