配列のスライシング
Python のシーケンスと同じように、NumPy の配列 (ndarray オブジェクト) も次の構文でスライシングして部分配列を抽出することができます。
x[start=0 : stop=size : step=1]
start には開始インデックス、stop には終了インデックス + 1 を渡します。
step は start から何個おきに要素を取り出すかという引数です。
引数はすべてオプションなので省略できます。たとえば、x[:5] は x[0:5] と等価です(インデックス 0 から 4 まで取り出すという意味です)。
# NUMPY_SLICE
# In[1]
import numpy as np
# x = [0 1 2 3 4 5 6 7 8 9]
x = np.arange(10)
# インデックス0~2
print("x[:3] ⇒", x[:3])
# インデックス7以降すべて
print("x[7:] ⇒", x[7:])
# インデックス5~8
print("x[5:9] ⇒", x[5:9])
# 2個おきの要素
print("x[::2] ⇒", x[::2])
# インデックス4から2個おきの要素
print("x[4::2] ⇒", x[4::2])
# x[:3] ⇒ [0 1 2]
# x[7:] ⇒ [7 8 9]
# x[5:9] ⇒ [5 6 7 8]
# x[::2] ⇒ [0 2 4 6 8]
# x[4::2] ⇒ [4 6 8]
step に負の値を渡すと、逆順に要素を取り出します。
# In[2]
# x = [0 1 2 3 4 5 6 7 8 9]
x = np.arange(10)
# 末尾から逆順にすべての要素
print("x[::-1] ⇒", x[::-1])
# 末尾から逆順に3つおきの要素
print("x[::-3] ⇒", x[::-3])
# インデックス5から逆順にすべての要素
print("x[5::-1] ⇒", x[5::-1])
# x[::-1] ⇒ [9 8 7 6 5 4 3 2 1 0]
# x[::-3] ⇒ [9 6 3 0]
# x[5::-1] ⇒ [5 4 3 2 1 0]
多次元配列をスライシングする場合は、軸ごとにカンマ (,) で区切りながらスライス位置を指定します。
x[start:stop:step, start:stop:step, ...]
実例を見てみましょう。準備として、乱数を使って 5 × 5 の配列を作成しておきます(大きな配列が必要なときに乱数は便利です)。
# In[3]
# 乱数seedを10に設定
np.random.seed(10)
# 乱数を使って5×5の配列を作成
x = np.random.randint(1, 100, (5, 5))
# seedを固定しているので、必ず次の配列が生成されます
#[[10 16 65 29 90]
# [94 30 9 74 1]
# [41 37 17 12 55]
# [89 63 34 73 79]
# [50 52 55 78 70]]
1 ~ 2 行目, 1 ~ 3 列目をスライシングして部分配列を取り出してみます。
# In[4]
# 1~2行目,1~3列目をスライシング
print(x[:2, :3])
# [[10 16 65]
# [94 30 9]]
2 ~ 3行目, 3 ~4 列目をスライシングします。
# In[5]
# 2~3行目, 3~4列目をスライシング
print(x[1:3, 3:4])
# [[ 9 74]
# [17 12]]
最初の行を取り出すコードです。
# In[6]
# 最初の行をスライシング
print(x[0, :])
# [10 16 65 29 90]
最初の列を取り出すコードです。
# In[7]
# 最初の列をスライシング
print(x[:, 0])
# [10 94 41 89 50]
ファンシーインデックス
ファンシーインデックス はインデックスのリストまたは配列を渡して複数の要素に同時アクセスします。スライシングよりも複雑な参照を可能にしますが、copy を作成するので実行速度は遅くなります。
最初に 1 次元配列のファンシーインデックスを試してみましょう。
次のような素数の並ぶ配列をつくっておきます。
# NUMPY_FANCY_INDEX
# In[1]
import numpy as np
# 1次元配列を定義
x = np.array([2, 3, 5, 7, 9, 11, 13, 17, 19])
print(x)
# [2 3 5 7 9 11 13 17 19]
この配列からインデックス 7, 2, 4 の要素を取り出すときには、次のようなコードを書きます。
# In[2]
# 配列x
# [ 2 3 5 7 9 11 13 17 19]
# インデックスのリストを作成
x_index = [7, 2, 4]
print(x[x_index])
# [17 5 9]
新しい配列の形状を自由に設定することができます。
# In[3]
# 配列x
# [ 2 3 5 7 9 11 13 17 19]
# インデックス配列を作成
x_index = np.array([[5, 1],
[8, 3]])
print(x[x_index])
# [[11 3]
# [19 7]]
次は多次元配列のファンシーインデックスの実例を見てみます。
準備として seed=20 の疑似乱数で配列をつくっておきます。
# In[4]
# 乱数seedを20に設定
np.random.seed(20)
# 乱数を使って5×5の配列を作成
x = np.random.randint(1, 100, (5, 5))
# seedを固定しているので、必ず次の配列が生成されます
#[[91 16 96 29 91]
# [10 21 76 23 72]
# [35 97 41 86 91]
# [27 84 17 63 17]
# [ 8 99 7 27 14]]
この配列のインデックス (3, 1) の要素は 84, インデックス (4, 4) の要素は 14 です。これらの要素を並べた部分配列を作成するときは次のようなコードを書きます。
# In[4]
# 行方向のインデックスリスト
x_row = [3, 4]
# 列方向のインデックスリスト
x_col = [1, 4]
y = x[x_row, x_col]
print(y)
# [84 14]
ファンシーインデックスは普通のインデックスと組合わせて使用することができます。たとえば、配列の 2 行目のインデックス 4, 0, 2 の要素を参照したい場合は次のように書くことができます。
# In[6]
# 2 行目のインデックス4,0,2の要素を参照
print(x[1, [4, 0, 2]])
# [72 10 76]
スライシングとファンシーインデックスを組合わせることもできます。2 行目以降のインデックス 4, 0, 2 の列を参照する場合は次のようなコードを記述します。
# In[7]
# 2 行目以降のインデックス4,0,2の列を参照
print(x[1:, [4, 0, 2]])
# [[72 10 76]
# [91 35 41]
# [17 27 17]
# [14 8 7]]
numpy.take()
numpy.take() は任意の軸 (axis に指定した軸) に沿って要素を抽出する関数です。
seed=31 の乱数配列をつくって関数の機能を確認してみましょう。
# PYTHON_NUMPY_TAKE
# In[1]
import numpy as np
# 乱数seedを31に設定
np.random.seed(31)
# 乱数を使って5×5の配列を作成
x = np.random.randint(1, 100, (5, 5))
# seedを固定しているので、必ず次の配列が生成されます
#[[83 88 17 99 24]
# [59 29 94 93 43]
# [47 7 83 19 26]
# [82 24 29 11 85]
# [45 76 55 19 25]]
axis に 0 を渡すと、第 2 引数のインデックスリストに対応する行が抽出されます。
# In[2]
# 4,1,3行目を取り出す
y = np.take(x, [3, 0, 2], axis=0)
print(y)
# [[82 24 29 11 85]
# [83 88 17 99 24]
# [83 88 17 99 24]
axis に 1 を渡すと、第 2 引数のインデックスリストに対応する列が抽出されます。
# In[3]
# 4,1,3列目を取り出す
z = np.take(x, [3, 0, 2], axis=1)
print(z)
# [[99 83 17]
# [93 59 94]
# [19 47 83]
# [11 82 29]
# [19 45 55]]
axis を省略すると、最初の行からインデックスリストに対応する要素が抽出されます。
# In[4]
# 4,1,3行目を取り出す
w = np.take(x, [3, 0, 2])
print(w)
# [99 83 17]
「1次元配列のスライシング」の説明文で、
「step に負の値を渡すと、start と stop が入れ替わって、逆順に取り出します」
→ 「step に負の値を渡すと、逆順に要素を取り出します」のほうがわかりやすいと思いました。
「start と stop が入れ替わって …」
確かに、わかりにくい表現ですね。久しぶりに、この記事を見た私自身が何のことかよくわからなかったぐらいですから …
直しておきました。m(_ _)m