Как выполнить выборку массива numpy и эффективно выполнить вычисления для каждой выборки?

#python #performance #pandas #numpy #vectorization

#python #Производительность #pandas #numpy #векторизация

Вопрос:

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

Например, если у меня есть [2, 5, 8, 9, 6] и размер окна равен 3, результат будет

 [[1, 2.5, 4],
 [1, 1.6, 1.8],
 [1, 1.125, 0.75]].
  

То, что я делаю сейчас, в основном представляет собой цикл for

 import numpy as np
arr = np.array([2., 5., 8., 9., 6.])
window_size = 3
for i in range(len(arr) - window_size   1):
  result.append(arr[i : i   window_size] / arr[i])
  

и т.д.

Когда массив большой, он работает довольно медленно, интересно, есть ли способы получше? Я думаю, что нет способа обойти сложность O (n ^ 2), но, возможно, в numpy есть некоторые оптимизации, о которых я не знаю.

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

1. Опубликованный вами код не дает опубликованного вами результата. Также, пожалуйста, добавьте инициализацию ваших переменных.

2. Также, возможно, используйте пример, который не приводит к симметричной матрице, поскольку это затрудняет правильную трансляцию numpy.

Ответ №1:

Вот векторизованный подход, использующий broadcasting

 N = 3  # Window size
nrows = a.size-N 1
a2D = a[np.arange(nrows)[:,None]   np.arange(N)]
out = a2D/a[:nrows,None].astype(float)
  

Мы также можем использовать NumPy strides для более эффективного извлечения скользящих окон, например —

 n = a.strides[0]
a2D = np.lib.stride_tricks.as_strided(a,shape=(nrows,N),strides=(n,n))
  

Пример запуска —

 In [73]: a
Out[73]: array([4, 9, 3, 6, 5, 7, 2])

In [74]: N = 3
    ...: nrows = a.size-N 1
    ...: a2D = a[np.arange(nrows)[:,None]   np.arange(N)]
    ...: out = a2D/a[:nrows,None].astype(float)
    ...: 

In [75]: out
Out[75]: 
array([[ 1.        ,  2.25      ,  0.75      ],
       [ 1.        ,  0.33333333,  0.66666667],
       [ 1.        ,  2.        ,  1.66666667],
       [ 1.        ,  0.83333333,  1.16666667],
       [ 1.        ,  1.4       ,  0.4       ]])
  

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

1. Ваше решение выдает правильный результат с тестовым массивом, но не работает при других длинах массива с operands could not be broadcast together -ошибкой.

2. @Khris Спасибо, что указали на это, в моем коде действительно была ошибка. Исправлено.

3. Я также работал над решением с использованием strides . Ваше второе решение с использованием strides в два раза быстрее, чем ваше первое решение. Я рассчитал время работы OP и ваших двух алгоритмов с длиной массива 10000 . OP был 31.7 ms , ваш первый был 740 µs , ваш второй был 378 µs . Мой был таким же, как ваш второй, поэтому я его не публикую.

4. @Khris Спасибо за тестирование! Приятно видеть 80x ускорение!