Numpy применяет функцию к каждому элементу в массиве

#python #arrays #list #numpy

#python #массивы #numpy

Вопрос:

Итак, допустим, у меня есть 2d-массив. Как я могу применить функцию к каждому отдельному элементу в массиве и заменить этот элемент возвращаемым значением? Кроме того, возвращаемым значением функции будет кортеж, поэтому массив станет трехмерным.

Вот код в виду.

 def filter_func(item):
    if 0 <= item < 1:
        return (1, 0, 1)
    elif 1 <= item < 2:
        return (2, 1, 1)
    elif 2 <= item < 3:
        return (5, 1, 4)
    else:
        return (4, 4, 4)

myarray = np.array([[2.5, 1.3], [0.4, -1.0]])

# Apply the function to an array

print(myarray)

# Should be array([[[5, 1, 4],
#                   [2, 1, 1]],
#                  [[1, 0, 1],
#                   [4, 4, 4]]])
 

Есть идеи, как я мог бы это сделать? Один из способов — это сделать np.array(list(map(filter_func, myarray.reshape((12,))))).reshape((2, 2, 3)) , но это довольно медленно, особенно когда мне нужно сделать это для массива формы (1024, 1024).

Я также видел, как люди использовали np.vectorize , но это каким-то образом заканчивается тем, что (array([[5, 2], [1, 4]]), array([[1, 1], [0, 4]]), array([[4, 1], [1, 4]])). тогда оно имеет форму (3, 2, 2) .

Ответ №1:

Нет необходимости что-либо менять в вашей функции.

Просто примените векторизованную версию вашей функции к вашему массиву и сложите результат:

 np.stack(np.vectorize(filter_func)(myarray), axis=2)
 

Результат:

 array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])
 

Комментарии:

1. Этот параметр указывает, вдоль какой оси должна происходить укладка (описано в документации Numpy ). Попробуйте мой код с axis == 1 и 0 , чтобы увидеть разницу.

2. Спасибо! Это именно то, что мне было нужно. Это примерно в 8 раз быстрее, чем моя первая попытка!

3. В моих таймингах это vectorize происходит медленнее, чем у вас list(map...) . Я всегда считал vectorize , что это медленнее, чем обычная итерация.

Ответ №2:

вы могли бы использовать эту функцию с векторизованной реализацией

 def func(arr):
    
    elements = np.array([
        [1, 0, 1],
        [2, 1, 1],
        [5, 1, 4],
        [4, 4, 4],
    ])
    
    arr  = arr.astype(int)
    mask = (arr != 0) amp; (arr != 1) amp; (arr != 2)

    arr[mask] = -1
    
    return elements[arr]
 

вы не сможете переписать свой массив из-за несоответствия формы
, но вы можете перезаписать переменную myarray

 myarray = func(myarray)
myarray

>>>   [[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]]
 

Комментарии:

1. Ммм, как бы я это сделал, если бы у меня уже была функция? Я действительно не понимаю, что делает ваш код

Ответ №3:

Ваш список-карта:

 In [4]: np.array(list(map(filter_func, myarray.reshape((4,))))).reshape((2, 2, 3))                   
Out[4]: 
array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])
 

Вариант, использующий понимание вложенного списка:

 In [5]: np.array([[filter_func(j) for j in row] for row in myarray])                                 
Out[5]: 
array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])
 

При использовании vectorize результатом является один массив для каждого элемента, возвращаемого функцией.

 In [6]: np.vectorize(filter_func)(myarray)                                                           
Out[6]: 
(array([[5, 2],
        [1, 4]]),
 array([[1, 1],
        [0, 4]]),
 array([[4, 1],
        [1, 4]]))
 

Как показывает @Vladi, они могут быть объединены с stack (или np.array сопровождаться транспонированием):

 In [7]: np.stack(np.vectorize(filter_func)(myarray),2)                                               
Out[7]: 
array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])
 

Ваш список-карта самый быстрый. Я никогда не считал vectorize , что это быстрее:

 In [8]: timeit np.array(list(map(filter_func, myarray.reshape((4,))))).reshape((2, 2, 3))            
17.2 µs ± 47.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [9]: timeit np.array([[filter_func(j) for j in row] for row in myarray])                          
20.5 µs ± 78.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [10]: timeit np.stack(np.vectorize(filter_func)(myarray),2)                                       
75.2 µs ± 297 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
 

Вывод np.vectorize(filter_func) из цикла синхронизации немного помогает.

frompyfunc аналогично vectorize , но возвращает dtype объекта. Обычно это быстрее:

 In [29]: timeit np.stack(np.frompyfunc(filter_func, 1,3)(myarray),2).astype(int)                     
28.7 µs ± 125 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
 

Как правило, если у вас есть функция, которая принимает только скалярные входные данные, трудно сделать лучше, чем простая итерация. vectorize/frompyfunc не улучшайте это. Оптимальное использование numpy требует переписывания функции для работы непосредственно с массивами, как демонстрирует @Hammad.

Хотя в этом небольшом примере даже это правильное numpy решение не работает быстрее. Я ожидаю, что он будет масштабироваться лучше:

 In [32]: timeit func(myarray)                                                                        
25 µs ± 60.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
 

Комментарии:

1. Отображение списка заняло 6,34 секунды для массива размером 1024 на 1024, но векторизация заняла всего 1,18 секунды. Возможно, список-карта лучше подходит для массивов меньшего размера.