web-dev-qa-db-ja.com

NumPy 2D配列のスライス、またはnxn配列(n> m)からmxm部分行列を抽出するにはどうすればよいですか?

NumPy nxn配列をスライスしたい。私はその配列のm行と列の任意選択を抽出したい(つまり、行/列の数にパターンがない)新しい、mxm配列。この例では、配列が4x4で、そこから2x2配列を抽出したいとします。

配列は次のとおりです。

from numpy import *
x = range(16)
x = reshape(x,(4,4))

print x
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

削除する行と列は同じです。最も簡単なケースは、先頭または末尾にある2x2サブマトリックスを抽出する場合です。

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

しかし、行/列の別の混合物を削除する必要がある場合はどうなりますか?最初と3番目の行/行を削除して、サブマトリックス[[5,7],[13,15]]を抽出する必要がある場合はどうなりますか?行/行の任意の構成が可能です。行と列の両方のインデックスの配列/リストを使用して配列にインデックスを付ける必要があることをどこかで読みましたが、うまくいかないようです:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

私は1つの方法を見つけました:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

これに関する最初の問題は、私がそれと一緒に生きることができるけれども、それがほとんど読めないということです。誰かがより良い解決策を持っているなら、私は確かにそれを聞きたいです。

他のことは、私が読むことです フォーラムで 配列で配列にインデックスを付けると、NumPyが目的の配列のコピーを作成することを強制するため、大きな配列で処理する場合、これが問題になる可能性があります。なぜそうなのか/このメカニズムはどのように機能するのか?

156
levesque

Svenが述べたように、x[[[0],[2]],[1,3]]は1列と3列に一致する0行と2行を返し、x[[0,2],[1,3]]は配列の値x [0,1]とx [2,3]を返します。

最初に挙げた例を実行するのに役立つ関数numpy.ix_があります。 x[numpy.ix_([0,2],[1,3])]を使用して、最初の例と同じことができます。これにより、余分な角かっこをすべて入力する必要がなくなります。

54
Justin Peel

この質問に答えるには、Numpyで多次元配列のインデックスがどのように機能するかを調べる必要があります。最初に、質問からのx配列があるとしましょう。 xに割り当てられたバッファには、0から15までの16個の昇順整数が含まれます。たとえば、x[i,j]の1つの要素にアクセスする場合、NumPyはバッファの先頭に対するこの要素のメモリ位置を把握する必要があります。これは、実際にi*x.shape[1]+jを計算することで実行されます(実際のメモリオフセットを取得するには、intのサイズで乗算します)。

y = x[0:2,0:2]のような基本的なスライシングによってサブ配列を抽出すると、結果のオブジェクトは、xと基本バッファーを共有します。しかし、y[i,j]にアクセスするとどうなりますか? NumPyはi*y.shape[1]+jを使用して配列へのオフセットを計算できません。これは、yに属するデータがメモリ内で連続していないためです。

NumPyはstridesを導入することでこの問題を解決します。 x[i,j]にアクセスするためのメモリオフセットを計算する場合、実際に計算されるのはi*x.strides[0]+j*x.strides[1]です(これには既にintのサイズの係数が含まれています)。

x.strides
(16, 4)

yが上記のように抽出されると、NumPyは新しいバッファーを作成しませんが、does同じバッファーを参照する新しい配列オブジェクトを作成します(そうでない場合はyがちょうどxと等しくなります。)新しい配列オブジェクトは、xとは異なる形状を持ち、バッファーへの開始オフセットが異なる場合がありますが、xとストライドを共有します(少なくともこの場合)。

y.shape
(2,2)
y.strides
(16, 4)

このようにして、y[i,j]のメモリオフセットを計算すると、正しい結果が得られます。

しかし、NumPyはz=x[[1,3]]のようなものに対して何をすべきでしょうか?元のバッファがzに使用されている場合、ストライドメカニズムは正しいインデックス付けを許可しません。 NumPyは理論的にはcouldストライドよりも洗練されたメカニズムを追加しますが、これにより要素アクセスが比較的高価になり、配列の考え全体に何らかの形で反抗します。さらに、ビューはもはや本当に軽量なオブジェクトではなくなります。

これについては、 インデックス作成に関するNumPyのドキュメント で詳しく説明しています。

ああ、そしてあなたの実際の質問をほとんど忘れていました:複数のリストを使ったインデックス作成が期待通りに機能するようにする方法は次のとおりです。

x[[[1],[3]],[1,3]]

これは、インデックス配列が broadcasted で共通の形状になっているためです。もちろん、この特定の例では、基本的なスライスで間に合わせることもできます。

x[1::2, 1::2]
107
Sven Marnach

x[[1,3]][:,[1,3]]はほとんど読めないと思います。意図をより明確にしたい場合は、次のことができます。

a[[1,3],:][:,[1,3]]

私はスライスの専門家ではありませんが、通常、配列にスライスしようとして値が連続している場合、ストライド値が変更されたビューに戻ります。

例えば入力33および34では、2x2配列を取得しますが、ストライドは4です。したがって、次の行にインデックスを付けると、ポインターはメモリ内の正しい位置に移動します。

明らかに、このメカニズムはインデックスの配列の場合にはうまくいきません。したがって、numpyはコピーを作成する必要があります。結局のところ、他の多くの行列演算関数は、サイズ、ストライド、および連続メモリ割り当てに依存しています。

12
Dat Chu

他のすべての行とすべての列をスキップしたい場合は、基本的なスライスでそれを行うことができます:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

これは、配列のコピーではなく、ビューを返します。

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

z=x[(1,3),:][:,(1,3)]は高度なインデックスを使用するため、コピーを返します。

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

xは変更されていないことに注意してください。

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

任意の行と列を選択する場合、基本的なスライスは使用できません。 x[rows,:][:,columns]のような、rowscolumnsがシーケンスである高度なインデックスを使用する必要があります。もちろん、これは元の配列のビューではなくコピーを提供します。 numpy配列は連続メモリ(一定のストライド)を使用し、任意の行と列を持つビューを生成する方法がないため(これは非一定のストライドを必要とするため)、これは予想されるとおりです。

10
unutbu

Numpyを使用すると、インデックスの各コンポーネントにスライスを渡すことができます。したがって、上記のx[0:2,0:2]の例は機能します。

列または行を均等にスキップするだけの場合は、3つのコンポーネント(つまり、開始、停止、ステップ)でスライスを渡すことができます。

繰り返しますが、上の例の場合:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

基本的には、最初の次元のスライス、インデックス1から開始し、インデックスが4以上の場合は停止し、各パスのインデックスに2を追加します。 2番目の次元についても同じです。繰り返しますが、これは一定のステップに対してのみ機能します。

内部的にまったく異なることをするようになった構文-x[[1,3]][:,[1,3]]が実際に行うことは、元の配列(x[[1,3]]部分で行われます)から行1と3のみを含む新しい配列を作成し、それを再スライスして-3番目の配列を作成します-前の配列の列1と3のみが含まれます。

5
jsbueno

同様の質問がここにあります: 最もpythonianな方法でndarrayのサブndarrayで書く。 Python 2

あなたのケースの以前の投稿のソリューションに従って、ソリューションは次のようになります。

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

Ix_を使用:

x[np.ix_(rows_to_keep, columns_to_keep)] 

どちらですか:

array([[ 5,  7],
       [13, 15]])
3
Rafael Valero

これがどれほど効率的かはわかりませんが、range()を使用して両方の軸でスライスできます

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
0
Valery Marcel