Продолжая этот вопрос много лет назад, есть ли в 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])
Этот вопрос возник из-за моей вчерашней попытки написать быстрый Rolling_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
np.r_[np.full(n, np.nan), xs[:-n]]
можно ли заменить на тоnp.r_[[np.nan]*n, xs[:-n]]
же самое для другого состояния, без необходимостиnp.full
[np.nan]*n
- это простой питон и поэтому будет медленнее, чемnp.full(n, np.nan)
. Не для маленькогоn
, но он будет преобразован в массив numpy с помощью np.r_, что лишает преимущества.[np.nan]*n
работает быстрее, чемnp.full(n, np.nan)
дляn=[10,1000,10000]
. Нужно проверить,np.r_
попадает ли он.Ответы:
Не 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)
где по умолчанию вводится постоянное значение извне массива со значением
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])
источник
Для тех, кто хочет просто скопировать и вставить самую быструю реализацию сдвига, есть тест и заключение (см. Конец). Кроме того, я ввел параметр 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 - победитель! Это третье решение OP.
источник
shift5
лучше писатьresult[:] = arr
вместоresult = arr
, чтобы поведение функции было согласованным.type(np.NAN) is float
. Если вы сдвигаете целочисленный массив с помощью этих функций, вам необходимо указать целочисленное значение fill_value.Не существует единственной функции, которая делала бы то, что вы хотите. Ваше определение смены немного отличается от того, что делает большинство людей. Способы сдвига массива чаще всего зацикливаются:
>>>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 при отрицательном.Я буду экспериментировать со временем, чтобы увидеть, как каждый работает с реальными данными.источник
np.roll()
; Я использовал технику в ссылках в моем вопросе. Что касается вашей реализации, есть ли шанс, что вы сможете заставить свою функцию работать при отрицательных значениях сдвига?np.concatenate()
что это намного быстрее, чемnp.r_[]
. Вnp.roll()
конце концов, первое - это то , что использует.Вы можете конвертировать
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.])
источник
Тесты и знакомство с Numba
1. Резюме
scipy.ndimage.interpolation.shift
) - самое медленное решение, указанное на этой странице.(1) длины ваших массивов
(2) количества сдвига, которое вам нужно сделать.
2. Подробные тесты с лучшими вариантами
shift4_numba
(определено ниже), если вам нужен хороший универсал3. Код
3.1
shift4_numba
import numba @numba.njit def shift4_numba(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)))
3.2.
shift5_numba
import numba @numba.njit def shift5_numba(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
3.3.
shift5
shift5_numba
, просто удалите декоратор @ numba.njit.4 Приложение
4.1 Подробная информация об используемых методах
shift_scipy
:scipy.ndimage.interpolation.shift
(scipy 1.4.1) - вариант из принятого ответа, который явно является самой медленной альтернативой .shift1
:np.roll
Иout[:num] xnp.nan
от IronManMark20 & gzcshift2
:np.roll
Иnp.put
по IronManMark20shift3
:np.pad
иslice
от gzcshift4
:np.concatenate
иnp.full
автор chrisaycockshift5
: использование два разаresult[slice] = x
по Крисэйкокуshift#_numba
: @ numba .njit оформленные версии предыдущего.В
shift2
иshift3
содержались функции, которые не поддерживались текущей версией numba (0.50.1).4.2 Другие результаты испытаний
4.2.1 Относительное время, все методы
4.2.2 Исходные тайминги, все методы
4.2.3 Необработанные тайминги, несколько лучших методов
источник
Вы также можете сделать это с помощью Pandas:
Используя массив длиной 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)
Используя Pandas:
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.
источник
Series
техника потребовала 146 нас на моем компьютере, тогда как мой подход потребовал менее 4 нас.Если вы хотите однострочник от numpy и не слишком беспокоитесь о производительности, попробуйте:
np.sum(np.diag(the_array,1),0)[:-1]
Объяснение:
np.diag(the_array,1)
создает матрицу с вашим массивом по одной диагонали,np.sum(...,0)
суммирует матрицу по столбцам и...[:-1]
берет элементы, которые соответствуют размеру исходного массива. Игра с параметрами1
and:-1
as может дать вам сдвиги в разных направлениях.источник
Один из способов сделать это, не разбивая код на кейсы
с массивом:
def shift(arr, dx, default_value): result = np.empty_like(arr) get_neg_or_none = lambda s: s if s < 0 else None get_pos_or_none = lambda s: s if s > 0 else None result[get_neg_or_none(dx): get_pos_or_none(dx)] = default_value result[get_pos_or_none(dx): get_neg_or_none(dx)] = arr[get_pos_or_none(-dx): get_neg_or_none(-dx)] return result
с матрицей это можно сделать так:
def shift(image, dx, dy, default_value): res = np.full_like(image, default_value) get_neg_or_none = lambda s: s if s < 0 else None get_pos_or_none = lambda s : s if s > 0 else None res[get_pos_or_none(-dy): get_neg_or_none(-dy), get_pos_or_none(-dx): get_neg_or_none(-dx)] = \ image[get_pos_or_none(dy): get_neg_or_none(dy), get_pos_or_none(dx): get_neg_or_none(dx)] return res
источник