6.6. インデックスとスライス

ndarray には 4 つの方法でアクセスします。単一インデックス、スライス、ブールマスク、そしてそれぞれの代入形式です。

6.6.1. 単一の要素

角括弧によるインデックスは、指定された位置の値を返します:

a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1])      # 0 9
print(a[1], a[-2])      # 1 8

負のインデックスは、Python の list と同様に末尾から数えます。範囲外のインデックスは IndexError を送出します。

高ランク配列では、各軸がインデックスを取ります。インデックスは 1 組の括弧の中にカンマで区切って入れます:

m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1])          # 4
print(m[2, 0])          # 6

軸の数より少ないインデックスが与えられた場合、インデックス指定されていない軸はそのまま残ります。結果はソースの ランクが減少したビュー になります:

print(m[0])             # the first row, as a 1-D view of m

6.6.2. スライス

スライス start:stop:step は配列の ビュー を返します。ビューはソースと基盤データバッファを共有し、ビューを介した書き込みはソースに書き込まれます:

a = np.arange(10, dtype=np.uint8)
v = a[::2]              # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

独立したバッファが必要なときは、copy() が明示的にそれを生成します。

スライスは自然に高次元へ拡張されます。各軸が独自のスライスを取ります:

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]], dtype=np.uint8)

m[0]            # first row
m[0, :2]        # first two elements of row 0
m[:, 0]         # column 0 (still 2-D in ulab)
m[-1]           # last row
m[::2, ::2]     # every other row, every other column

整数(単一インデックス、軸を削除する)とスライス(軸を保持する)を混在させることは許可されており、単一行・単一列へのアクセスは通常このように記述します。

6.6.3. ブールマスク

ソースと同じ形状のブール配列は、マスクが True である要素を選択します。ブールインデックスは現在 1 次元配列で動作します。高ランクの入力は NotImplementedError を送出します:

a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])

出力:

array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)

マスクは通常の bool ndarray なので、それを生成する任意の式が機能します:

b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])

ブールインデックスは コピー を返します。選択された要素はマスクが True である任意の位置に存在し、ソースを規則的なストライドでたどった位置ではありません。そのため、ビューがそれらをアドレス指定するために使えるディスクリプタが存在せず、結果は独自のバッファへ実体化されます。

6.6.4. 整数配列インデックス

インデックスのリストまたは配列を括弧内に渡すと、それらの要素を一度で取り出します:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)

結果は コピー です。取り出された要素はもはやソースとストレージを共有しません。同じ形式は代入の左辺でも機能します:

a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)

take()選択と並べ替え で扱っています)は同じ操作の関数形式で、ストリーミングループでアロケーションなしに使うための out= キーワードを受け付けます。

6.6.5. スライス代入

スライスとマスクは、右辺と同様に代入の 左辺 にも現れます。右辺はスカラー、別の配列、またはビューのいずれでもかまいません:

m = np.zeros((3, 3), dtype=np.uint8)
m[0]      = 1            # whole row 0 set to 1
m[:, 2]   = 3            # whole column 2 set to 3
m[1, 1:3] = [7, 8]       # row 1, columns 1 and 2

左辺のブールマスクは、条件を満たす要素を置き換えます:

a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)

a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)

6.6.6. カメラ上でスライス代入が重要な理由

スライス代入はすでに存在する配列を介して書き込みます。新しい配列はアロケートされません。それが次の違いです:

out = a + b              # makes a new array the size of a
out = out * 2            # makes another new array

と:

out[:] = a               # writes into the existing out
out   += b               # in place
out   *= 2               # in place

最初のバージョンはカメラに新しい配列 2 つ分の RAM を要求します。2 番目のバージョンは何も要求しません。RAM が限られたマイクロコントローラでは、この違いが、快適に動作するスクリプトとメモリ不足になるスクリプトとの違いになることがしばしばあります。

パフォーマンス でこのパターンを詳しく扱っています。今のところの重要な規則は、スライス代入、インプレース算術演算子(+=*= など)、およびユニバーサル関数の out= キーワードの 3 つが、アロケーションなしの更新を可能にするツールであるということです。