NumPy配列ndarrayのスライスによる部分配列の選択と代入
Pythonではコロンを使って表すスライス[start:stop:step]
によって、リストや文字列、タプルなどのシーケンスオブジェクトの一部分を選択して取得したり別の値を代入したりできる。
NumPy配列ndarrayに対してもスライスで部分配列を選択して抽出したり別の値を代入したりすることが可能。
以下の内容について説明する。
- スライスの基本
- 一次元のNumPy配列ndarrayにおけるスライス
- 多次元のNumPy配列ndarrayにおけるスライス
- ビュー(参照)とコピー
- ファンシーインデックス(リストによる選択)との組み合わせ
NumPy配列ndarrayの要素や部分配列を選択する方法についてのまとめは以下の記事を参照。
また、条件を満たす行・列を抽出したい場合は以下の記事を参照。
スライスの基本
Pythonではコロンを使って表すスライス[start:stop:step]
によって、リストや文字列、タプルなどのシーケンスオブジェクトの一部分を選択して取得したり別の値を代入したりできる。
import numpy as np
l = list(range(10))
print(l)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(l[4:8])
# [4, 5, 6, 7]
print(l[-5:-2])
# [5, 6, 7]
print(l[::-1])
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
スライスはPython標準の機能。詳細は以下の記事を参照。
一次元のNumPy配列ndarrayにおけるスライス
選択
一次元のNumPy配列numpy.ndarray
をスライスで選択する場合は、上述のPythonの基本的なスライスと同じ。
a = np.arange(10)
print(a)
# [0 1 2 3 4 5 6 7 8 9]
print(a[4:8])
# [4 5 6 7]
print(a[-5:-2])
# [5 6 7]
print(a[::-1])
# [9 8 7 6 5 4 3 2 1 0]
代入
スライスを使った代入の振る舞いはPythonのリスト(list
型)とNumPy配列numpy.ndarray
で異なる。
Pythonのリストに対するスライスでの代入は以下の記事を参照。スライスで選択した要素数と代入する要素数は一致していなくてもOK。
numpy.ndarray
では右辺の値がブロードキャストされて代入される。
- 関連記事: NumPyのブロードキャスト(形状の自動変換)
すなわち、右辺がスカラー値であればスライスで選択された要素がすべてそのスカラー値で置き換えられ、一次元の配列であればそのまま代入される。
a[3:6] = 100
print(a)
# [ 0 1 2 100 100 100 6 7 8 9]
a[3:6] = [100, 200, 300]
print(a)
# [ 0 1 2 100 200 300 6 7 8 9]
配列を代入する場合は代入する配列の要素数とスライスで選択された要素数が一致していないとエラーValueError
になるので注意。
# a[3:6] = [100, 200, 300, 400]
# ValueError: cannot copy sequence with size 4 to array axis with dimension 3
step
を指定したスライスによる飛び飛びの値への代入でも同様。
a = np.arange(10)
print(a)
# [0 1 2 3 4 5 6 7 8 9]
print(a[2:8:2])
# [2 4 6]
a[2:8:2] = 100
print(a)
# [ 0 1 100 3 100 5 100 7 8 9]
a[2:8:2] = [100, 200, 300]
print(a)
# [ 0 1 100 3 200 5 300 7 8 9]
多次元のNumPy配列ndarrayにおけるスライス
多次元のNumPy配列ndarrayに対しては各次元のスライスをカンマで区切って指定できる。
以下の二次元配列を例とする。
a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
選択
各次元のスライスをカンマで区切って指定。
print(a[1:, 1:3])
# [[ 5 6]
# [ 9 10]]
行の選択
全体を表すスライス:
を使うと行を選択できる。この場合、後ろの, :
は省略できる。
print(a[1:, :])
# [[ 4 5 6 7]
# [ 8 9 10 11]]
print(a[1:])
# [[ 4 5 6 7]
# [ 8 9 10 11]]
1行を選択する場合、スライスではなくスカラー値でインデックスを指定すると一次元配列となるが、スライスで1行分を選択すると元の配列と同じく二次元配列となる。
print(a[1])
# [4 5 6 7]
print(a[1].shape)
# (4,)
print(a[1:2])
# [[4 5 6 7]]
print(a[1:2].shape)
# (1, 4)
行列演算など形状が重要な場合は注意。
列の選択
列の選択も同様。この場合、はじめの:
は省略できない。
print(a[:, 1:3])
# [[ 1 2]
# [ 5 6]
# [ 9 10]]
行と同様に、1列を選択する場合、スライスではなくスカラー値でインデックスを指定すると一次元配列となるが、スライスで1列分を選択すると元の配列と同じく二次元配列となる。
print(a[:, 1])
# [1 5 9]
print(a[:, 1].shape)
# (3,)
print(a[:, 1:2])
# [[1]
# [5]
# [9]]
print(a[:, 1:2].shape)
# (3, 1)
複数の:
が繰り返す場合は...
を使うことができる。以下の記事を参照。
代入
多次元のNumPy配列ndarrayに対する代入も一次元の場合と同様に右辺の値がブロードキャストされて代入される。
配列を代入する場合は代入する配列の要素数とスライスで選択された領域の対応する要素数が一致していないとエラーValueError
になるので注意。
a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a[1:, 1:3])
# [[ 5 6]
# [ 9 10]]
a[1:, 1:3] = 100
print(a)
# [[ 0 1 2 3]
# [ 4 100 100 7]
# [ 8 100 100 11]]
a[1:, 1:3] = [100, 200]
print(a)
# [[ 0 1 2 3]
# [ 4 100 200 7]
# [ 8 100 200 11]]
a[1:, 1:3] = [[100, 200], [300, 400]]
print(a)
# [[ 0 1 2 3]
# [ 4 100 200 7]
# [ 8 300 400 11]]
step
を指定したスライスによる飛び飛びの値への代入でも同様。
a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a[1:, ::2])
# [[ 4 6]
# [ 8 10]]
a[1:, ::2] = 100
print(a)
# [[ 0 1 2 3]
# [100 5 100 7]
# [100 9 100 11]]
a[1:, ::2] = [100, 200]
print(a)
# [[ 0 1 2 3]
# [100 5 200 7]
# [100 9 200 11]]
a[1:, ::2] = [[100, 200], [300, 400]]
print(a)
# [[ 0 1 2 3]
# [100 5 200 7]
# [300 9 400 11]]
ビュー(参照)とコピー
スライスで抽出した部分配列は元の配列のビュー(参照)であり、部分配列の要素を変更すると元の配列の要素も変更される。
a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_slice = a[1:, 1:3]
print(a_slice)
# [[ 5 6]
# [ 9 10]]
a_slice[0, 0] = 100
print(a_slice)
# [[100 6]
# [ 9 10]]
print(a)
# [[ 0 1 2 3]
# [ 4 100 6 7]
# [ 8 9 10 11]]
スライスで抽出した部分配列のコピーを作成したい場合はcopy()
メソッドを使う。コピーの要素を変更しても元の配列の要素は変更されない。
a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_slice_copy = a[1:, 1:3].copy()
print(a_slice_copy)
# [[ 5 6]
# [ 9 10]]
a_slice_copy[0, 0] = 100
print(a_slice_copy)
# [[100 6]
# [ 9 10]]
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
numpy.ndarray
におけるビューとコピーについては以下の記事を参照。
ファンシーインデックス(リストによる選択)との組み合わせ
NumPyにはインデックスのリストによってnumpy.ndarray
から部分配列を選択するファンシーインデックスという仕組みがある。
これとスライスを組み合わせて部分配列を選択し取得できる。
a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a[[0, 2], 1:3])
# [[ 1 2]
# [ 9 10]]
代入も同様。
a[[0, 2], 1:3] = 100
print(a)
# [[ 0 100 100 3]
# [ 4 5 6 7]
# [ 8 100 100 11]]
a[[0, 2], 1:3] = [100, 200]
print(a)
# [[ 0 100 200 3]
# [ 4 5 6 7]
# [ 8 100 200 11]]
a[[0, 2], 1:3] = [[100, 200], [300, 400]]
print(a)
# [[ 0 100 200 3]
# [ 4 5 6 7]
# [ 8 300 400 11]]
ファンシーインデックスで抽出した部分配列はビューではなくコピーとなるので注意。
a_subset = a[[0, 2], 1:3]
print(a_subset)
# [[100 200]
# [300 400]]
a_subset[0, 0] = -1
print(a_subset)
# [[ -1 200]
# [300 400]]
print(a)
# [[ 0 100 200 3]
# [ 4 5 6 7]
# [ 8 300 400 11]]