NumPyのファンシーインデックス(リストによる選択と代入)

NumPyにはインデックスのリストによってNumPy配列ndarrayの部分配列を選択するファンシーインデックスという仕組みがあります。選択した部分配列を抽出したり新たな値を代入したりできます。 例えば二次元配列に対しては任意の位置の行や列を選択できて便利だが、行・列両方を指定したい場合は注意が必要。 以下の内容について説明します。

一次元のNumPy配列ndarrayのファンシーインデックス 多次元のNumPy配列ndarrayのファンシーインデックス 行の選択 列の選択 そのほかの部分配列の選択

ファンシーインデックスを使った代入 ビュー(参照)とコピー スライスとの組み合わせ

ファンシーインデックスを利用した行や列の並べ替えについては以下の記事を参照。

リストではなくスライスで部分配列を選択したい場合は以下の記事を参照。

また、条件を満たす行・列を抽出したい場合は以下の記事を参照。

一次元のNumPy配列ndarrayのファンシーインデックス

以下の一次元のNumPy配列ndarrayを例とします。

import numpy as np

a = np.arange(10) * 10
print(a)
# [ 0 10 20 30 40 50 60 70 80 90]

インデックス(0始まりの位置)を指定することで各要素にアクセス可能。

print(a[5])
# 50

print(a[8])
# 80

インデックスのリストや配列を指定すると複数の要素にアクセスできます。このような仕組みをファンシーインデックスという。

print(a[[5, 8]])
# [50 80]

インデックスの並びは昇順である必要はなく、重複していても問題ありません。

print(a[[5, 4, 8, 0]])
# [50 40 80  0]

print(a[[5, 5, 5, 5]])
# [50 50 50 50]

ファンシーインデックスの結果の形状は、指定するリスト・配列の形状に依存します。 以下のようにnumpy.ndarrayの二次元配列を指定すると対応する要素がその形状に従って返される。

idx = np.array([[5, 4], [8, 0]])
print(idx)
# [[5 4]
#  [8 0]]

print(a[idx])
# [[50 40]
#  [80  0]]

リストのリストで構成された二次元配列の場合エラーとなります。

# print(a[[[5, 4], [8, 0]]])
# IndexError: too many indices for array

[]でさらに囲むと問題ありません。

print(a[[[[5, 4], [8, 0]]]])
# [[50 40]
#  [80  0]]

なぜこうなるかはまだ調べていません。

多次元のNumPy配列ndarrayのファンシーインデックス

以下の二次元のNumPy配列ndarrayを例とします。

a_2d = np.arange(12).reshape((3, 4))
print(a_2d)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

行の選択

インデックス(0始まりの位置)を指定することで各行にアクセス可能。

print(a_2d[0])
# [0 1 2 3]

print(a_2d[2])
# [ 8  9 10 11]

インデックスのリストや配列を指定すると複数の行にアクセスできます。インデックスの並びは昇順である必要はなく、重複していても問題ありません。

print(a_2d[[2, 0]])
# [[ 8  9 10 11]
#  [ 0  1  2  3]]

print(a_2d[[2, 2, 2]])
# [[ 8  9 10 11]
#  [ 8  9 10 11]
#  [ 8  9 10 11]]

列の選択

列の指定はファンシーインデックスだけでなくスライスと組み合わせて実現します。 NumPy配列ndarrayに対するスライスについては以下の記事を参照。

以下のように各列にアクセス可能。

print(a_2d[:, 1])
# [1 5 9]

print(a_2d[:, 3])
# [ 3  7 11]

なお、形状を保ったまま1列分を抽出したい場合は列の指定にもスライスを使います。

print(a_2d[:, 1:2])
# [[1]
#  [5]
#  [9]]

列をインデックスのリストや配列を指定すると複数の行にアクセスできます。インデックスの並びは昇順である必要はなく、重複していても問題ありません。

print(a_2d[:, [3, 1]])
# [[ 3  1]
#  [ 7  5]
#  [11  9]]

print(a_2d[:, [3, 3, 3]])
# [[ 3  3  3]
#  [ 7  7  7]
#  [11 11 11]]

そのほかの部分配列の選択

行全体や列全体ではない部分配列をファンシーインデックスを使って指定する方法を説明します。 多次元のNumPy配列ndarrayの要素へのアクセスは各次元のインデックスをカンマ,で区切って指定します。

print(a_2d[0, 1])
# 1

print(a_2d[2, 3])
# 11

各次元のインデックスをリストや配列で指定すると、対応するインデックスの位置の要素が抽出される。 例えば二次元配列の場合に行・列のインデックスをリストで指定しても、その行・列の部分配列が選択されるわけではないので注意。

print(a_2d[[0, 2], [1, 3]])
# [ 1 11]

この例の結果の値はそれぞれ以下のインデックスの値に対応しています。

# index
# [[0, 1] [2, 3]]

各次元に指定したリスト・配列がブロードキャスト不可能な組み合わせの場合はエラーIndexErrorとなります。

# print(a_2d[[0, 2, 1], [1, 3]])
# IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (3,) (2,)

インデックスに二次元配列を指定すると二次元配列の結果が得られる。この場合はNumPy配列ndarrayではなくリストのリストでも問題ありません。

print(a_2d[[[0, 0], [2, 2]], [[1, 3], [1, 3]]])
# [[ 1  3]
#  [ 9 11]]

この例の結果の値はそれぞれ以下のインデックスの値に対応しています。

# index
# [[0, 1] [0, 3]
#  [2, 1] [2, 3]]

各次元に指定したインデックスはブロードキャストして処理されるので以下のように省略できます。

print(a_2d[[[0], [2]], [1, 3]])
# [[ 1  3]
#  [ 9 11]]

このようなインデックスを生成するのに便利なnumpy.ix_()関数があります。

numpy.ix_ — NumPy v1.14 Manual

引数に各次元のインデックスをそれぞれ一次元のリスト・配列で指定します。

idxs = np.ix_([0, 2], [1, 3])
print(idxs)
# (array([[0],
#        [2]]), array([[1, 3]]))

結果はnumpy.ndarrayを要素とするタプル。各要素が各次元に合わせた形状に変換されたインデックスとなります。

print(type(idxs))
# <class 'tuple'>

print(type(idxs[0]))
# <class 'numpy.ndarray'>

print(idxs[0])
# [[0]
#  [2]]

print(idxs[1])
# [[1 3]]

numpy.ix_()の結果をそのままインデックスとして指定すると部分配列が選択される。インデックスの並びは昇順である必要はなく、重複していても問題ありません。

print(a_2d[np.ix_([0, 2], [1, 3])])
# [[ 1  3]
#  [ 9 11]]

print(a_2d[np.ix_([2, 0], [3, 3, 3])])
# [[11 11 11]
#  [ 3  3  3]]

以下の書き方もできます。行を選択してから列を選択します。

print(a_2d[[0, 2]][:, [1, 3]])
# [[ 1  3]
#  [ 9 11]]

この書き方の場合、後述のように値の代入ができないので注意。

ファンシーインデックスを使った代入

ファンシーインデックスでは値を抽出するだけでなく、選択した部分配列に値を代入することも可能。 右辺の値がブロードキャストされて代入される。

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[np.ix_([0, 2], [1, 3])] = 100
print(a_2d)
# [[  0 100   2 100]
#  [  4   5   6   7]
#  [  8 100  10 100]]

a_2d[np.ix_([0, 2], [1, 3])] = [100, 200]
print(a_2d)
# [[  0 100   2 200]
#  [  4   5   6   7]
#  [  8 100  10 200]]

a_2d[np.ix_([0, 2], [1, 3])] = [[100, 200], [300, 400]]
print(a_2d)
# [[  0 100   2 200]
#  [  4   5   6   7]
#  [  8 300  10 400]]

ファンシーインデックスで行を選択してからさらに列を選択する書き方の場合、値の代入はできない。

print(a_2d[[0, 2]][:, [1, 3]])
# [[100 200]
#  [300 400]]

a_2d[[0, 2]][:, [1, 3]] = 0
print(a_2d)
# [[  0 100   2 200]
#  [  4   5   6   7]
#  [  8 300  10 400]]

インデックスの並びが昇順でなくても対応した位置に代入される。

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[[2, 0]] = [[100, 200, 300, 400], [500, 600, 700, 800]]
print(a_2d)
# [[500 600 700 800]
#  [  4   5   6   7]
#  [100 200 300 400]]

インデックスが重複している場合は上書きされ、結果として最後の値が代入される。

a_2d[[2, 2]] = [[-1, -2, -3, -4], [-5, -6, -7, -8]]
print(a_2d)
# [[500 600 700 800]
#  [  4   5   6   7]
#  [ -5  -6  -7  -8]]

ビュー(参照)とコピー

ファンシーインデックスはビュー(参照)ではなくコピーを返す。 ファンシーインデックスの結果を変数に代入してその要素を変更しても、元の配列の要素は変更されない。

a_2d = np.arange(12).reshape((3, 4))
print(a_2d)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

a_fancy = a_2d[np.ix_([0, 2], [1, 3])]
print(a_fancy)
# [[ 1  3]
#  [ 9 11]]

a_fancy[0, 0] = 100
print(a_fancy)
# [[100   3]
#  [  9  11]]

print(a_2d)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

スライスとの組み合わせ

NumPyの部分配列の選択方法としてスライスがあります。

各次元のインデックスをそれぞれファンシーインデックスとスライスで指定することができます。

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, 0], ::-1])
# [[11 10  9  8]
#  [ 3  2  1  0]]

print(a_2d[::2, [3, 0, 1]])
# [[ 3  0  1]
#  [11  8  9]]

スライスはビュー(参照)を返すが、ファンシーインデックスでの指定が含まれている場合はコピーとなります。

シェア

関連カテゴリー

Python NumPy

NumPy配列ndarrayの形状を変換するreshapeの使い方と-1の意味 pandas.DataFrame, SeriesとNumPy配列ndarrayを相互に変換 Python, OpenCV, NumPyで画像のアルファブレンドとマスク処理 NumPyでRGB画像の色チャンネルを分離して単色化、白黒化、色交換 NumPy配列ndarrayの行・列を任意の順番に並べ替え、選択(抽出) Pythonでの画像処理、Pillow, NumPy, OpenCVの違いと使い分け NumPyのsortとargsort関数で任意の行・列を基準にソート NumPyで欠損値np.nanを含む配列ndarrayの合計や平均を算出 『Pythonデータサイエンスハンドブック』は良書(NumPy, pandasほか) NumPyで全要素を同じ値で初期化した配列ndarrayを生成 Python, OpenCV, Pillow(PIL)で画像サイズ(幅、高さ)を取得 NumPy配列ndarrayに新たな次元を追加するnp.newaxis NumPy配列ndarrayを任意の最小値・最大値に収めるclip NumPyで任意の行・列を削除するdeleteの使い方 NumPy配列ndarrayをタイル状に繰り返し並べるnp.tile

Last Updated: 6/26/2019, 10:34:03 PM