Элементы сдвига в массиве numpy
Следуя этому вопросу лет назад, есть ли каноническая функция "сдвига" в numpy? Я ничего не вижу из документации.
Вот простая версия того, что я ищу:
def shift(xs, n):
if n >= 0:
return np.r_[np.full(n, np.nan), xs[:-n]]
else:
return np.r_[xs[-n:], np.full(-n, np.nan)]
Использование этого типа:
In [76]: xs
Out[76]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [77]: shift(xs, 3)
Out[77]: array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
In [78]: shift(xs, -3)
Out[78]: array([ 3., 4., 5., 6., 7., 8., 9., nan, nan, nan])
Этот вопрос пришел из моей попытки написать быстрый файл rol_product вчера. Мне нужен был способ "сдвинуть" кумулятивный продукт, и все, о чем я мог думать, это повторить логику в np.roll()
.
Итак, np.concatenate()
намного быстрее, чем np.r_[]
. Эта версия функции работает намного лучше:
def shift(xs, n):
if n >= 0:
return np.concatenate((np.full(n, np.nan), xs[:-n]))
else:
return np.concatenate((xs[-n:], np.full(-n, np.nan)))
Еще более быстрая версия просто предварительно выделяет массив:
def shift(xs, n):
e = np.empty_like(xs)
if n >= 0:
e[:n] = np.nan
e[n:] = xs[:-n]
else:
e[n:] = np.nan
e[:n] = xs[-n:]
return e
Ответы
Ответ 1
Не numpy, но scipy обеспечивает именно необходимую функциональность сдвига,
import numpy as np
from scipy.ndimage.interpolation import shift
xs = np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
shift(xs, 3, cval=np.NaN)
где default - ввести постоянное значение извне массива со значением cval
, установите здесь nan
. Это дает желаемый результат,
array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
и отрицательный сдвиг работает аналогично,
shift(xs, -3, cval=np.NaN)
Обеспечивает вывод
array([ 3., 4., 5., 6., 7., 8., 9., nan, nan, nan])
Ответ 2
Для тех, кто хочет просто скопировать и вставить самую быструю реализацию shift, есть тест и заключение (см. В конце). Кроме того, я ввел параметр fill_value и исправил некоторые ошибки.
эталонный тест
import numpy as np
import timeit
# enhanced from IronManMark20 version
def shift1(arr, num, fill_value=np.nan):
arr = np.roll(arr,num)
if num < 0:
arr[num:] = fill_value
elif num > 0:
arr[:num] = fill_value
return arr
# use np.roll and np.put by IronManMark20
def shift2(arr,num):
arr=np.roll(arr,num)
if num<0:
np.put(arr,range(len(arr)+num,len(arr)),np.nan)
elif num > 0:
np.put(arr,range(num),np.nan)
return arr
# use np.pad and slice by me.
def shift3(arr, num, fill_value=np.nan):
l = len(arr)
if num < 0:
arr = np.pad(arr, (0, abs(num)), mode='constant', constant_values=(fill_value,))[:-num]
elif num > 0:
arr = np.pad(arr, (num, 0), mode='constant', constant_values=(fill_value,))[:-num]
return arr
# use np.concatenate and np.full by chrisaycock
def shift4(arr, num, fill_value=np.nan):
if num >= 0:
return np.concatenate((np.full(num, fill_value), arr[:-num]))
else:
return np.concatenate((arr[-num:], np.full(-num, fill_value)))
# preallocate empty array and assign slice by chrisaycock
def shift5(arr, num, fill_value=np.nan):
result = np.empty_like(arr)
if num > 0:
result[:num] = fill_value
result[num:] = arr[:-num]
elif num < 0:
result[num:] = fill_value
result[:num] = arr[-num:]
else:
result[:] = arr
return result
arr = np.arange(2000).astype(float)
def benchmark_shift1():
shift1(arr, 3)
def benchmark_shift2():
shift2(arr, 3)
def benchmark_shift3():
shift3(arr, 3)
def benchmark_shift4():
shift4(arr, 3)
def benchmark_shift5():
shift5(arr, 3)
benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5']
for x in benchmark_set:
number = 10000
t = timeit.timeit('%s()' % x, 'from __main__ import %s' % x, number=number)
print '%s time: %f' % (x, t)
Результат теста:
benchmark_shift1 time: 0.265238
benchmark_shift2 time: 0.285175
benchmark_shift3 time: 0.473890
benchmark_shift4 time: 0.099049
benchmark_shift5 time: 0.052836
Заключение
shift5 - победитель! Это ОП третьего решения.
Ответ 3
Нет единственной функции, которая делает то, что вы хотите. Ваше определение сдвига немного отличается от того, что делают большинство людей. Способы смещения массива чаще всего зацикливаются:
>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])
Однако вы можете делать то, что хотите, с двумя функциями.
Рассмотрим a=np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
:
def shift2(arr,num):
arr=np.roll(arr,num)
if num<0:
np.put(arr,range(len(arr)+num,len(arr)),np.nan)
elif num > 0:
np.put(arr,range(num),np.nan)
return arr
>>>shift2(a,3)
[ nan nan nan 0. 1. 2. 3. 4. 5. 6.]
>>>shift2(a,-3)
[ 3. 4. 5. 6. 7. 8. 9. nan nan nan]
После запуска cProfile в вашей заданной функции и приведенного выше кода, я обнаружил, что предоставленный вами код делает 42 вызова функций, а shift2
совершил 14 вызовов, когда arr положителен, а 16 - отрицательный. Я буду экспериментировать с временем, чтобы увидеть, как каждый из них выполняет реальные данные.
Ответ 4
Вы можете конвертировать ndarray
в Series
или DataFrame
с pandas
первым, то вы можете использовать shift
метод, как вы хотите.
Пример:
In [1]: from pandas import Series
In [2]: data = np.arange(10)
In [3]: data
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [4]: data = Series(data)
In [5]: data
Out[5]:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int64
In [6]: data = data.shift(3)
In [7]: data
Out[7]:
0 NaN
1 NaN
2 NaN
3 0.0
4 1.0
5 2.0
6 3.0
7 4.0
8 5.0
9 6.0
dtype: float64
In [8]: data = data.values
In [9]: data
Out[9]: array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
Ответ 5
Вы также можете сделать это с пандами:
Использование массива длиной 2356:
import numpy as np
xs = np.array([...])
Используя scipy:
from scipy.ndimage.interpolation import shift
%timeit shift(xs, 1, cval=np.nan)
# 956 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Используя панд:
import pandas as pd
%timeit pd.Series(xs).shift(1).values
# 377 µs ± 9.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
В этом примере использование Pandas было примерно в 8 раз быстрее, чем Scipy.
Ответ 6
Если вы хотите, чтобы NumPy содержала одну строчку и не слишком беспокоилась о производительности, попробуйте:
np.sum(np.diag(the_array,1),0)[:-1]
Объяснение: np.diag(the_array,1)
создает матрицу с вашим массивом без диагонали, np.sum(...,0)
суммирует матрицу по столбцам, а ...[:-1]
берет элементы, которые соответствуют размеру исходного массива. Использование параметров 1
и :-1
в качестве параметров может привести к сдвигам в разных направлениях.