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
ゼロ

2
@JohnGalt[np.nan]*nはプレーンなPythonであるため、より遅くなりnp.full(n, np.nan)ます。小さいn場合ではありませんが、np.r_によってnumpy配列に変換されるため、利点が失われます。
スウェンゼル2015年

@swenzelちょうどそれを計時し、[np.nan]*nよりも速いnp.full(n, np.nan)ですn=[10,1000,10000]np.r_ヒットするかどうかを確認する必要があります。
ゼロ

速度が懸念される場合、配列サイズは最良のアルゴリズムに大きな役割を果たします(以下にベンチマーク比較を追加)。また、最近では、numba.njitを使用して、繰り返し呼び出された場合にシフトを高速化できます。
np 87

回答:


99

しつこいではありませんが、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])

23
scipyシフト機能は本当に遅いです。np.concatenateを使用して自分でロールしたところ、はるかに高速でした。
gaefan 2015年

12
numpy.rollの方が高速です。パンダもそれを使用しています。github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/…–
fx-kirin

このページにリストされている他のすべての選択肢(以下の私の回答を参照)に対してscipy.ndimage.interpolation.shift(scipy 1.4.1)をテストしたところ、これは可能な限り最も遅い解決策です。アプリケーションで速度が重要でない場合にのみ使用してください。
np 87

70

シフトの最速の実装をコピーして貼り付けたいだけの人のために、ベンチマークと結論があります(最後を参照)。さらに、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の3番目のソリューションです。


比較していただきありがとうございます。新しいアレイを使用せずにそれを行うための最速の方法は何ですか?
FiReTiTi 2017

2
の最後の節では、関数の動作の一貫性を保つために、の代わりshift5に書く方が良いです。result[:] = arrresult = arr
avysk 2017

2
これは答えとして選ばれるべきです
wyx 2018年

@avyskコメントは非常に重要です-shift5メソッドを更新してください。コピーを返すこともあれば、参照を返すこともある関数は、地獄への道です。
デビッド

2
@ Josmoor98それはtype(np.NAN) is float。これらの関数を使用して整数配列をシフトする場合は、整数のfill_valueを指定する必要があります。
gzc

8

あなたが望むことをする単一の機能はありません。あなたのシフトの定義は、ほとんどの人がしていることとは少し異なります。配列をシフトする方法は、より一般的にループされます。

>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])

ただし、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 2015年

興味深いことに、np.concatenate()よりもはるかに高速ですnp.r_[]np.roll()結局のところ、前者が使用するものです。
chrisaycock 2015年

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と一緒にパンダを使用しています、そしてこれは非常に役に立ちます!
vanDavv 2018年

5

ベンチマークとNumbaの紹介

1.まとめ

  • 受け入れられた答え(scipy.ndimage.interpolation.shift)は、このページにリストされている最も遅い解決策です。
  • Numba(@ numba.njit)は、配列サイズが約25.000より小さい場合に、パフォーマンスを向上させます。
  • 「任意の方法」は、配列サイズが大きい(> 250.000)場合にも同様に適しています。
  • 最速のオプションは、実際には
        (1)配列の長さ
        (2)実行する必要のあるシフトの量によって異なります。
  • 以下は、このページ(2020-07-11)にリストされているすべての異なるメソッドのタイミングの図です。一定のシフト= 10を使用しています。ご覧のとおり、配列サイズが小さい場合、一部のメソッドは+ 2000%以上の時間を使用します。最良の方法。

相対タイミング、一定シフト(10)、すべての方法

2.最良のオプションを備えた詳細なベンチマーク

  • shift4_numba優れたオールアラウンドが必要な場合は、(以下に定義)を選択してください

相対的なタイミング、最良の方法(ベンチマーク)

3.コード

3.1 shift4_numba

  • 良いオールアラウンド; 最大20%wrt。任意の配列サイズで最良の方法に
  • 中程度の配列サイズの最良の方法:〜500 <N <20.000。
  • 警告:Numba jit(ジャストインタイムコンパイラ)は、decorated関数を複数回呼び出している場合にのみパフォーマンスを向上させます。通常、最初の呼び出しは後続の呼び出しより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(ジャストインタイムコンパイラ)は、decorated関数を複数回呼び出している場合にのみパフォーマンスを向上させます。通常、最初の呼び出しは後続の呼び出しより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.shiftscipy 1.4.1)-受け入れられた回答からのオプション。これは明らかに最も遅い選択肢です。
  • shift1np.rollout[:num] xnp.nanすることによりIronManMark20gzc
  • shift2np.rollそしてIronManMark20np.putによって
  • shift3np.padそしてgzcsliceによって
  • shift4np.concatenateそしてchrisaycocknp.fullによって
  • shift5:2回を使用result[slice] = xすることにより chrisaycock
  • shift#_numba:@ numba.njit以前の装飾バージョン。

shift2そしてshift3現在numba(0.50.1)によってサポートされていなかった機能を含んでいました。

4.2その他のテスト結果

4.2.1相対的なタイミング、すべての方法

4.2.2生のタイミング、すべての方法

4.2.3生のタイミング、いくつかの最良の方法


4

パンダでもこれを行うことができます:

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の使用はScipyよりも約8倍高速でした


2
最速の方法は、質問の最後に投稿した事前割り当てです。あなたのSeriesテクニックは私のコンピューターで146人を要しましたが、私のアプローチは4人未満でした。
chrisaycock

0

numpyのワンライナーが必要で、パフォーマンスについてあまり気にしない場合は、次のことを試してください。

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

説明:np.diag(the_array,1)対角線から1つ離れた配列で行列を作成し、行列をnp.sum(...,0)列ごとに合計し...[:-1]、元の配列のサイズに対応する要素を取得します。遊ん1:-1パラメータはあなたに別の方向にシフトを与えることができますよう。


-2

コードをケースにこぼさずにそれを行う1つの方法

配列あり:

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.