メインコンテンツへスキップ

3.2.3 配列のインデックスとスライス

NumPy のインデックスとスライスの図

学習目標

  • 1次元配列と多次元配列の基本インデックスとスライスを理解する
  • ブールインデックスを使った条件抽出を学ぶ
  • ファンシーインデックス(Fancy Indexing)を理解する
  • ビュー(View)とコピー(Copy)の違いを理解する

1次元配列のインデックスとスライス

1次元配列のインデックスは、Python のリストと基本的に同じです:

import numpy as np

arr = np.array([10, 20, 30, 40, 50, 60, 70, 80])

# ===== 基本インデックス =====
print(arr[0]) # 10 最初の要素
print(arr[3]) # 40 4番目の要素
print(arr[-1]) # 80 最後の要素
print(arr[-2]) # 70 末尾から2番目の要素

# ===== スライス [start:stop:step] =====
print(arr[2:5]) # [30 40 50] インデックス 2 から 4
print(arr[:3]) # [10 20 30] 最初の3個
print(arr[5:]) # [60 70 80] インデックス 5 から末尾まで
print(arr[::2]) # [10 30 50 70] 1つおきに取り出す
print(arr[::-1]) # [80 70 60 50 40 30 20 10] 逆順にする

2次元配列のインデックスとスライス

2次元配列は [行, 列] の形でアクセスします:

matrix = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
])

単一要素へのアクセス

print(matrix[0, 0])    # 1    0行0列
print(matrix[1, 2]) # 7 1行2列
print(matrix[-1, -1]) # 16 最後の行・最後の列

行全体 / 列全体へのアクセス

print(matrix[0])       # [1 2 3 4]     0行目(行全体)
print(matrix[0, :]) # [1 2 3 4] 同じ意味。より明示的な書き方

print(matrix[:, 0]) # [ 1 5 9 13] 0列目(列全体)
print(matrix[:, -1]) # [ 4 8 12 16] 最後の列

行・列のスライス

# 最初の2行、最初の3列を取り出す
sub = matrix[:2, :3]
print(sub)
# [[1 2 3]
# [5 6 7]]

# 1〜2行目、2〜3列目を取り出す
sub2 = matrix[1:3, 2:4]
print(sub2)
# [[ 7 8]
# [11 12]]

# 1つおきに行を取り出す(0行目、2行目)
sub3 = matrix[::2]
print(sub3)
# [[ 1 2 3 4]
# [ 9 10 11 12]]

2次元インデックスの図解

matrix = 
列0 列1 列2 列3
行0 [ 1 2 3 4 ]
行1 [ 5 6 7 8 ]
行2 [ 9 10 11 12 ]
行3 [ 13 14 15 16 ]

matrix[1, 2] → 7 (1行2列)
matrix[:2, :3] → [[1,2,3], [5,6,7]] (最初の2行、最初の3列)
matrix[:, 1] → [2, 6, 10, 14] (すべての行、1列目)

ブールインデックス:条件抽出

これは NumPy のとても強力な機能の1つです。条件式を使って、データを直接抽出できます。

基本の考え方

arr = np.array([15, 23, 8, 42, 31, 5, 19, 27])

# 1段階目:条件式からブール配列を作る
mask = arr > 20
print(mask) # [False True False True True False False True]

# 2段階目:ブール配列をインデックスとして使い、True に対応する要素を取り出す
result = arr[mask]
print(result) # [23 42 31 27]

# 通常は1行でまとめて書ける
print(arr[arr > 20]) # [23 42 31 27]

よく使う条件抽出

scores = np.array([85, 92, 78, 65, 95, 43, 88, 72, 55, 90])

# 合格点(>= 60)
print(scores[scores >= 60]) # [85 92 78 65 95 88 72 90]

# 優秀点(>= 90)
print(scores[scores >= 90]) # [92 95 90]

# 不合格点(< 60)
print(scores[scores < 60]) # [43 55]

# 60〜80 の点数(複数条件は & でつなぐ。各条件はかっこで囲む)
print(scores[(scores >= 60) & (scores <= 80)]) # [78 65 72]

# 60未満または90より高い点数(複数条件は | でつなぐ)
print(scores[(scores < 60) | (scores > 90)]) # [92 95 43 55]

# 反転(~)
print(scores[~(scores >= 60)]) # [43 55] scores[scores < 60] と同じ
複数条件の書き方

NumPy では、複数条件を組み合わせるときに Python の and / or使えません。代わりに次を使います。

  • &and の代わりに使う(かつ)
  • |or の代わりに使う(または)
  • ~not の代わりに使う(否定)
  • 各条件は必ずかっこで囲む
# ❌ 間違った書き方
arr[arr > 5 and arr < 20]

# ✅ 正しい書き方
arr[(arr > 5) & (arr < 20)]

2次元配列でのブールインデックス

matrix = np.array([
[85, 92, 78],
[65, 95, 43],
[88, 72, 90]
])

# 80より大きい点数をすべて取り出す
print(matrix[matrix > 80]) # [85 92 95 88 90]
# 注意:結果は1次元配列になります!

# 不合格点を60に変更する(条件付き代入)
matrix[matrix < 60] = 60
print(matrix)
# [[85 92 78]
# [65 95 60] ← 43 が 60 に変わった
# [88 72 90]]

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

ファンシーインデックスでは、整数配列をインデックスとして使い、指定した位置の要素をまとめて取り出せます。

1次元のファンシーインデックス

arr = np.array([10, 20, 30, 40, 50, 60, 70])

# インデックス 1, 3, 5 の要素を取り出す
print(arr[[1, 3, 5]]) # [20 40 60]

# 同じ要素を繰り返し取り出せる
print(arr[[0, 0, 2, 2]]) # [10 10 30 30]

# 好きな順番で取り出せる
print(arr[[6, 4, 2, 0]]) # [70 50 30 10]

2次元のファンシーインデックス

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

# 0行目と2行目を取り出す
print(matrix[[0, 2]])
# [[ 1 2 3 4]
# [ 9 10 11 12]]

# 特定の位置:(0,1), (1,2), (2,3) の3要素を取り出す
rows = [0, 1, 2]
cols = [1, 2, 3]
print(matrix[rows, cols]) # [ 2 7 12]

ビュー(View)vs コピー(Copy)

NumPy のビューとコピーの落とし穴図解

ここは初心者がつまずきやすいポイントです。NumPy のスライスはコピーではなくビューを返します。

ビュー:変更すると元の配列にも影響する

arr = np.array([1, 2, 3, 4, 5])

# スライスはビュー
sub = arr[1:4]
print(sub) # [2 3 4]

# sub を変更すると arr も変わる!
sub[0] = 99
print(sub) # [99 3 4]
print(arr) # [ 1 99 3 4 5] ← 元の配列も変わった!

コピー:互いに影響しない

arr = np.array([1, 2, 3, 4, 5])

# copy() で独立したコピーを作る
sub = arr[1:4].copy()
print(sub) # [2 3 4]

# sub を変更しても arr は変わらない
sub[0] = 99
print(sub) # [99 3 4]
print(arr) # [1 2 3 4 5] ← 元の配列は変わらない

いつビュー? いつコピー?

操作戻り値の種類
スライスビューarr[2:5]
ブールインデックスコピーarr[arr > 3]
ファンシーインデックスコピーarr[[1, 3, 5]]
.copy()コピーarr[2:5].copy()
.reshape()ビュー(通常)arr.reshape(2, 3)
実用的なアドバイス

ある操作がビューを返すのかコピーを返すのか不安なとき、元の配列をうっかり変更したくないなら .copy() を付けましょう。安全第一です。

safe_sub = arr[1:4].copy()  # いつでも安全

実践例:インデックスでデータを分析する

Titanic の場面に戻って、NumPy のインデックスを使ってデータを分析してみましょう。

import numpy as np

# 10人の乗客データを模したもの
ages = np.array([22, 38, 26, 35, 35, np.nan, 54, 2, 27, 14])
fares = np.array([7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.08, 11.13, 30.07])
survived = np.array([0, 1, 1, 1, 0, 0, 0, 0, 1, 1])

# 生存者の平均運賃を求める
survivor_fares = fares[survived == 1]
print(f"生存者の平均運賃: ${np.mean(survivor_fares):.2f}")

# 30歳より上の乗客の運賃を取り出す(NaN を除外する必要がある)
valid_mask = ~np.isnan(ages) # NaN を除外
age_mask = ages > 30
combined_mask = valid_mask & age_mask
print(f"30歳以上の乗客の運賃: {fares[combined_mask]}")

# 運賃が高い上位3人の乗客のインデックスを取り出す
top3_indices = np.argsort(fares)[-3:][::-1] # 並べ替えた後、最後の3つを取り、逆順にする
print(f"運賃 Top 3 のインデックス: {top3_indices}")
print(f"対応する運賃: {fares[top3_indices]}")

まとめ

インデックス方法構文戻り値の種類適した場面
基本インデックスarr[i], arr[i, j]要素値単一要素を取得する
スライスarr[start:stop:step]ビュー連続した範囲を取得する
ブールインデックスarr[arr > 5]コピー条件で抽出する
ファンシーインデックスarr[[1, 3, 5]]コピー離れた位置の要素を取る

手を動かしてみよう

練習1:基本スライス

arr = np.arange(1, 21)  # [1, 2, 3, ..., 20]

# 1. 最初の5個の要素を取り出す
# 2. すべての奇数位置の要素を取り出す(インデックス 1, 3, 5, ...)
# 3. 最後の3個の要素を取り出す
# 4. 配列を逆順にする

練習2:2次元スライス

matrix = np.arange(1, 26).reshape(5, 5)
print(matrix)
# [[ 1 2 3 4 5]
# [ 6 7 8 9 10]
# [11 12 13 14 15]
# [16 17 18 19 20]
# [21 22 23 24 25]]

# 1. 真ん中の 3×3 の部分行列を取り出す
# 2. 2列目のすべての要素を取り出す
# 3. 対角線の要素 [1, 7, 13, 19, 25] を取り出す(ヒント:ファンシーインデックスを使う)

練習3:ブールインデックスの実践

# あるクラス20人の数学の点数
math_scores = np.array([
78, 92, 65, 88, 45, 95, 72, 81, 56, 90,
83, 67, 94, 73, 85, 60, 98, 77, 69, 87
])

# 1. すべての不合格点(< 60)を取り出す
# 2. 80〜90 の点数(両端含む)を取り出す
# 3. 合格した学生の平均点を計算する
# 4. すべての不合格点を60に変更する
# 5. 変更後の平均点を計算する