Подтвердить что ты не робот

Элементы сдвига в массиве 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
4b9b3361

Ответ 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 в качестве параметров может привести к сдвигам в разных направлениях.