NumPy配列ndarrayの要素・行・列を取得(抽出)、代入
NumPy配列ndarray
の要素の値や行・列などの部分配列を取得(抽出)したり、選択範囲に新たな値・配列を代入する方法について説明する。
公式ドキュメントの該当部分は以下。
ここでは以下の内容について説明する。
- 配列
ndarray
の要素や部分配列(行・列など)の選択の基本 - 整数値(インデックス)で指定
- スライスで指定
- bool値のリストで指定(ブーリアンインデックス)
- 整数値のリストで指定(ファンシーインデックス)
- 異なる指定方法の組み合わせ
- 選択範囲に新たな値・配列を代入
- ビュー(参照)とコピー
要素・部分配列(行・列など)の削除、連結、追加については以下の記事を参照。
- 関連記事: NumPyで任意の行・列を削除するnp.deleteの使い方
- 関連記事: NumPy配列ndarrayを結合(concatenate, stack, blockなど)
- 関連記事: NumPy配列ndarrayに要素・行・列を挿入、追加するinsertの使い方
配列ndarrayの要素や部分配列(行・列など)の選択の基本
NumPy配列ndarray
の要素や部分配列(行・列など)は[○, ○, ○, ...]
のように各次元の位置や範囲をカンマ区切りで指定する。
import numpy as np
a_1d = np.arange(10)
print(a_1d)
# [0 1 2 3 4 5 6 7 8 9]
print(a_1d[3])
# 3
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[0, 2])
# 2
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
print(a_3d[0, 1, 2])
# 6
各次元の位置は、以下のように様々な形式で指定できる。
print(a_3d[1, [True, False, True], ::2])
# [[12 14]
# [20 22]]
以下、位置・範囲の指定方法について説明する。
整数値(インデックス)で指定
単独の位置(インデックス)は整数int
のスカラー値で指定する。
0
始まりで、負の値を使うと末尾から指定できる(-1
が一番最後)。存在しない位置を指定するとエラーとなる。
a_1d = np.arange(10)
print(a_1d)
# [0 1 2 3 4 5 6 7 8 9]
print(a_1d[0])
# 0
print(a_1d[4])
# 4
print(a_1d[-1])
# 9
print(a_1d[-4])
# 6
# print(a_1d[100])
# IndexError: index 100 is out of bounds for axis 0 with size 10
# print(a_1d[-100])
# IndexError: index -100 is out of bounds for axis 0 with size 10
多次元配列の場合も同様。次元ごとにそれぞれ位置を指定する。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[0, -1])
# 3
後ろの次元の指定は省略可能。例えば二次元配列の場合は行のみを指定して、その行を一次元配列として取得できる。
これは次に説明するように末尾のスライス:
が省略されている状態([x]
は[x, :]
と等価)。列を選択したい場合は[:, x]
とする。
print(a_2d[0])
# [0 1 2 3]
print(a_2d[0, :])
# [0 1 2 3]
print(a_2d[:, 0])
# [0 4 8]
すべての次元を整数int
で指定した場合、要素の値が返される。その型は元のndarray
のデータ型dtype
による。
後ろの次元を省略した場合は配列ndarray
が返される。
print(type(a_2d[0, -1]))
# <class 'numpy.int64'>
print(type(a_2d[0]))
# <class 'numpy.ndarray'>
スライスで指定
スライスstart:end:step
で範囲を選択できる。詳細は以下の記事も参照。
一次元配列の例。
a_1d = np.arange(10)
print(a_1d)
# [0 1 2 3 4 5 6 7 8 9]
print(a_1d[2:7])
# [2 3 4 5 6]
print(a_1d[:7])
# [0 1 2 3 4 5 6]
print(a_1d[2:])
# [2 3 4 5 6 7 8 9]
print(a_1d[2:7:2])
# [2 4 6]
二次元配列の例。末尾の:
は省略可能。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[:2, 1:3])
# [[1 2]
# [5 6]]
print(a_2d[:2, :])
# [[0 1 2 3]
# [4 5 6 7]]
print(a_2d[:2])
# [[0 1 2 3]
# [4 5 6 7]]
スライスx:x+1
で幅1
の範囲(1行や1列)を選択することもできるが、整数int
のスカラー値で指定した場合とは結果の形状shape
、次元数ndim
が異なる。
print(a_2d[1:2])
# [[4 5 6 7]]
print(a_2d[1:2].shape)
# (1, 4)
print(a_2d[1])
# [4 5 6 7]
print(a_2d[1].shape)
# (4,)
print(a_2d[:, 2:3])
# [[ 2]
# [ 6]
# [10]]
print(a_2d[:, 2:3].shape)
# (3, 1)
print(a_2d[:, 2])
# [ 2 6 10]
print(a_2d[:, 2].shape)
# (3,)
上述のように、すべての次元を整数値で指定すると配列ndarray
ではなくスカラー値が返される。
print(a_2d[1:2, 2:3])
# [[6]]
print(a_2d[1:2, 2:3].shape)
# (1, 1)
print(a_2d[1, 2])
# 6
print(type(a_2d[1, 2]))
# <class 'numpy.int64'>
このように、スライスで選択した場合は元のndarray
の次元数が保持されるが、整数int
のスカラー値で選択した場合は次元数が削られる。
三次元以上の多次元配列の場合も同様に、スライスと整数では、同じ範囲を選択しているようでも扱いが異なる。配列同士の連結などの処理では形状shape
、次元数ndim
が異なると結果が変わったりエラーになったりする場合があるので注意。
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
print(a_3d[0, 0, 0])
# 0
print(a_3d[0, 0])
# [0 1 2 3]
print(a_3d[0])
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_3d[0:1, 0:1, 0:1])
# [[[0]]]
print(a_3d[0:1, 0:1])
# [[[0 1 2 3]]]
print(a_3d[0:1])
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]]
bool値のリストで指定(ブーリアンインデックス)
各次元のサイズ(要素数)と同じサイズのbool
値(True
, False
)のリストやndarray
で指定すると、True
の位置のみが選択される。マスク処理のようなイメージ。
一次元配列の例。リストでもndarray
でもOK。サイズが異なるとエラーになる。
a_1d = np.arange(4)
print(a_1d)
# [0 1 2 3]
print(a_1d[[True, False, True, False]])
# [0 2]
print(a_1d[np.array([True, False, True, False])])
# [0 2]
# print(a_1d[[True, False]])
# IndexError: boolean index did not match indexed array along dimension 0; dimension is 4 but corresponding boolean dimension is 2
二次元配列の例。全体を示すスライス:
と合わせて任意の行・列を抽出可能。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[[True, False, True]])
# [[ 0 1 2 3]
# [ 8 9 10 11]]
print(a_2d[:, [True, False, True, False]])
# [[ 0 2]
# [ 4 6]
# [ 8 10]]
複数の次元に同時にbool
のリストを指定すると想定通りの結果にならない。np.ix_()
を使う。
print(a_2d[[True, False, True], [True, False, True, False]])
# [ 0 10]
print(a_2d[np.ix_([True, False, True], [True, False, True, False])])
# [[ 0 2]
# [ 8 10]]
幅1
の範囲(1行や1列)を選択した場合、スライスと同様、元のndarray
の次元数が保持される。
print(a_2d[:, [True, False, False, False]])
# [[0]
# [4]
# [8]]
ちなみに、ndarray
の比較演算の結果はbool
値を要素とするndarray
となる。このような元の配列と同じ形状shape
のbool
型のndarray
を[]
に指定すると、True
の要素のみが選択され、一次元に平坦化された配列が返される。
print(a_2d > 5)
# [[False False False False]
# [False False True True]
# [ True True True True]]
print(type(a_2d > 5))
# <class 'numpy.ndarray'>
print(a_2d[a_2d > 5])
# [ 6 7 8 9 10 11]
複数条件を指定することも可能。括弧()
で囲み、&
(かつ)、|
(または)を使う。否定~
も使える。and
, or
, not
だとエラーになるので注意。
print((a_2d > 5) & (a_2d < 10))
# [[False False False False]
# [False False True True]
# [ True True False False]]
print(a_2d[(a_2d > 5) & (a_2d < 10)])
# [6 7 8 9]
ブーリアンインデックスを利用すると、条件を満たす要素をカウントしたり、条件を満たす行・列を抽出したりできる。
整数値のリストで指定(ファンシーインデックス)
整数値のリストやndarray
で範囲を選択することも可能。詳細は以下の記事も参照。
一次元配列の例。
順番が前後したり重複したりしてもよい。負の値も使用可能。選択というよりも、元の配列の位置を選んで新たな配列を再構成するイメージ。リストでもndarray
でもOK。
a_1d = np.arange(4)
print(a_1d)
# [0 1 2 3]
print(a_1d[[0, 2]])
# [0 2]
print(a_1d[[0, 3, 2, 1, 2, -1, -2]])
# [0 3 2 1 2 3 2]
print(a_1d[np.array([0, 3, 2, 1, 2, -1, -2])])
# [0 3 2 1 2 3 2]
二次元配列の例。全体を示すスライス:
と合わせて任意の行・列を抽出可能。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[[0, 2]])
# [[ 0 1 2 3]
# [ 8 9 10 11]]
print(a_2d[:, [0, 2]])
# [[ 0 2]
# [ 4 6]
# [ 8 10]]
上述のブーリアンインデックスと同様に、複数の次元に同時にリストを指定すると想定通りの結果にならない。np.ix_()
を使う。
print(a_2d[[0, 2], [0, 2]])
# [ 0 10]
print(a_2d[np.ix_([0, 2], [0, 2])])
# [[ 0 2]
# [ 8 10]]
一次元の例と同様、順番が前後したり重複したりしてもよい。負の値も使用可能。
print(a_2d[np.ix_([0, 2, 1, 1, -1, -1], [0, 2, 1, 3])])
# [[ 0 2 1 3]
# [ 8 10 9 11]
# [ 4 6 5 7]
# [ 4 6 5 7]
# [ 8 10 9 11]
# [ 8 10 9 11]]
要素数1
のリストで選択した場合も、スライスやブーリアンインデックスと同様、元のndarray
の次元数が保持される。整数のスカラー値で指定した場合とは結果が異なるので注意。
print(a_2d[:, [1]])
# [[1]
# [5]
# [9]]
print(a_2d[:, [1]].shape)
# (3, 1)
print(a_2d[:, 1])
# [1 5 9]
print(a_2d[:, 1].shape)
# (3,)
異なる指定方法の組み合わせ
これまでの例にも出てきているが、次元ごとに異なる形式で指定してもOK。
二次元配列の例。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[1, 1:3])
# [5 6]
print(a_2d[1, [False, True, True, False]])
# [5 6]
print(a_2d[1, [1, 2]])
# [5 6]
print(a_2d[1:3, 1:3])
# [[ 5 6]
# [ 9 10]]
print(a_2d[1:3, [False, True, True, False]])
# [[ 5 6]
# [ 9 10]]
print(a_2d[1:3, [1, 2]])
# [[ 5 6]
# [ 9 10]]
ブール値のリスト(ブーリアンインデックス)と整数値のリスト(ファンシーインデックス)の組み合わせはnp.ix_()
を使う必要がある。
print(a_2d[np.ix_([1, 2], [False, True, True, False])])
# [[ 5 6]
# [ 9 10]]
三次元以上の多次元配列の場合、ブール値のリストまたは整数値のリストを適用するのがひとつの次元だけだと特に問題ない。
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
print(a_3d[:, :2, [True, False, True, False]])
# [[[ 0 2]
# [ 4 6]]
#
# [[12 14]
# [16 18]]]
print(a_3d[:, :2, [0, 2]])
# [[[ 0 2]
# [ 4 6]]
#
# [[12 14]
# [16 18]]]
ブール値のリストまたは整数値のリストを複数次元に適用する場合、[]
にそのまま指定してもnp.ix_()
を使ってもうまくいかない。
np.ix_()
の中で使えるのは一次元のリストやndarray
のみで、スライスが使えない。
print(a_3d[:, [0, 2], [0, 2]])
# [[ 0 10]
# [12 22]]
# print(a_3d[np.ix_(:, [0, 2], [0, 2])])
# SyntaxError: invalid syntax
スライスの代わりにrange()
を使う方法がある。全体を選択する場合はshape
属性を使うとサイズによらずに指定可能。
- 関連記事: Pythonのrange関数の使い方
- 関連記事: NumPy配列ndarrayの次元数、形状、サイズ(全要素数)を取得
print(a_3d[np.ix_(range(2), [0, 2], [0, 2])])
# [[[ 0 2]
# [ 8 10]]
#
# [[12 14]
# [20 22]]]
print(a_3d[np.ix_(range(a_3d.shape[0]), [0, 2], [0, 2])])
# [[[ 0 2]
# [ 8 10]]
#
# [[12 14]
# [20 22]]]
np.ix_()
の中ではスカラー値も使えない。要素数1
のリストやndarray
として指定する必要がある。この場合、結果の次元数は元のndarray
と同じ。
# print(a_3d[np.ix_(0, [0, 2], [0, 2])])
# ValueError: Cross index must be 1 dimensional
print(a_3d[np.ix_([0], [0, 2], [0, 2])])
# [[[ 0 2]
# [ 8 10]]]
もしかするともっといい方法があるかもしれない
選択範囲に新たな値・配列を代入
これまでの例では範囲を選択して要素や部分配列の値を取得していたが、選択範囲に新たな値や配列を代入することも可能。代入する場合は次に説明するビューとコピーの違いも要注意。
右辺にスカラー値を指定すると、左辺の選択範囲のすべての要素にそのスカラー値が代入される。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_2d[0, 0] = 100
print(a_2d)
# [[100 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_2d[0] = 100
print(a_2d)
# [[100 100 100 100]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_2d[np.ix_([False, True, True], [1, 3])] = 200
print(a_2d)
# [[100 100 100 100]
# [ 4 200 6 200]
# [ 8 200 10 200]]
右辺には配列も指定できる。
左辺の選択範囲と形状shape
が一致しているとそのまま代入される。以下の例のように、飛び飛びの場所でも問題ない。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[::2, :3])
# [[ 0 1 2]
# [ 8 9 10]]
print(np.arange(6).reshape(2, 3) * 100)
# [[ 0 100 200]
# [300 400 500]]
a_2d[::2, :3] = np.arange(6).reshape(2, 3) * 100
print(a_2d)
# [[ 0 100 200 3]
# [ 4 5 6 7]
# [300 400 500 11]]
右辺の配列と左辺の選択範囲の形状が一致していない場合、ブロードキャストして代入される。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[::2, :3])
# [[ 0 1 2]
# [ 8 9 10]]
print(np.arange(3) * 100)
# [ 0 100 200]
a_2d[::2, :3] = np.arange(3) * 100
print(a_2d)
# [[ 0 100 200 3]
# [ 4 5 6 7]
# [ 0 100 200 11]]
ブロードキャストできない形状だとエラー。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[::2, :3])
# [[ 0 1 2]
# [ 8 9 10]]
print(np.arange(2) * 100)
# [ 0 100]
# a_2d[::2, :3] = np.arange(2) * 100
# ValueError: could not broadcast input array from shape (2) into shape (2,3)
ブロードキャストについては以下の記事を参照。
- 関連記事: NumPyのブロードキャスト(形状の自動変換)
ビュー(参照)とコピー
部分配列を選択する場合、各次元の位置の指定形式によって、ビュー(参照)を返すものとコピーを返すものがある。
例えばスライスを使うとビューが返される。
2つのndarray
が同じメモリのデータを参照している(片方がもう片方のビューである)かどうかはnp.shares_memory()
で確認できる。
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_slice = a_2d[:2, :2]
print(a_slice)
# [[0 1]
# [4 5]]
print(np.shares_memory(a_2d, a_slice))
# True
ビューの場合、選択した部分配列の要素の値を変更すると元の配列の要素の値も変わる。逆に元の配列の要素の値を変更すると部分配列の要素の値も変わる。
a_slice[0, 0] = 100
print(a_slice)
# [[100 1]
# [ 4 5]]
print(a_2d)
# [[100 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_2d[0, 0] = 0
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_slice)
# [[0 1]
# [4 5]]
bool値のリストで指定するブーリアンインデックスや、整数値のリストで指定するファンシーインデックスの場合、ビューではなくコピーが返される。
a_fancy_index = a_2d[[0, 1]]
print(a_fancy_index)
# [[0 1 2 3]
# [4 5 6 7]]
print(np.shares_memory(a_2d, a_fancy_index))
# False
この場合、選択した部分配列の要素の値を変更しても元の配列はそのまま。逆も同様。
a_fancy_index[0, 0] = 100
print(a_fancy_index)
# [[100 1 2 3]
# [ 4 5 6 7]]
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
ndarray
のcopy()
メソッドを使うとコピーが生成される。スライスで選択した部分配列を元の配列とは別々に処理したい場合はcopy()
を使えばよい。
a_slice_copy = a_2d[:2, :2].copy()
print(a_slice_copy)
# [[0 1]
# [4 5]]
print(np.shares_memory(a_2d, a_slice_copy))
# False
a_slice_copy[0, 0] = 100
print(a_slice_copy)
# [[100 1]
# [ 4 5]]
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
異なる指定方法を組み合わせる場合、ブーリアンインデックスやファンシーインデックスが含まれているとコピーが返される。
a_fancy_index_slice = a_2d[[0, 1], :3]
print(a_fancy_index_slice)
# [[0 1 2]
# [4 5 6]]
print(np.shares_memory(a_2d, a_fancy_index_slice))
# False
整数のスカラー値とスライスの場合はビュー。
a_scalar_slice = a_2d[1, :3]
print(a_scalar_slice)
# [4 5 6]
print(np.shares_memory(a_2d, a_scalar_slice))
# True