NumPy配列ndarrayのスライスによる部分配列の選択と代入

Posted: | Tags: Python, NumPy

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では右辺の値がブロードキャストされて代入される。

すなわち、右辺がスカラー値であればスライスで選択された要素がすべてそのスカラー値で置き換えられ、一次元の配列であればそのまま代入される。

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

関連カテゴリー

関連記事