Оконная операция над фреймом данных pandas для вывода евклидова расстояния от предыдущих n записей

У меня есть отсортированный (по «значениям») фрейм данных, который выглядит следующим образом. Безымянный столбец использует index.

        x_cord  y_cord  value
3384209 1650    1741    0.009752
3382265 1650    1740    0.009481
3384208 1649    1741    0.008943
3382264 1649    1740    0.008676
3384210 1651    1741    0.008473
... ... ... ...
1679661 46      865     0.000000
1679660 45      865     0.000000
1679659 44      865     0.000000
1679658 43      865     0.000000
5038847 1944    2592    0.000000

Как я могу создать еще один столбец со списком евклидовых расстояний до предыдущих n строк? Например,

  • в первой строке будет пустой список
  • вторая строка будет иметь список расстояний между первой и второй строками (см. координаты), то есть 1 элемент.
  • третий ряд будет иметь. список из двух элементов, то есть между третьим и вторым, а также третьим и первым.
  • 21-я строка будет содержать список из десяти пунктов с евклидовыми расстояниями между ним и предыдущими девятью пунктами.

В списке всегда будет не более десяти (или n) элементов.

Как я могу этого добиться?

К вашему сведению

def euc_distance(x1, y1, x2, y2):
    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

person Dharmender Tathgur    schedule 30.01.2021    source источник


Ответы (2)


Numpy спешит на помощь! (быстрое решение, в основном векторизованное):

def fun(k, z):
    na = np.full((k, z.shape[1]), np.nan)
    a = np.row_stack((na, z[:-k, :]))
    return np.linalg.norm(z - a, axis=1)

vfun = np.vectorize(fun, signature='(),(n,m)->(n)')

Теперь вы можете получить массив (n, k) со всеми расстояниями:

>>> k = 4  # just for nice printing; replace with 10 in your setting
>>> z = pd.DataFrame(vfun(range(1, k+1), df[['x_cord', 'y_cord']].values).T)
... z
             0            1            2            3
0          NaN          NaN          NaN          NaN
1     1.000000          NaN          NaN          NaN
2     1.414214     1.000000          NaN          NaN
3     1.000000     1.000000     1.414214          NaN
4     2.236068     2.000000     1.414214     1.000000
5  1828.496924  1826.262303  1826.741635  1827.140115
6     1.000000  1829.374757  1827.140115  1827.619216
7     1.000000     2.000000  1830.252715  1828.018052
8     1.000000     2.000000     3.000000  1831.130798
9  2568.332144  2567.592063  2566.852158  2566.112429

Вы можете преобразовать это в списки, если хотите:

>>> z.apply(list, axis=1)
0                                 [nan, nan, nan, nan]
1                                 [1.0, nan, nan, nan]
2                  [1.4142135623730951, 1.0, nan, nan]
3                  [1.0, 1.0, 1.4142135623730951, nan]
4     [2.23606797749979, 2.0, 1.4142135623730951, 1.0]
5    [1828.4969237053695, 1826.2623031755325, 1826....
6    [1.0, 1829.3747565766835, 1827.1401150431786, ...
7    [1.0, 2.0, 1830.2527147910475, 1828.018052427273]
8                  [1.0, 2.0, 3.0, 1831.1307981681703]
9    [2568.3321436294023, 2567.592062614309, 2566.8...

Собираем все вместе:

out = df.assign(
    distances=pd.DataFrame(
        vfun(list(range(1, k+1)), df[['x_cord', 'y_cord']].values).T,
        index=df.index,
    ).apply(list, axis=1)
)

Из:

         x_cord  y_cord     value  \
3384209    1650    1741  0.009752   
3382265    1650    1740  0.009481   
3384208    1649    1741  0.008943   
3382264    1649    1740  0.008676   
3384210    1651    1741  0.008473   
1679661      46     865  0.000000   
1679660      45     865  0.000000   
1679659      44     865  0.000000   
1679658      43     865  0.000000   
5038847    1944    2592  0.000000   

                                                 distances  
3384209                               [nan, nan, nan, nan]  
3382265                               [1.0, nan, nan, nan]  
3384208                [1.4142135623730951, 1.0, nan, nan]  
3382264                [1.0, 1.0, 1.4142135623730951, nan]  
3384210   [2.23606797749979, 2.0, 1.4142135623730951, 1.0]  
1679661  [1828.4969237053695, 1826.2623031755325, 1826....  
1679660  [1.0, 1829.3747565766835, 1827.1401150431786, ...  
1679659  [1.0, 2.0, 1830.2527147910475, 1828.018052427273]  
1679658                [1.0, 2.0, 3.0, 1831.1307981681703]  
5038847  [2568.3321436294023, 2567.592062614309, 2566.8...  

В вашем случае установите k=10 вместо 4.

person Pierre D    schedule 31.01.2021

Сначала сдвиньте все 10 предыдущих строк в отдельный столбец для каждой смены:

for i in range(1,11):
    df["x_cord_"+str(i)] = df["x_cord"].shift(i)
    df["y_cord_"+str(i)] = df["y_cord"].shift(i)

Затем вычислите расстояние между каждой строкой и 10 другими координатами, которые теперь хранятся в соответствующих столбцах. if гарантирует, что если в столбце есть Nan, результирующий список не будет содержать это значение.

df["distance"] = df.apply(lambda row: [euc_distance(row["x_cord"], row["y_cord"], row["x_cord_"+str(i)], row["y_cord_"+str(i)]) for i in range(1,11)  if not math.isnan(row["x_cord_"+str(i)])], axis=1)

В завершение удалите только столбцы, созданные для расчета.

df = df[["x_cord", "y_cord", "value", "distance"]]

Выход:

          x_cord  y_cord       value    distance
3384209     1650    1741    0.009752    []
3382265     1650    1740    0.009481    [1.0]
3384208     1649    1741    0.008943    [1.4142135623730951, 1.0]
3382264     1649    1740    0.008676    [1.0, 1.0, 1.4142135623730951]
3384210     1651    1741    0.008473    [2.23606797749979, 2.0, 1.4142135623730951, 1.0]

Это может быть улучшено.

person XavierBrt    schedule 30.01.2021