2012-06-15 8 views
4

У меня есть трехмерный ndarray 2D-координата, например:NumPy: Выполнение функции над каждым ndarray элементом

[[[1704 1240] 
    [1745 1244] 
    [1972 1290] 
    [2129 1395] 
    [1989 1332]] 

[[1712 1246] 
    [1750 1246] 
    [1964 1286] 
    [2138 1399] 
    [1989 1333]] 

[[1721 1249] 
    [1756 1249] 
    [1955 1283] 
    [2145 1399] 
    [1990 1333]]] 

Конечная цель состоит в том, чтобы удалить точку, ближайшую к заданной точке ([1989 г. тысячи триста тридцать-два]) из каждой «группы» из 5 координат. Моя мысль заключалась в том, чтобы создать массив расстояний аналогичной формы, а затем использовать argmin для определения индексов значений, которые нужно удалить. Тем не менее, я не уверен, как приступить к применению функции, например, для вычисления расстояния до данной точки, каждому элементу в ndarray, по крайней мере, на NumPythonic.

ответ

4

Список постижений являются очень неэффективными способ иметь дело с массивами numpy. Они являются особенно плохим выбором для расчета расстояния.

Чтобы найти разницу между данными и точкой, вы должны просто сделать data - point. Затем вы можете рассчитать расстояние, используя np.hypot, или, если хотите, поместите квадрат, суммируйте его и возьмите квадратный корень.

Это немного проще, если вы сделаете его массивом Nx2 для целей расчета.

В принципе, вы хотите что-то вроде этого:

import numpy as np 

data = np.array([[[1704, 1240], 
        [1745, 1244], 
        [1972, 1290], 
        [2129, 1395], 
        [1989, 1332]], 

       [[1712, 1246], 
        [1750, 1246], 
        [1964, 1286], 
        [2138, 1399], 
        [1989, 1333]], 

       [[1721, 1249], 
        [1756, 1249], 
        [1955, 1283], 
        [2145, 1399], 
        [1990, 1333]]]) 

point = [1989, 1332] 

#-- Calculate distance ------------ 
# The reshape is to make it a single, Nx2 array to make calling `hypot` easier 
dist = data.reshape((-1,2)) - point 
dist = np.hypot(*dist.T) 

# We can then reshape it back to AxBx1 array, similar to the original shape 
dist = dist.reshape(data.shape[0], data.shape[1], 1) 
print dist 

Это дает:

array([[[ 299.48121811], 
     [ 259.38388539], 
     [ 45.31004304], 
     [ 153.5219854 ], 
     [ 0.  ]], 

     [[ 290.04310025], 
     [ 254.0019685 ], 
     [ 52.35456045], 
     [ 163.37074401], 
     [ 1.  ]], 

     [[ 280.55837182], 
     [ 247.34186868], 
     [ 59.6405902 ], 
     [ 169.77926846], 
     [ 1.41421356]]]) 

Теперь, убрав ближайший элемент является немного сложнее, чем просто получать ближайший элемент.

С помощью numpy вы можете использовать булево индексирование, чтобы сделать это довольно легко.

Однако вам нужно немного беспокоиться о выравнивании ваших осей.

Ключ должен понимать, что операции «трансляции» в режиме «широковещания» выполняются по оси последней. В этом случае мы хотим бродить по средней оси.

Также -1 может использоваться в качестве заполнителя для размера оси. Numpy рассчитает допустимый размер, если -1 помещается как размер оси.

Что мы должны сделать, будет выглядеть немного так:

#-- Remove closest point --------------------- 
mask = np.squeeze(dist) != dist.min(axis=1) 
filtered = data[mask] 

# Once again, let's reshape things back to the original shape... 
filtered = filtered.reshape(data.shape[0], -1, data.shape[2]) 

Вы могли бы сделать, что одна линия, я просто разбив его для удобства чтения. Ключ состоит в том, что dist != something дает булевский массив, который затем можно использовать для индексации исходного массива.

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

import numpy as np 

data = np.array([[[1704, 1240], 
        [1745, 1244], 
        [1972, 1290], 
        [2129, 1395], 
        [1989, 1332]], 

       [[1712, 1246], 
        [1750, 1246], 
        [1964, 1286], 
        [2138, 1399], 
        [1989, 1333]], 

       [[1721, 1249], 
        [1756, 1249], 
        [1955, 1283], 
        [2145, 1399], 
        [1990, 1333]]]) 

point = [1989, 1332] 

#-- Calculate distance ------------ 
# The reshape is to make it a single, Nx2 array to make calling `hypot` easier 
dist = data.reshape((-1,2)) - point 
dist = np.hypot(*dist.T) 

# We can then reshape it back to AxBx1 array, similar to the original shape 
dist = dist.reshape(data.shape[0], data.shape[1], 1) 

#-- Remove closest point --------------------- 
mask = np.squeeze(dist) != dist.min(axis=1) 
filtered = data[mask] 

# Once again, let's reshape things back to the original shape... 
filtered = filtered.reshape(data.shape[0], -1, data.shape[2]) 

print filtered 

Урожайность:

array([[[1704, 1240], 
     [1745, 1244], 
     [1972, 1290], 
     [2129, 1395]], 

     [[1712, 1246], 
     [1750, 1246], 
     [1964, 1286], 
     [2138, 1399]], 

     [[1721, 1249], 
     [1756, 1249], 
     [1955, 1283], 
     [2145, 1399]]]) 

На стороне записки, если более чем один пункт одинаково близко, это не будет работать. Массивные массивы должны иметь одинаковое количество элементов вдоль каждого измерения, поэтому в этом случае вам нужно будет повторно выполнить группировку.

+0

А как-то я не видел этого, прежде чем отправил. Я думал об использовании 'apply_along_axis', но я тестировал его, и это намного быстрее. – senderle

+0

'apply_along_axis' должно использовать меньше памяти, поэтому оба подхода по-прежнему полезны! –

+0

Спасибо! Очень краткий, но информативный. Так быстро, тоже. – OneTrickyPony

0

Есть несколько способов сделать это, но вот один, используя списочные:

Расстояние Функция:

In [35]: from numpy.linalg import norm 

In [36]: dist = lambda x,y:norm(x-y) 

Входные данные:

In [39]: GivenMatrix = scipy.rand(3, 5, 2) 

In [40]: GivenMatrix 
Out[40]: 
array([[[ 0.83798666, 0.90294439], 
     [ 0.8706959 , 0.88397176], 
     [ 0.91879085, 0.93512921], 
     [ 0.15989245, 0.57311869], 
     [ 0.82896003, 0.53589968]], 

     [[ 0.0207089 , 0.9521768 ], 
     [ 0.94523963, 0.31079109], 
     [ 0.41929482, 0.88559614], 
     [ 0.87885236, 0.45227422], 
     [ 0.58365369, 0.62095507]], 

     [[ 0.14757177, 0.86101539], 
     [ 0.58081214, 0.12632764], 
     [ 0.89958321, 0.73660852], 
     [ 0.3408943 , 0.45420989], 
     [ 0.42656333, 0.42770216]]]) 

In [41]: q = scipy.rand(2) 

In [42]: q 
Out[42]: array([ 0.03280889, 0.71057403]) 

выходные Compute расстояния:

In [44]: distances = [[dist(x, q) for x in SubMatrix] 
         for SubMatrix in GivenMatrix] 

In [45]: distances 
Out[45]: 
[[0.82783910695733931, 
    0.85564093542511577, 
    0.91399620574915652, 
    0.18720096539588818, 
    0.81508758596405939], 
[0.24190557184498068, 
    0.99617079746515047, 
    0.42426891258164884, 
    0.88459501973012633, 
    0.55808740166908177], 
[0.18921712490174292, 
    0.80103146210692744, 
    0.86716521557255788, 
    0.40079819635686459, 
    0.48482888965287363]] 

Для ранжирования результатов для каждой подматрицы:

In [46]: scipy.argsort(distances) 
Out[46]: 
array([[3, 4, 0, 1, 2], 
     [0, 2, 4, 3, 1], 
     [0, 3, 4, 1, 2]]) 

Что касается удаления, то я лично считаю, что это самое простым путем преобразования GivenMatrix в list, а затем с помощью del:

>>> GivenList = GivenMatrix.tolist() 

>>> del GivenList[1][2] # delete third row from the second 5-by-2 submatrix 
1

Если я правильно понял ваш вопрос, я думаю, что вы ищете apply_along_axis. Использование numpy «S встроенные в радиовещании, мы можем просто вычесть точку из массива:

>>> a - numpy.array([1989, 1332]) 
array([[[-285, -92], 
     [-244, -88], 
     [ -17, -42], 
     [ 140, 63], 
     [ 0, 0]], 

     [[-277, -86], 
     [-239, -86], 
     [ -25, -46], 
     [ 149, 67], 
     [ 0, 1]], 

     [[-268, -83], 
     [-233, -83], 
     [ -34, -49], 
     [ 156, 67], 
     [ 1, 1]]]) 

Тогда мы можем применить numpy.linalg.norm к нему:

>>> dist = a - numpy.array([1989, 1332]) 
>>> numpy.apply_along_axis(numpy.linalg.norm, 2, dist) 
array([[ 299.48121811, 259.38388539, 45.31004304, 
     153.5219854 , 0.  ], 
     [ 290.04310025, 254.0019685 , 52.35456045, 
     163.37074401, 1.  ], 
     [ 280.55837182, 247.34186868, 59.6405902 , 
     169.77926846, 1.41421356]]) 

Наконец, некоторые булевой маски обману, наряду с пар reshape звонков:

>>> a[normed != normed.min(axis=1).reshape((-1, 1))].reshape((3, 4, 2)) 
array([[[1704, 1240], 
     [1745, 1244], 
     [1972, 1290], 
     [2129, 1395]], 

     [[1712, 1246], 
     [1750, 1246], 
     [1964, 1286], 
     [2138, 1399]], 

     [[1721, 1249], 
     [1756, 1249], 
     [1955, 1283], 
     [2145, 1399]]]) 

ответ Джо Kington является быстрее, хотя. Ну что ж. Я оставлю это для потомков.

def joes(data, point): 
    dist = data.reshape((-1,2)) - point 
    dist = np.hypot(*dist.T) 
    dist = dist.reshape(data.shape[0], data.shape[1], 1) 
    mask = np.squeeze(dist) != dist.min(axis=1) 
    return data[mask].reshape((3, 4, 2)) 

def mine(a, point): 
    dist = a - point 
    normed = numpy.apply_along_axis(numpy.linalg.norm, 2, dist) 
    return a[normed != normed.min(axis=1).reshape((-1, 1))].reshape((3, 4, 2)) 

>>> %timeit mine(data, point) 
1000 loops, best of 3: 586 us per loop 
>>> %timeit joes(data, point) 
10000 loops, best of 3: 48.9 us per loop 
Смежные вопросы