NumPy

【numpy】ファンシーインデックス(Fancy Indexing)の基本

【numpy】ファンシーインデックス(Fancy Indexing)の基本

numpyの配列の要素を指定するためのファンシーインデックス(Fancy Indexing)について基本を解説します。

ファンシーインデックス(Fancy Indexing)

一般的な要素の指定方法

numpyの配列(ndarray)で一般的に要素にアクセスする場合には、「要素位置を指定する」「スライスにより範囲を指定する」「条件式で一致するものを取り出す」といった方法があります。

簡単な例でおさらいしておきましょう。

import numpy as np

temp = np.arange(10) * 10
print(f'temp = {temp}')

# 要素にアクセスする方法
# 位置を指定
print(f'temp[9] = {temp[9]}')
# スライスによるアクセス
print(f'temp[5:] = {temp[5:]}')
# ブール値のマスクによるアクセス
print(f'temp >= 70 : {temp >= 70}')
print(f'temp[temp >= 70] = {temp[temp >= 70]}')
【実行結果】
temp = [ 0 10 20 30 40 50 60 70 80 90]
temp[9] = 90
temp[5:] = [50 60 70 80 90]
temp >= 70 : [False False False False False False False  True  True  True]
temp[temp >= 70] = [70 80 90]

要素位置を指定する場合には、temp[9]のように要素位置を指定することで、その位置の値を取得することができます。

スライスにより範囲を指定する場合には、temp[5:]のように対象となる範囲を指定します。この場合は5以降の要素を全て取り出すという意味です。

条件式に一致するものを取り出す場合には、temp[temp >= 7]のように条件となる式を指定します。この場合、実態としてtemp >= 7はbool型の配列になっていて、Trueとなっている要素が抽出されることになります。

上記をイメージ図にしてみると以下のようになります。

numpy 配列 要素 アクセス

配列の参照方法に関する情報としては以下も参考にしてください。

numpyには、ファンシーインデックス(Fancy Indexing)と呼ばれている要素へのアクセス方法があります。

本記事では、このファンシーインデックスについて基本的な内容を紹介していきます。

ファンシーインデックスによる指定方法の基本

ファンシーインデックス(Fancy Indexing)とは、インデックスを指定する配列を渡すことで対象となる要素を取り出す方法です。

言葉だけだとよく分からないと思いますので、以降で具体的な例を見ながら確認していきましょう。

配列からインデックス配列で指定した位置の値を取り出す

1次元配列から指定位置の要素を取り出す例を紹介します。また、取り出す際に要素の並び順を変更します。以下の例で見ていきましょう。

import numpy as np

temp = np.arange(10) * 10
print(f'temp = {temp}')

# index用リストを作成
ind = [1, 7, 5]
print(f'ind = {ind}')
print(f'temp[ind] = {temp[ind]}')
【実行結果】
temp = [ 0 10 20 30 40 50 60 70 80 90]
ind = [1, 7, 5]
temp[ind] = [10 70 50]

この例では、0から90までの要素の配列tempから要素を抽出します。index用のリストindは取り出す位置を決めている配列です。

ここで注目してほしいのがindの中身は[1, 7, 5]というように任意の順番を指定しているという点です。これにより取得される要素は[10 70 50]のように順番が入れ替わっていることが分かります。

上記をイメージ図にしてみると以下のようになります。

numpy ファンシーインデックス Fancy Indexing

このように要素を順番を変更しつつ取り出したいときにはファンシーインデックスの考え方を利用できます。

インデックス配列の形状にして値を取り出す

1次元の配列から要素を取り出して2次元配列を作成する例を紹介します。この例では、インデックス配列の形状に合わせた値の取り出しができることが理解できます。

import numpy as np

temp = np.arange(10) * 10
print(f'temp = {temp}')

# 2次元のindexリストを作成し1次元のデータから配列を作成
ind = np.array([[1, 5], [9, 2]])
print(f'ind = \n{ind}')
print(f'temp[ind] = \n{temp[ind]}')
【実行結果】
temp = [ 0 10 20 30 40 50 60 70 80 90]
ind = 
[[1 5]
 [9 2]]
temp[ind] = 
[[10 50]
 [90 20]]

上記の例を見てもらえば分かる通り、元のtempという配列は1次元データです。

インデックス用の配列としてindという配列を2次元配列にし、元の1次元配列から取り出したい要素位置を指定しています。その結果、元は1次元配列の要素から2次元配列を取り出せています。

上記をイメージ図にしてみると以下のようになります。

numpy ファンシーインデックス Fancy Indexing

このように元のデータからデータ形状を変えつつ取り出す際にもファンシーインデックスの考え方が利用できます。

各次元の要素位置を指定して値を取り出す

配列の各次元の要素位置を指定して値を取り出す方法を紹介します。以下の例で見てみましょう。

import numpy as np

temp = np.arange(15).reshape((5, 3))
print(f'temp = \n{temp}')

# 行を指定するインデックス
row_ind = np.array([0, 2, 4])
print(f'row_ind = {row_ind}')
# 列を指定するインデックス
col_ind = np.array([2, 0, 1])
print(f'col_ind = {col_ind}')

# (0, 2), (2, 0), (4, 1)の位置の値を取得して配列を作成していることになる
print(f'temp[row_ind, col_ind] = {temp[row_ind, col_ind]}')
【実行結果】
temp = 
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]
 [12 13 14]]
row_ind = [0 2 4]
col_ind = [2 0 1]
temp[row_ind, col_ind] = [ 2  6 13]

この例では、2次元配列から行方向、列方向のそれぞれの位置を指定して値を取り出しています。

row_indは行方向で取得する位置、col_indは列方向で取得する位置を指定しています。具体的には(0,2)、(2,0)、(4,1)の値を取りだすと指定していることになります。

上記をイメージ図にしてみると以下のようになります。

numpy ファンシーインデックス Fancy Indexing

配列の部分的な値を取り出す

前述の例は、元の配列の各位置の値を取り出すような抽出(2次元→1次元)の例でした。今度は、元の配列の次元のまま部分的に値を取り出す方法を紹介します。

import numpy as np

temp = np.arange(15).reshape((5, 3))
print(f'temp = \n{temp}')

# 行を指定するインデックス
row_ind = np.array([0, 2, 4])[:, np.newaxis]
print(f'row_ind = \n{row_ind}')
# 列を指定するインデックス
col_ind = np.array([2, 0])
print(f'col_ind = {col_ind}')

# 0, 2, 4行目から、2, 0列目の値を取り出す
print(f'temp[row_ind, col_ind] = \n{temp[row_ind, col_ind]}')
【実行結果】
temp = 
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]
 [12 13 14]]
row_ind = 
[[0]
 [2]
 [4]]
col_ind = [2 0]
temp[row_ind, col_ind] = 
[[ 2  0]
 [ 8  6]
 [14 12]]

この例では、行を指定するインデックスの配列をnp.newaxisによって軸を追加しています。具体的にはもともとrow_ind.shape = (3, )であったものを、row_ind.shape= (3, 1)にしていることになります。

このようにすることで、元の2次元を保ちつつ必要な行、列の値を抽出しています。この例の特徴的なところは、指定行の要素を取り出しつつ、列の順序を変えているところです。

実際numpyを使って分析をする場面では、行は各データ、列は属性として扱うことが多いです。この例で実施してるのは特定のデータ番号を取り出しつつ、属性を自分の見たい順に並び替えていることと同じです。

上記をイメージ図にしてみると以下のようになります。

numpy ファンシーインデックス Fancy Indexing

配列をマスキングをする

分析を行うときには、複数行の任意列だけデータを取得したいようなケースがあります。このような場合には、bool型のリストを使うと簡単に取得できます。

以下の例で見てみましょう。

import numpy as np

temp = np.arange(15).reshape((5, 3))
print(f'temp = \n{temp}')

# 行を指定して任意の列をマスキングして取得する
row_ind = np.array([0, 2, 4])[:, np.newaxis]
col_mask = np.array([1, 0, 1], dtype=bool)
print(f'temp[row_ind, col_mask] = \n{temp[row_ind, col_mask]}')
【実行結果】
temp = 
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]
 [12 13 14]]
temp[row_ind, col_mask] = 
[[ 0  2]
 [ 6  8]
 [12 14]]

この例では、col_maskで取り出すべき位置をbool型の配列として指定しています。具体的には0列目と2列目を取り出せるように指定しています。

このようにすることで、元の配列の一部をマスキングしたような配列を作り出すことが可能です。今回は、row_indは番号順のようになっていますが、行の順番を変えたかったら数字の順番を変えればもちろん順序を変えることができます。

numpy ファンシーインデックス Fancy Indexing

この例では、列の順番はそのままの順で取り出されることになります。「マスキング」と書いた通り、一部の対象をマスクしているような使い方です。もし列の順番もを変えたいような場合は、前述の列インデックス位置を指定する方法を使用するようにしてください。