programing

Numpy 배열에서 요소 이동

oldcodes 2023. 8. 7. 23:07
반응형

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])

어제 fastrolling_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

위의 제안이 답입니다.미리 할당된 어레이를 사용합니다.

numpy가 아니라 Scipy는 당신이 원하는 시프트 기능을 정확히 제공합니다.

import numpy as np
from scipy.ndimage 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의 세 번째 솔루션입니다.

벤치마크 및 Numba 소개

요약

  • 인정된 답변(scipy.ndimage.interpolation.shift)는 이 페이지에 나열된 솔루션 중 가장 느린 솔루션입니다.
  • Numba(@numba.njit)는 어레이 크기가 ~25.000보다 작을 때 성능을 약간 향상시킵니다.
  • 어레이 크기가 큰 경우(250.000보다 큰 경우) "모든 방법"이 동일하게 좋습니다.
  • 가장 빠른 옵션은 다음과 같습니다.

    필요한 이동량입니다.
  • 아래는 이 페이지(2020-07-11)에 나열된 모든 다양한 방법의 타이밍 그림이며, 상수 시프트 = 10을 사용합니다.보다시피 어레이 크기가 작은 일부 방법은 최적의 방법보다 +2000% 이상의 시간을 사용합니다.

Relative timings, constant shift (10), all methods

최상의 옵션이 포함된 세부 벤치마크

  • 선택하세요.shift4_numba(아래 정의) 좋은 올라운드를 원하는 경우

Relative timings, best methods (Benchmarks)

코드

3.1 shift4_numba

  • 우수한 만능 재료, 어레이 크기에 상관없이 최대 20% 쓰기 가능
  • 중간 크기의 어레이에서 최적의 방법: ~ 500 < N < 20.000.
  • 주의: Numbajit (just in time 컴파일러)는 장식된 함수를 두 번 이상 호출하는 경우에만 성능을 향상시킵니다.첫 번째 통화는 일반적으로 후속 통화보다 3-4배 더 오래 걸립니다.미리 컴파일된 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

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

  • 어레이 크기 ~ 20.000 < N < 250.000인 최적의 방법
  • 과 동일shift5_numba@numba.njit 장식기를 제거하기만 하면 됩니다.

4 부록

4.1 사용방법에 대한 세부사항

  • shift_scipy:scipy.ndimage.interpolation.shift(scip 1.4.1) - 승인된 답변의 옵션으로, 가장 느린 대안임이 분명합니다.
  • shift1:np.roll그리고.out[:num] xnp.nan아이언맨 마크 20 & gzc에 의해
  • shift2:np.roll그리고.np.put아이언맨 마크20에 의해
  • shift3:np.pad그리고.slicegzc 단위로
  • shift4:np.concatenate그리고.np.full틀림없이
  • shift5하기 2회 용사result[slice] = x틀림없이
  • shift#_numba@numba.njit 장식된 이전 버전.

shift2그리고.shift3에는 현재 numba(0.50.1)에서 지원하지 않는 함수가 포함되어 있습니다.

4.2 기타 시험결과

4.2.1 상대적 시간, 모든 방법

4.2.2 원재료명, 모든 방법

4.2.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개의 함수 호출을 하는 것을 발견했습니다.shift2arr이 양성일 때 14번, 음성일 때 16번 통화했습니다. 저는 각각이 실제 데이터로 어떻게 수행되는지 보기 위해 타이밍을 실험할 것입니다.

변환할 수 있습니다.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.])

판다와 함께 할 수도 있습니다.

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)

이 예에서, 판다를 사용하는 것은 Scipy보다 약 8배 더 빠릅니다.

numpy의 원라이너를 원하지만 성능에 대해 크게 걱정하지 않는다면 다음을 시도해 보십시오.

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

설명:np.diag(the_array,1)대각선에서 한 번에 배열을 사용하여 행렬을 만듭니다.np.sum(...,0)행렬을 열 단위로 합합니다....[:-1]원래 배열의 크기에 해당하는 요소를 사용합니다.컴퓨터를 가지고 노는 것.1그리고.:-1매개 변수가 다른 방향으로 이동할 수 있기 때문입니다.

판다 라이브러리와 같은 numba 및 음의 이동 값을 지원하는 간단하고 효과적인 방법.인수에서 원래 배열이 손상되는 것을 방지하고 정수 배열과 함께 작동합니다.

import numpy as np
from numba import njit

@njit
def numba_shift(arr_: np.ndarray, shift: np.int32 = 1) -> np.ndarray:
    arr = arr_.copy().astype(np.float64)
    if shift > 0:
        arr[shift:] = arr[:-shift]
        arr[:shift] = np.nan
    else:
        arr[:shift] = arr[-shift:]
        arr[shift:] = np.nan
    return arr

:

ar = np.array([1,2,3,4,5,6])
numba_shift(ar,-1)

    array([ 2.,  3.,  4.,  5.,  6., nan])

시간:

%timeit numba_shift(ar,-1)

1.02 µs ± 9.42 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

참고: numba가 필요하지 않으면 numpy만 사용하여 라인을 삭제합니다.@njit그리고 numba 가져오기.

아마도요.np.roll당신에게 필요한 것입니다.

arr = np.arange(10)
shift = 2  # shift length
arr_1 = np.roll(arr, shift=shift)
arr_1[:shift] = np.nan

내 해결책은 다음과 같습니다.np.roll및 마스킹된 배열:

import numpy as np
import numpy.ma as ma # this is for masked array

def shift(arr, shift):
    r_arr = np.roll(arr, shift=shift)
    m_arr = ma.masked_array(r_arr,dtype=float)
    if shift > 0: m_arr[:shift] = ma.masked
    else: m_arr[shift:] = ma.masked
    return m_arr.filled(np.nan)

기본적으로, 저는 그냥.np.roll배열을 이동하려면 다음을 사용합니다.ma.masked_array불필요한 요소를 유효하지 않은 것으로 표시하고 유효하지 않은 위치를 다음으로 채웁니다.np.nan설정합니다.dtypefloat으로 채워지도록.np.nan아무 문제도 일으키지 않을 것입니다.

In [11]: shift(arr, 3)
Out[11]: array([nan, nan, nan,  0.,  1.,  2.,  3.,  4.,  5.,  6.])

In [12]: shift(arr, -3)
Out[12]: array([ 3.,  4.,  5.,  6.,  7.,  8.,  9., nan, nan, nan])

대답의 .shift5)는배열을

def shift(array, offset, constant_values=0):
  """Returns copy of array shifted by offset, with fill using constant."""
  array = np.asarray(array)
  offset = np.atleast_1d(offset)
  assert len(offset) == array.ndim
  new_array = np.empty_like(array)

  def slice1(o):
    return slice(o, None) if o >= 0 else slice(0, o)

  new_array[tuple(slice1(o) for o in offset)] = (
      array[tuple(slice1(-o) for o in offset)])

  for axis, o in enumerate(offset):
    new_array[(slice(None),) * axis +
              (slice(0, o) if o >= 0 else slice(o, None),)] = constant_values

  return new_array

여기에 numpy의 특별한 내장 함수를 사용하지 않고 numba와 호환되는 2차원 솔루션이 있습니다.

def shift(array, dy, dx):
    n, m = array.shape[:2]
    e = np.zeros((n, m))
    if dy > 0 and dx > 0:
        e[dy:, dx:] = array[:-dy, :-dx]
        return e
    elif dy > 0 and dx < 0:
        e[dy:, :dx] = array[:-dy, -dx:]
        return e
    elif dy < 0 and dx > 0:
        e[:dy, dx:] = array[-dy:, :-dx]
        return e
    elif dy < 0 and dx < 0:
        e[:dy, :dx] = array[-dy:, -dx:]
        return e
    elif dy < 0 and dx == 0:
        e[:dy, :] = array[-dy:, :]
        return e
    elif dy > 0 and dx == 0:
        e[dy:, :] = array[:-dy, :]
        return e
    elif dy == 0 and dx < 0:
        e[:, :dx] = array[:, -dx:]
        return e
    elif dy == 0 and dx > 0:
        e[:, dx:] = array[:, :-dx]
        return e

더 빠른 솔루션이 있습니다. @gzc의 벤치마크 솔루션에 2개의 벤치마크를 추가했습니다.

def shift6(arr, num, fill_value=np.nan):
    for _ in range(num):
        darr.appendleft(fill_value)

def shift7(arr, num, fill_value=np.nan):
    darr = deque(arr)
    for _ in range(num):
        darr.appendleft(fill_value)

darr = deque(arr)

def benchmark_shift6():
    shift6(arr, 3)
    
def benchmark_shift7():
    shift6(arr, 3)

benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5', 'benchmark_shift6', 'benchmark_shift7']

그리고 제 노트북의 출력은 제안된 다른 솔루션보다 훨씬 우수합니다.

%s time: ('benchmark_shift1', 0.08232757700170623) 
%s time: ('benchmark_shift2', 0.0934765400015749) 
%s time: ('benchmark_shift3', 0.14349375600431813) 
%s time: ('benchmark_shift4', 0.03575193700089585) 
%s time: ('benchmark_shift5', 0.01389261399890529) 
%s time: ('benchmark_shift6', 0.0025887360025080852) 
%s time: ('benchmark_shift7', 0.0024806019937386736)

코드를 사례에 쏟지 않고 수행하는 한 가지 방법

배열 포함:

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

언급URL : https://stackoverflow.com/questions/30399534/shift-elements-in-a-numpy-array

반응형