3.

NumPy 配列要素参照完全ガイド(インデックス / スライス / ファンシー / Boolean)

編集
この記事の要点
  • 基本参照: a[i, j](カンマ区切り)/ スライス a[1:3, :]
  • ファンシーインデックス: a[[0, 2, 4]] で任意の行・列を抽出
  • Boolean インデックス: a[a > 5] で条件マッチ要素のみ
  • ビュー vs コピー: スライスはビュー(メモリ共有)、ファンシー / Boolean はコピー
  • 形状操作: reshape / ravel / flatten / newaxis / np.ix_

基本: インデックス参照

NumPy 配列はカンマ区切りで多次元アクセスできます。Python リストの a[i][j] より直接的です:

import numpy as np

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

# 単一要素
a[0, 0]    # 1
a[2, 3]    # 12
a[-1, -1]  # 12 (負のインデックスも可)

# 1 行取得
a[0]       # array([1, 2, 3, 4])
a[0, :]    # 同じ

# 1 列取得
a[:, 0]    # array([1, 5, 9])

# 部分行列 (2x2)
a[0:2, 1:3]
# array([[2, 3],
#        [6, 7]])

スライス: a[start:stop:step]

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

a[2:5]     # [2 3 4]    (stop は含まない)
a[:3]      # [0 1 2]
a[7:]      # [7 8 9]
a[::2]     # [0 2 4 6 8]  ステップ 2
a[::-1]    # [9 8 7 6 5 4 3 2 1 0]  逆順
a[1:8:3]   # [1 4 7]    1 から 8 未満を 3 ステップ

# 2D
m = np.arange(16).reshape(4, 4)
m[::2, ::2]
# array([[ 0,  2],
#        [ 8, 10]])

m[1:, :-1]    # 1 行目以降、最終列を除く
m[::-1]       # 行を逆順
m[:, ::-1]    # 列を逆順

ファンシーインデックス(整数配列インデックス)

インデックスにリストや配列を渡すと、任意の位置を一度に取り出せます:

a = np.array([10, 20, 30, 40, 50])

# 0, 2, 4 番目を抽出
a[[0, 2, 4]]   # array([10, 30, 50])

# 重複・順序自由
a[[4, 0, 4, 2]]   # array([50, 10, 50, 30])

# 2D で行抽出
m = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
m[[0, 2]]    # array([[1, 2], [5, 6]])

# 行と列を組合せ(同サイズ配列を渡す)
rows = np.array([0, 1, 2])
cols = np.array([1, 0, 1])
m[rows, cols]   # array([2, 3, 6])   = (0,1), (1,0), (2,1)

# np.ix_ で「行集合 × 列集合」のクロス積
m[np.ix_([0, 2], [0, 1])]
# array([[1, 2],
#        [5, 6]])

Boolean インデックス

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

# 条件にマッチする要素のみ
a[a > 5]              # [ 6  7  8  9 10]
a[a % 2 == 0]         # [ 2  4  6  8 10]

# 複数条件 (& |、and/or は不可)
a[(a > 3) & (a < 8)]  # [4 5 6 7]
a[(a < 3) | (a > 8)]  # [ 1  2  9 10]

# 反転
a[~(a > 5)]           # [1 2 3 4 5]

# 2D で行抽出
m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])
mask = m.sum(axis=1) > 10
m[mask]
# array([[4, 5, 6],
#        [7, 8, 9]])

ビュー vs コピー

スライスはビュー(元配列のメモリを共有)、ファンシー・Boolean はコピー。これを知らないと、片方を書き換えたつもりが両方変わって混乱します。

a = np.arange(10)

# スライス → ビュー
v = a[2:5]
v[0] = 999
print(a)   # [  0   1 999   3   4   5   6   7   8   9] ← 元も変わる
print(v.base is a)   # True

# ファンシーインデックス → コピー
f = a[[2, 3, 4]]
f[0] = -1
print(a[2])   # 999 (変わらない、コピーだから)
print(f.base is None)   # True

# Boolean インデックス → コピー
m = a[a > 5]
m[0] = -1
print(a)   # 元配列は変わらない

# 明示的にコピーが欲しい
b = a[2:5].copy()

形状を変える操作

a = np.arange(12)

# reshape: 形状変更(要素数が一致する必要)
a.reshape(3, 4)
a.reshape(2, 2, 3)
a.reshape(-1, 4)   # -1 で自動計算(3, 4 になる)

# ravel: 1 次元化(できればビューを返す)
m = np.arange(12).reshape(3, 4)
m.ravel()    # array([0, 1, ..., 11]) ビュー

# flatten: 1 次元化(必ずコピー)
m.flatten()  # array([0, 1, ..., 11]) コピー

# newaxis: 次元追加
a = np.arange(4)            # shape (4,)
a[:, np.newaxis]            # shape (4, 1)
a[np.newaxis, :]            # shape (1, 4)
a[:, None]                  # newaxis のエイリアス

# tile: 繰り返し
np.tile(a, 3)               # [0 1 2 3 0 1 2 3 0 1 2 3]
np.tile(a, (2, 3))          # 2x12 配列

np.take と np.put

ファンシーインデックスより明示的なAPI:

a = np.array([10, 20, 30, 40, 50])

# 取得
np.take(a, [0, 2, 4])   # array([10, 30, 50])

# 2D の軸指定
m = np.arange(12).reshape(3, 4)
np.take(m, [0, 2], axis=0)   # 行 0, 2
np.take(m, [1, 3], axis=1)   # 列 1, 3

# 書き込み
np.put(a, [0, 2], [-1, -2])
print(a)   # [-1 20 -2 40 50]

メモリレイアウト: C order vs F order

# C order (デフォルト, 行優先): 行ベクトル走査が高速
c = np.arange(12).reshape(3, 4)
print(c.flags['C_CONTIGUOUS'])   # True

# F order (列優先, Fortran 互換): 列ベクトル走査が高速
f = np.arange(12).reshape(3, 4, order='F')
print(f.flags['F_CONTIGUOUS'])   # True

# 大きな配列では走査方向に合わせると速度が大きく変わる
# - C order : a[i, :] や a.sum(axis=1) が高速
# - F order : a[:, j] や a.sum(axis=0) が高速

よく使うパターン集

やりたいこと書き方
最大値の位置np.argmax(a) / np.unravel_index(np.argmax(a), a.shape)
上位 N 件a[np.argsort(a)[-N:]] または np.partition
n 番目だけ取得a[::n]
最終要素a[-1]
対角成分np.diag(a)
三角行列np.triu(a) / np.tril(a)
非ゼロの位置np.nonzero(a)
条件マッチの個数(a > 5).sum()

FAQ

Q: a[1, 2]a[1][2] はどちらが速い?
A: a[1, 2]a[1][2] は一旦 a[1](ビュー)を作ってから再アクセスするので 2 倍遅くなります。

Q: マスクで取り出した部分を書き換えると元配列に反映される?
A: 取り出し(読み)はコピーですが、左辺に置けば代入はビュー的に動作します:

a = np.arange(10)
a[a > 5] = 0    # ← 代入なら反映される
print(a)        # [0 1 2 3 4 5 0 0 0 0]

Q: 列ベクトルに次元追加する一番簡単な方法は?
A: a[:, None]a.reshape(-1, 1) でも同じ。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 配列の作成
  2. 多次元配列の作成
  3. 要素の参照
  4. 要素の追加
  5. 要素の更新
  6. 要素の削除
  7. ブロードキャスト
  8. 多次元配列の構造の確認
  9. 多次元配列を1次元配列に変換
  10. 1次元配列を多次元配列に変換
  11. NumPy 配列の範囲指定(スライス・多次元・Boolean / Fancy Index)
  12. 平均値の算出
  13. 行列を結合する方法

最近更新/作成されたページ