NumPyは、Pythonにおける数値計算のデファクトスタンダードとして君臨しており、その中心となるのが高速な配列操作です。
大量のデータを効率的に扱うためには、配列の中から必要なデータのみを特定して取り出す「スライス」と「切り出し」の技術を習得することが欠かせません。
本記事では、1次元配列の基本から、実務で多用される多次元配列の高度な切り出し、さらには条件指定による抽出(ブールインデックス参照)までを体系的に整理しました。
NumPyにおけるスライスの基本構造
NumPyの配列(ndarray)におけるスライスの基本は、Python標準のリール型と似ていますが、多次元配列をカンマ区切りで直感的に操作できる点が大きく異なります。
基本書式は array[start:stop:step] です。
- start:抽出を開始するインデックス(含める)
- stop:抽出を終了するインデックス(含めない)
- step:要素をいくつ飛ばしで取得するか(増分)
まずは、もっともシンプルな1次元配列の例を確認しましょう。
import numpy as np
# 0から9までの配列を作成
arr = np.arange(10)
# インデックス2から5の手前まで抽出
slice_1 = arr[2:5]
# 2つ飛ばし(ステップ2)で抽出
slice_2 = arr[::2]
# 逆順に抽出
slice_3 = arr[::-1]
print("元の配列:", arr)
print("インデックス2:5:", slice_1)
print("ステップ2:", slice_2)
print("逆順:", slice_3)
元の配列: [0 1 2 3 4 5 6 7 8 9]
インデックス2:5: [2 3 4]
ステップ2: [0 2 4 6 8]
逆順: [9 8 7 6 5 4 3 2 1 0]
ここで注目すべきは、NumPyのスライスは元の配列の「ビュー(View)」を返すという点です。
これは、スライスされた配列の値を変更すると、元の配列の値も書き換わることを意味します。
メモリ効率を最大化するための仕様ですが、意図しない書き換えを防ぐには .copy() を使用する必要があります。
多次元配列(2次元・3次元)の切り出し
データ分析や画像処理では、行列(2次元配列)やテンソル(3次元以上の配列)を扱う場面がほとんどです。
NumPyでは、次元ごとにスライス範囲を カンマ で区切って指定します。
2次元配列の行と列の指定
2次元配列 arr[行の範囲, 列の範囲] という形式で指定します。
# 3x4の2次元配列を作成
arr_2d = np.array([
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33]
])
# 1行目から最後まで、かつ1列目から3列目の手前までを抽出
sub_array = arr_2d[1:, 1:3]
# 特定の列(例:2列目すべて)を取得
col_2 = arr_2d[:, 2]
print("部分配列:\n", sub_array)
print("2列目のみ:", col_2)
部分配列:
[[21 22]
[31 32]]
2列目のみ: [12 22 32]
「:」のみを記述した場合は「その次元のすべてを選択する」という意味になります。
上記の例で [:, 2] としているのは、行方向はすべて選択し、列方向はインデックス2のみに絞り込むという操作です。
3次元以上の配列と省略記号
画像データのように「バッチ、高さ、幅、チャンネル」といった4次元以上のデータを扱う際、途中の次元をすべて選択したい場合には Ellipsis(…) を使うと便利です。
# 5次元のダミー配列(shape=(2, 3, 4, 5, 6))
arr_5d = np.zeros((2, 3, 4, 5, 6))
# 最初の次元が0、最後の次元が1、それ以外はすべて選択
extracted = arr_5d[0, ..., 1]
print("抽出後の形状:", extracted.shape)
抽出後の形状: (3, 4, 5)
... を使うことで、中間の複数の次元に対して :, :, : と記述する手間を省き、コードの可読性を高めることができます。
条件指定による抽出(ブールインデックス参照)
特定の数値より大きい要素だけを抜き出したい、あるいは特定のフラグが立っているデータのみを抽出したい場合には、ブールインデックス参照(Boolean Indexing)を使用します。
これは、配列に対して比較演算を行い、返ってきた True/False の配列をインデックスとして渡す手法です。
data = np.array([15, 30, 45, 10, 55, 20])
# 30より大きい要素を抽出
condition = data > 30
filtered_data = data[condition]
print("条件配列:", condition)
print("抽出結果:", filtered_data)
条件配列: [False False True False True False]
抽出結果: [45 55]
複数の条件を組み合わせる場合は、Python標準の and や or ではなく、ビット演算子である & (AND) や | (OR) を使用し、各条件を () で囲む必要があります。
# 20以上かつ50未満の要素を抽出
multi_condition = (data >= 20) & (data < 50)
result = data[multi_condition]
print("複数条件での抽出:", result)
複数条件での抽出: [30 45 20]
ファンシーインデックス参照(Fancy Indexing)
特定の位置にある要素を不連続に、かつ特定の順序で取得したい場合には、整数配列によるインデックス指定、通称「ファンシーインデックス」を利用します。
arr = np.array([100, 200, 300, 400, 500])
# 0番目、4番目、2番目の順に取得
indices = [0, 4, 2]
fancy_result = arr[indices]
print("ファンシーインデックスの結果:", fancy_result)
ファンシーインデックスの結果: [100 500 300]
ファンシーインデックスの重要な特性は、スライスとは異なり、常にデータの「コピー(Copy)」を返す点です。
抽出した配列を変更しても元の配列には影響しません。
| 操作 | 戻り値の性質 | メモリ消費 | 元データへの影響 |
|---|---|---|---|
| 基本スライス | ビュー (View) | 低い (参照のみ) | あり |
| ブールインデックス | コピー (Copy) | 高い (新規作成) | なし |
| ファンシーインデックス | コピー (Copy) | 高い (新規作成) | なし |
実践:データのスライスを用いた前処理例
実際のデータ分析シーンを想定し、2次元データの特定の列を基準にしたフィルタリングと、スライスによるデータのトリミングを組み合わせてみましょう。
以下の例では、5行3列のデータ([ID, スコアA, スコアB])から、スコアAが50点以上の行だけを抽出し、さらにID列を除外する操作を行います。
# サンプルデータ: ID, ScoreA, ScoreB
dataset = np.array([
[101, 45, 88],
[102, 72, 92],
[103, 50, 78],
[104, 30, 65],
[105, 88, 95]
])
# 1. スコアA(インデックス1の列)が50以上の行を判定
mask = dataset[:, 1] >= 50
# 2. 条件に合う行を抽出し、かつスコアのみ(1列目以降)を切り出し
filtered_scores = dataset[mask, 1:]
print("フィルタリング後のスコアデータ:\n", filtered_scores)
フィルタリング後のスコアデータ:
[[72 92]
[50 78]
[88 95]]
このように、ブールインデックスによる行の選択と、スライスによる列の範囲指定を一行で記述できるのがNumPyの強みです。
スライス操作におけるパフォーマンスの注意点
NumPyはC言語レベルで実装された連続メモリ配置を利用しているため、スライス操作は極めて高速です。
しかし、大規模なデータを扱う際には以下の2点に注意してください。
- メモリリークの可能性:
非常に巨大な配列から小さな一部をスライスして保持し続ける場合、スライスは「ビュー」であるため、元の巨大な配列全体がメモリ上に残り続けます。もし小さな断片しか必要ないのであれば、.copy()を明示的に呼び出して元のメモリを解放できるようにしてください。 - 不連続なアクセス:
ステップを大きく指定したスライス(例:arr[::1000])やファンシーインデックスは、メモリ上の連続していない場所にアクセスするため、CPUキャッシュの効率が下がり、連続スライスに比べて計算速度が低下する場合があります。
まとめ
NumPyのスライスと切り出し操作は、単なるデータ抽出の手段にとどまらず、プログラムのパフォーマンスと可読性を左右する重要な要素です。
- 基本スライス:
[start:stop:step]で「ビュー」を生成。 - 多次元スライス:カンマ区切りで各次元を指定。
- ブールインデックス:条件式で「コピー」を抽出。
- ファンシーインデックス:インデックスのリストで自由な順序の「コピー」を作成。
これらの手法を適切に使い分けることで、ループ処理(for文)を排除した高速なベクトル演算が可能になります。
特に「ビュー」と「コピー」の違いを意識することは、バグの混入を防ぐ上で非常に重要です。
2026年のデータサイエンス領域においても、これらの基礎技術は変わらず基盤として機能しています。
ぜひ日々のコーディングで積極的に活用し、効率的なデータ処理を実現してください。
