[NumPy超入門]多次元配列「ndarray」の高度なインデックス指定に触れてみようPythonデータ処理入門

NumPyが提供する多次元配列の要素を選択するために、その整数値のインデックスを配列で与えたり、ブーリアン値の配列を与えたりする方法を紹介します。覚えると便利に使えるはずです。

» 2023年07月21日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「Pythonデータ処理入門」のインデックス

連載目次

連載概要

 本連載はPythonについての知識を既にある程度は身に付けている方を対象として、Pythonでデータ処理を行う上で必須ともいえるNumPyやpandas、Matplotlibなどの各種ライブラリの基本的な使い方を学んでいくものです。そして、それらの使い方をある程度覚えた上で、それらを活用してデータ処理を行うための第一歩を踏み出すことを目的としています。


 前回はNumPyが提供する多次元配列「ndarray」に格納されている要素をインデックスやスライスを指定して選択する基本的な方法を紹介しました。今回は多次元配列に格納されている要素をより柔軟な形で選択する方法を見ていきます。

高度なインデックス指定またはファンシーインデクシング

 NumPyの多次元配列「ndarray」では、選択したい要素を表すインデックスを含んだ配列(やPythonのリストなど)を角かっこ「[]」の中に記述することも可能です。と書くと分かりにくいのでさっそく簡単な例を示しましょう。例なので簡単に一次元配列を扱ってみます。

a = np.arange(10)
print(a)  # [0 1 2 3 4 5 6 7 8 9]

一次元配列

 このような配列があったときに、0番目、8番目、5番目、1番目の要素をこの順番で取り出す(選択する)ときに次のようなコードを書けるということです。

idx = np.array([0, 8, 5, 1])
result = a[idx]
print(result)  # [0 8 5 1]

0番目、8番目、5番目、1番目の要素を選択するコード

 最初の行では、選択したい要素があるインデックスを含んだ配列を作成しています。そして、2行目ではそれを配列aのインデックスとして指定することで、その結果を取り出しています。ここでは「インデックスの値=要素の値」となっているので、インデックスの値と同じ値が得られています。

 このように、インデックス指定にndarrayオブジェクトを使用して、複数の要素を選択する方法をNumPyのドキュメントでは「高度なインデックス指定」(advanced indexing)または「ファンシーインデクシング」と呼んでいます。

 高度なインデックス指定では、インデックス指定に使用する配列の要素として整数かブーリアン値(True/False)が使えます。後者については後ほど紹介することにしましょう。

 また、インデックスを指定するためのオブジェクトとしてはndarrayやタプル以外のシーケンス(リストなど)、最低でも1つのシーケンスまたはndarrayオブジェクトを含むタプルを使用できます。以下に例を示します。

print(a[[0, 8, 5, 1]])  # タプル以外のシーケンス
print(a[np.array([0, 8, 5, 1])])  # ndarrayオブジェクト
print(a[(0, 8, 5, 1),])  # シーケンスを要素とするタプル
print(a[(0, 8, 5, 1)])  # IndexError:a[0, 8, 5, 1]と同じ。1次元配列なのに4つの次元を指定

高度なインデックス指定の例

 なお、高度なインデックス指定で値を選択(取得)した場合、それらはビューではなく元のデータのコピーとなる点には注意してください。以下はその例です。

tmp = a[[1, 2]]
print(tmp)  # [1 2]
tmp[0] = 11
print(tmp)  # [11 2]
print(a)  # [0 1 2 3 4 5 6 7 8 9]:変更が元データには反映されない

高度なインデックス指定で取得できるのは元データのコピー

 ただし、高度なインデックス指定により選択された要素に値を代入する場合には、元のデータが変更されます。

a[[1, 2]] = np.array([11, 22])
print(a)  # [ 0 11 22  3  4  5  6  7  8  9]:選択した要素への代入は元データに反映される

高度なインデックス指定で選択した要素への代入は元データに反映される

2次元配列で高度なインデックス指定を使用する

 2次元配列に対して高度なインデックス指定を使う場合には、少し注意が必要です。つまり、「[行インデックス, 列インデックス]」のような指定の仕方とは異なり、インデックス指定に使用する配列には次元ごと(行ごと、列ごと)にインデックスをまとめます。

 例えば、次のような3行4列の2次元配列があったとしましょう。

a = np.arange(12).reshape((3, 4))
print(a)
# 出力結果:
#[[ 0  1  2  3]
# [ 4  5  6  7]
# [ 8  9 10 11]]

2次元配列

 このときに0行0列(0)、1行1列(5)、2行2列(10)の値を選択するには次のようなインデックスを指定します。

rows = np.array([0, 1, 2])
columns = np.array([0, 1, 2])
result = a[rows, columns]
print(result)  # [ 0  5 10]

高度なインデックスでは次元ごとにインデックスの値をまとめる

 この例では、インデックス指定に使用する配列を2つ作成しています(rowsとcolumns)。その値はいずれも[0 1 2]という多次元配列です。そして、これらに行ごと列ごとのインデックスが格納されています。第0要素は共に0なので0行0列を、第1要素は共に1なので1行1列を、第2要素は共に2なので2行2列を示しています。得られた値もそれに対応したものになっていることを確認してください。

高度なインデックス指定でブーリアン値を要素とする配列を使う

 冒頭でも述べましたが、高度なインデックス指定ではブーリアン値を要素とする配列(リストなど)も使用できます。このときには、インデックスに使用する配列の要素のうち、値がTrueのものに対応する要素が選択され、Falseに対応する要素が選択されないということになります。文字だと分かりにくいので簡単な例を示しましょう。

 例えば、以下のような1次元配列があったとします。

a = np.arange(5)
print(a)  # [0 1 2 3 4]

5つの要素を格納する1次元配列

 ここで以下のようなブーリアン値を要素とする配列を作成します。

idx = np.array([True, False, True, False, True])
print(idx)  # [ True False  True False  True]

ブーリアン値を要素とする1次元配列

 高度なインデックス指定では、このような配列をインデックス指定に使えるということです。答えは既にお分かりでしょうが、以下に示します。

result = a[idx]
print(result)  # [0 2 4]

値がTrueの要素に対応する要素が選択される

 インデックス指定に使用する配列idxで値がTrueとなっているのは、第0要素と第2要素と第4要素の3つです。そのため、配列aでこれらに対応する第0要素と第2要素と第4要素が選択されています。

 これだけだとあまり意味がないようにも思えますが、NumPyでは特定の条件に合致するかどうかを調べて、その結果をTrue/Falseで示す配列を得ることも簡単に行えます。以下はその例です。

a = np.linspace(0, 1, 6)
print(a)  # [0.  0.2 0.4 0.6 0.8 1. ]

idx = a >= 0.5
print(idx)  # [False False False  True  True  True]

配列の要素の値が0.5以上かどうかを調べる

 最初の2行では0.0〜1.0の範囲において0.2刻みで増えていく値を要素とする配列を作成しています。そして、「idx = a >= 0.5」という行では、その配列の要素が0.5以上かどうかをチェックして、結果が真なら対応する要素をTrueとなり、そうでなければ対応する要素をFalseとなる配列を得ています。この結果は[False False False True True True]となります。0.6と0.8と1.0の3つの要素についてはTrue(0.5以上)で、他の要素についてはFalse(0.5未満)というわけです(当たり前ですね)。

 そして、得られた配列idxをインデックス指定に使用するのが以下のコードです。

result = a[idx]
print(result)  # [0.6 0.8 1. ]

配列aの中で値が0.5以上のものだけを選択する

 ここでは値が0.5以上のものについては配列idxの要素の値がTrueとなっているので、対応する要素が選択されたことが分かります。さらに次のようなことも可能です。

a[idx] = 1.0
print(a)  # [0.  0.2 0.4 1.  1.  1. ]

print(~idx)  # [ True  True  True False False False]
a[~idx] = 0.0
print(a)  # [0. 0. 0. 1. 1. 1.]

値が0.5以上の要素は1.0に、0.5未満のものは0.0にする

 「a[idx] = 1.0」とすることで、対応する3つの要素の値を1.0にしています。また、~演算子を使うと、ndarrayの要素の真偽を反転させることも可能です。このことを使って、値が0.5未満の要素についてはその値を0.0にしているのが後半のコードです。何らかの値を条件として配列の要素の値を書き換えるといったこともこのように簡単にできてしまうのが、高度なインデックス指定の便利なところだといえるでしょう。

高度なインデックス指定と基本的なインデックス指定の混在

 前回に紹介した基本的なインデックスやスライスの操作と、高度なインデックス指定は混在させて使用することも可能です。簡単な例だけですが、示しておきましょう。

a = np.arange(12).reshape((3, 4))
print(a)
# 出力結果:
#[[ 0  1  2  3]
# [ 4  5  6  7]
# [ 8  9 10 11]]

result = a[[0, 2], 1# 0行目と2行目の第1要素を選択
print(result)  # [1 9]

result = a[[0, 2], 1:]  # 0行目と2行目の第1要素以降を選択
print(result)
# 出力結果:
#[[ 1  2  3]
# [ 9 10 11]]

高度なインデックス指定と基本的なインデックス指定の混在

 最初の例ではまず3行4列の2次元配列を作成した後に、第0行と第2行の第1要素を選択するものです。「[0, 2]」という部分は高度なインデックス指定ですが、それに続く「1」は基本的なインデックス指定です。これは第0行と第2行の第1要素を選択することになります。

 次の例では、列の指定で「1:」とすることで第1要素以降を指定している以外は同じです。これにより、第0行と第2行の第1要素以降が選択されます。


 2つのインデックス指定をうまく組み合わせれば、通常のPythonのリストなどよりも柔軟な形で多次元配列の要素を操作できるようになるでしょう。今回は高度なインデックス指定の基礎的な使い方を紹介しました。より詳細で複雑なことをしたい場合には、NumPyのドキュメント「Indexing on ndarrays」をぜひご覧ください。

 少々短くなりましたが、今回は前回に引き続きndarrayオブジェクトの要素を高度なインデックス指定を用いて選択する方法を紹介しました。次回は行列の演算について見ていく予定です。

「Pythonデータ処理入門」のインデックス

Pythonデータ処理入門

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。