numpy 배열의 요소 이동


83

몇 년 전이 질문에 대한 후속 조치 로 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
Zero

2
@JohnGalt [np.nan]*n는 일반 파이썬이므로 np.full(n, np.nan). small n은 아니지만 np.r_에 의해 numpy 배열로 변환되어 이점을 얻습니다.
swenzel

@swenzel 그냥 그것을 초과하고 [np.nan]*n빠르게보다 np.full(n, np.nan)위한 n=[10,1000,10000]. np.r_맞았 는지 확인해야합니다 .
Zero

속도가 중요한 경우 어레이 크기가 최상의 알고리즘을 위해 큰 역할을합니다 (아래 벤치 마크 비교 추가). 또한 요즘에는 numba.njit를 사용하여 반복적으로 호출하면 시프트를 더 빠르게 할 수 있습니다.
np8

답변:


100

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)

여기서 기본값은 value를 사용하여 배열 외부에서 상수 값을 가져 오는 것입니다 . 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])

23
scipy shift 기능은 정말 느립니다. np.concatenate를 사용하여 내 자신을 굴 렸고 훨씬 빨랐습니다.
개판

12
numpy.roll이 더 빠릅니다. 판다도 그것을 사용합니다. github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/…
fx-kirin

이 페이지에 나열된 다른 모든 대안에 대해 scipy.ndimage.interpolation.shift (scipy 1.4.1)를 테스트했으며 (아래 내 답변 참조) 가능한 가장 느린 솔루션입니다. 애플리케이션에서 속도가 중요하지 않은 경우에만 사용하십시오.
np8

71

가장 빠른 시프트 구현을 복사하여 붙여 넣으려는 사람들을 위해 벤치 마크와 결론이 있습니다 (끝 참조). 또한 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의 세 번째 솔루션입니다.


비교해 주셔서 감사합니다. 새 어레이를 사용하지 않고이를 수행하는 가장 빠른 방법이 무엇인지 아십니까?
FiReTiTi

2
의 마지막 절에서는 함수 동작을 일관되게 유지하기 위해 대신 shift5작성 result[:] = arr하는 것이 좋습니다 result = arr.
avysk

2
이것은 답변으로 chosed한다
wyx

@avysk 주석은 매우 중요합니다. shift5 메서드를 업데이트하십시오. 때때로 복사본을 반환하고 때때로 참조를 반환하는 함수는 지옥으로가는 경로입니다.
David

2
@ Josmoor98 그 이유는 type(np.NAN) is float. 이러한 함수를 사용하여 정수 배열을 이동하는 경우 정수 fill_value를 지정해야합니다.
gzc

9

원하는 것을 수행하는 단일 기능은 없습니다. 변화에 대한 정의는 대부분의 사람들이하는 것과 약간 다릅니다. 배열을 이동하는 방법은 더 일반적으로 반복됩니다.

>>>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을 실행 한 후 제공 한 코드 shift2는 arr이 양수일 때 14 번, 음수 일 때 16 번 호출하는 동안 42 번의 함수 호출을 수행하는 것을 발견했습니다 . 실제 데이터로 각각의 성능을 확인하기 위해 타이밍을 실험 할 것입니다.


1
봐 주셔서 감사합니다. 나는 알고있다 np.roll(); 내 질문의 링크에서 기술을 사용했습니다. 구현과 관련하여 함수가 음의 시프트 값에 대해 작동하도록 할 수 있습니까?
chrisaycock

흥미롭게도 np.concatenate()np.r_[]. 전자는 np.roll()결국 사용하는 것입니다.
chrisaycock

6

벤치 마크 및 Numba 소개

1. 요약

  • 허용되는 대답 ( scipy.ndimage.interpolation.shift) 은이 페이지에 나열된 가장 느린 솔루션입니다.
  • Numba (@ numba.njit)는 어레이 크기가 ~ 25.000보다 작을 때 약간의 성능 향상을 제공합니다.
  • "모든 방법"은 어레이 크기가 클 때 (> 250.000) 똑같이 좋습니다.
  • 가장 빠른 옵션은 실제로
        (1) 배열의 길이
        (2) 수행해야하는 이동량 에 따라 다릅니다 .
  • 아래는 상수 시프트 = 10을 사용하여이 페이지 (2020-07-11)에 나열된 모든 다른 방법의 타이밍 그림입니다. 보시다시피 작은 배열 크기로 일부 방법은 시간보다 + 2000 % 이상 사용됩니다. 최선의 방법.

상대 타이밍, 상수 시프트 (10), 모든 방법

2. 최상의 옵션이있는 상세한 벤치 마크

  • shift4_numba좋은 모든 것을 원한다면 (아래 정의)를 선택하십시오.

상대적 타이밍, 최상의 방법 (벤치 마크)

3. 코드

3.1 shift4_numba

  • 좋은 만능; 최대 20 % wrt. 어떤 배열 크기로도 최상의 방법으로
  • 중간 배열 크기의 최상의 방법 : ~ 500 <N <20.000.
  • 주의 사항 : Numba jit (just in time 컴파일러)은 데코 레이팅 된 함수를 두 번 이상 호출하는 경우에만 성능을 향상시킵니다. 첫 번째 호출은 일반적으로 후속 호출보다 3-4 배 더 오래 걸립니다.
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

  • 작은 (N <= 300 .. 1500) 어레이 크기에 가장 적합한 옵션입니다. 임계 값은 필요한 이동량에 따라 다릅니다.
  • 모든 어레이 크기에서 우수한 성능; 가장 빠른 솔루션에 비해 최대 + 50 %.
  • 주의 사항 : Numba jit (just in time 컴파일러)은 데코 레이팅 된 함수를 두 번 이상 호출하는 경우에만 성능을 향상시킵니다. 첫 번째 호출은 일반적으로 후속 호출보다 3-4 배 더 오래 걸립니다.
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

  • 배열 크기가 ~ 20.000 <N <250.000 인 최상의 방법
  • 와 동일하게 shift5_numba@ numba.njit 데코레이터를 제거하십시오.

4 부록

4.1 사용 방법에 대한 세부 정보

  • shift_scipy: scipy.ndimage.interpolation.shift(scipy 1.4.1)- 가장 느린 대안 인 수락 된 답변의 옵션입니다 .
  • shift1: np.rollout[:num] xnp.nan에 의해 IronManMark20 & GZC
  • shift2: np.rollnp.put에 의해 IronManMark20
  • shift3: np.padslice에 의해 GZC
  • shift4: np.concatenateand np.fullby chrisaycock
  • shift5: 두 번 사용 result[slice] = x하여 chrisaycock
  • shift#_numba: @ numba .njit 장식 버전 이전.

shift2shift3포함 기능 현재 numba (0.50.1)에 의해 지원되지 않았 음.

4.2 기타 테스트 결과

4.2.1 상대 타이밍, 모든 방법

4.2.2 원시 타이밍, 모든 방법

4.2.3 원시 타이밍, 최상의 방법은 거의 없음


5

당신은 변환 할 수 있습니다 ndarraySeries또는 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.])

좋아요, 많은 사람들이 numpy와 함께 pandas를 사용하고 있으며 이것은 매우 유용합니다!
VanDavv

4

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를 사용하는 것은 Scipy보다 약 8 배 더 빠릅니다.


2
가장 빠른 방법은 질문 끝에 게시 한 사전 할당입니다. 당신의 Series기술은 내 컴퓨터에서 146 명을 차지했지만 내 접근 방식은 4 명 미만이었습니다.
chrisaycock 19

0

numpy에서 한 줄짜리를 원하고 성능에 너무 신경 쓰지 않는다면 다음을 시도하십시오.

np.sum(np.diag(the_array,1),0)[:-1]

설명 : np.diag(the_array,1)대각선에서 일회성 배열로 행렬을 만들고 np.sum(...,0)행렬을 열 단위로 합한 ...[:-1]다음 원래 배열의 크기에 해당하는 요소를 가져옵니다. 주변에 재생 1:-1매개 변수는 당신을 줄 수있는 서로 다른 방향으로 이동한다.


-2

코드를 케이스에 흘리지 않고 수행하는 한 가지 방법

어레이 포함 :

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

이것은 깨끗하지도 빠르지도 않습니다.
chrisaycock
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.