NumPy配列のインデクシングとスライシングについてのまとめ

NumPy配列のインデクシングとスライシング

NumPyを使う上で必須となる操作である、インデクシングとスライシングの使い方についてまとめます。

インデクシングもスライシングも、ある配列(ndarray)から一部の要素を参照するための操作です。

Python標準の構文と同様、NumPy配列(ndarray)では、x[obj] でのインデクシングが可能です。xが配列で、objがインデックスの指定です。

NumPy配列(ndarry)のスライシングは、Python標準のリストに対するスライスをN次元に拡張したものです。

インデクシングとスライシングはいずれも、ビュー(参照)を返すものとコピーを返すものがあります。その違いをしっかりと理解しましょう。

ビューとコピー

インデクシングとスライシングはそれぞれ元となる配列から一部のデータを取得します。

取得できた値が元となる配列の参照である場合、それを ビュー と呼びます。したがって、ビューの値を更新すると元の配列の値に影響します。ただし配列がコピーされるわけではないのでメモリ消費量が抑えられます。

元のデータとは別のデータとして取得できる場合、これをコピーと呼ぶことにします。新しく別のメモリを消費しデータを保持します。

インデクシング

インデクシングとは文字通りインデックスを指定して配列から一部分の値を取り出す操作です。

基本的なインデクシングは上述の通り、Python標準の構文と違いはありません。a[0] みたいな形で参照します。インデクシングによって得られるのはビューです。

また、インデクシングで得られるビューは元の配列から次元が減ります。以下の例では2次元配列からインデクシングで得られるビューが1次元配列となっています。

import numpy as np

# 3×3の配列と1行目をインデクシングで取り出した配列
a = np.arange(1, 10).reshape((3, 3))
b = a[0]

# 値を書き換えてみる
a[0, 0] = 10
b[1] = 20

# 値が互いに反映されている
# インデクシングを行うと次元が減る
print(a)
# [[10 20  3]
#  [ 4  5  6]
#  [ 7  8  9]]
print(b)
# [10 20  3]

スライシング

基本的なスライシングは、配列の各次元において start:stop:step で配列から切り出す範囲を指定します。start, stop, step はそれぞれ省略可能で、省略するとデフォルトで 0, n-1(最終インデックス), 1 が使用されます。

切り出された配列(ndarray)はビューです。

1次元配列の場合

Pythonのスライシングと同様の構文です。1次元配列の例で使い方を見ていきます。

import numpy as np

a = np.array([0, 1, 2, 3, 4])

# 0から3番目まで(3番目の要素は含まない)
b = a[0:3]
c = a[:3] # startを省略すると0から
print(b) # [0 1 2]
print(c) # [0 1 2]

上記の例では、要素数5の配列をスライスし、インデックス0から3までの要素を切り出します。stopにしていしたインデックス3の要素は含まれませんので注意が必要です。

startを省略する(:stopとする)とインデックス0からとなります。stepを使用していませんので、1刻みの連番でインデックスを切り出します。

import numpy as np

a = np.array([0, 1, 2, 3, 4])
b = a[2:5] # 2番目から5番目
c = a[2:] # 2番目から最後まで
d = a[-3:] # 後ろから3番目(5-3=2)から最後まで

print(b) # [2 3 4]

上記例ではインデックス2の要素から最終要素までをスライシングしています。stopで指定するインデックスの要素は含まれないので、インデックス範囲外でもエラーになりませんが、最終要素までならstopの値は省略して書く方がいいでしょう。

また、インデックスの指定は負の数を指定することでインデックスの後ろから指定することができます。-1が最終要素を意味し、-nは最終要素から数えてn番目のインデックスを意味します。

import numpy as np

a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

b = a[::2] # 先頭から2つおきに切り出す
print(b) # [0 2 4 6 8]

c = a[1:-1:3] # 1番目から8番目まで3つおきに
print(c) # [1 4 7]

d = a[::-1] # step=-1で逆順
print(d) # [9 8 7 6 5 4 3 2 1 0]

stepは切り出す間隔です。-1を指定すると元の配列を逆順にしたものを返します。

多次元配列の場合

多次元配列になっても基本は変わりません。各次元に対して start:stop:step を指定します。カンマで区切って指定していきます。

3×4の2次元配列を例にしてスライシングで切り出す例です。

import numpy as np

a = np.arange(12).reshape((3, 4))
b = a[:, 0:2] # 全行の先頭から2列分
c = a[1:, :-1] # 2行目から最後から1列前まで
d = a[1:-1, 1:-1] # 最初と最後の行、列を対象外
e = a[0:1, 0:1] # 最初の行の最初の列

# スライシングで得られたビューの次元は元の配列と同じ
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(b)
# [[0 1]
#  [4 5]
#  [8 9]]

print(c)
# [[ 4  5  6]
#  [ 8  9 10]]

print(d)
# [[5 6]]

print(e)
# [[0]]

元の2次元配列から指定した範囲でスライスされているのが確認できます。

この例では元の配列が2次元配列なので、どのようにスライスした結果も2次元配列となっています。インデクシングだと次元が減った状態のビューが得られますが、スライシングは元の配列と同じ次元のビューが得られます。

まとめ

  • インデクシングとスライシングは元の配列の1部分を取得するために使用する
  • インデクシングとスライシングで得られる配列は元の配列のビューである。
  • スライシングは start:stop:step でスライスする範囲を指定する。
  • インデクシングで得られるビューは元の配列より次元が減るが、スライシングで得られるビューは元の配列と同じ次元を持つ。

参考URL