【NumPy入門】条件に合うデータを抽出・置換する方法!複数条件とnp.whereを解説

Python

こんにちは、ぼりたそです!

今回は、NumPyの配列から条件に合うデータを取り出したり、条件に応じて値を置き換えたりする方法について解説します。

NumPyでは、「40以上の値だけを取り出す」「20以上50未満の値を選ぶ」「条件に合う値を0に置き換える」といった操作を、配列に対してまとめて行えます。

データ分析や機械学習では、必要なデータの選択や前処理でよく使う操作ですので、ぜひ覚えておきたいですね。

この記事では、以下のポイントを解説します。

Point
  • 条件式:各要素が条件を満たすか判定する
  • 条件抽出:条件に合う値だけを取り出す
  • 複数条件:「かつ」「または」「否定」を組み合わせる
  • 条件による代入・np.where():条件に応じて値を置き換える
  • 2次元配列から要素や行を取り出す方法と注意点

それでは順番に確認していきましょう!

スポンサーリンク

NumPyで条件に合うデータを選ぶには?

前回の記事では、配列に対して演算子や数学関数を使うと、要素ごとに計算できることを紹介しました。

比較演算子を使った条件式でも、同じように各要素をまとめて判定できます。その判定結果を使うことで、必要なデータだけを選べます。

NumPyを使用するには、最初にライブラリを読み込みます。

import numpy as np

以降のコードは、この読み込みを実行した状態を前提とします。今回は、整数や小数を要素に持つ通常のNumPy配列を使用します。

配列の作成方法については、次の記事も参考にしてみてください。

条件式:各要素が条件を満たすか判定する

まずは、配列に条件式を適用したときの結果を確認してみましょう。

次の配列について、それぞれの値が40以上かどうかを調べます。

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

print(a >= 40)

# 実行結果:
# [False False False  True  True  True]

a >= 40では、配列の各要素を40と比較しています。条件を満たす位置はTrue、満たさない位置はFalseになります。

このように、配列を使った条件式の結果は、True・Falseを要素に持つ配列です。今回の例では、元の配列と同じ要素数6の配列になっています。

データを選ぶために使うこのような真偽値の配列を、「ブールマスク」や単に「マスク」と呼びます。

条件式には、次の比較演算子を使えます。

演算子条件の例意味
>a > 4040より大きい
>=a >= 4040以上
<a < 4040より小さい
<=a <= 4040以下
==a == 4040と等しい
!=a != 4040と等しくない

等しいかどうかの比較には、=ではなく==を使います。=は、変数や配列の要素に値を代入するための記号です。

条件抽出:条件に合う値だけを取り出す

条件に合う値を取り出す場合は、配列の[]の中に条件式を指定します。

配列[条件式]

例えば、40以上の値だけを取り出してみましょう。

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

print(a[a >= 40])

# 実行結果:
# [40 50 60]

a >= 40で作られたマスクのうち、Trueの位置にある40、50、60が取り出されています。

この操作を「ブールインデックスによる抽出」と呼びます。位置の番号を指定するインデックスやスライスに対して、条件を満たすかどうかでデータを選ぶ方法です。

最初は、条件式の結果を変数に分けるとわかりやすいでしょう。

mask = a >= 40

print(mask)
print(a[mask])

# 実行結果:
# [False False False  True  True  True]
# [40 50 60]

a[mask]とa[a >= 40]は、同じ結果になります。

また、取り出した値は元の配列に並んでいた順番を保ちます。値の大きさで並べ替える操作ではありません。

条件を満たす値がない場合

条件に合う値が1つもない場合は、要素数0の空の配列になります。

print(a[a > 100])

# 実行結果:
# []

条件に合う値がなくても、抽出そのものはエラーになりません。ただし、その結果からさらに先頭の値などを取り出そうとすると、存在しない位置を指定することになるため注意してください。

スポンサーリンク

複数条件:「かつ」「または」「否定」を組み合わせる

1つの条件だけでなく、複数の条件を組み合わせることもできます。

ここでは、次の配列を使用します。

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

&:「両方の条件を満たす」値を取り出す

「20以上、かつ50未満」のように、両方の条件を満たす値を選ぶ場合は、&を使います。

mask = (a >= 20) & (a < 50)

print(mask)
print(a[mask])

# 実行結果:
# [False  True  True  True False False]
# [20 30 40]

a >= 20とa < 50の両方がTrueの位置だけが選ばれます。20は含まれますが、50は含まれません。

また、条件式を直接[]の中に書くこともできます。

print(a[(a >= 20) & (a < 50)])

# 実行結果:
# [20 30 40]

それぞれの比較を丸括弧で囲むことがポイントです。

|:「どちらかの条件を満たす」値を取り出す

「20未満、または50以上」のように、少なくとも一方の条件を満たす値を選ぶ場合は、|を使います。

mask = (a < 20) | (a >= 50)

print(mask)
print(a[mask])

# 実行結果:
# [ True False False False  True  True]
# [10 50 60]

この例では、20未満の10と、50以上の50、60が取り出されています。

|は、両方の条件を満たす位置も選びます。同じ位置が2回取り出されるわけではありません。

~:条件を満たさない値を取り出す

条件を満たさない値を選ぶ場合は、真偽値の配列に~を付けます。

~によって、TrueはFalseに、FalseはTrueに反転します。

mask = ~(a >= 40)

print(mask)
print(a[mask])

# 実行結果:
# [ True  True  True False False False]
# [10 20 30]

今回の配列では、40以上ではない値、つまり40未満の値が取り出されています。

この例はa[a < 40]と書いても同じ結果になります。~は、すでに作ったマスクの反対側を選びたい場合にも便利です。

なお、ここで使っているのは**真偽値の配列に対する~**です。整数の配列に直接~を付けると、ビット反転という別の計算になります。

条件による代入:選んだ位置の値を置き換える

条件式で選んだ位置には、新しい値を代入することもできます。

配列[条件式] = 新しい値

例えば、40以上の値をすべて0に置き換えてみましょう。

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

print(a)

# 実行結果:
# [10 20 30  0  0  0]

条件を満たす位置だけが0に置き換わり、条件を満たさない10、20、30はそのまま残っています。

この書き方は、元の配列の値を直接変更します。

元の配列を残したい場合は、No.4で紹介したcopy()でコピーを作ってから変更します。

a = np.array([10, 20, 30, 40, 50, 60])
b = a.copy()
b[b >= 40] = 0

print(a)
print(b)

# 実行結果:
# [10 20 30 40 50 60]
# [10 20 30  0  0  0]

bを変更しても、元の配列aは変わりません。

スポンサーリンク

np.where():条件に応じて値を選ぶ

np.where()を使うと、条件を満たす位置と満たさない位置で、どちらの値を使うか選べます。

ここでは、引数を3つ指定する書き方を使います。

np.where(条件式, Trueの位置で使う値, Falseの位置で使う値)

例えば、40以上の位置には0、それ以外の位置には元の値を使う配列を作成してみましょう。

a = np.array([10, 20, 30, 40, 50, 60])
result = np.where(a >= 40, 0, a)

print(result)
print(a)

# 実行結果:
# [10 20 30  0  0  0]
# [10 20 30 40 50 60]

それぞれの引数は、次の意味になります。

引数指定した内容役割
1つ目a >= 40各要素が40以上か判定する
2つ目0Trueの位置では0を使う
3つ目aFalseの位置では元の配列の対応する値を使う

条件による直接代入と同じ値の並びを作れますが、np.where()は結果を新しい配列として返します。この呼び出しでは、元の配列aは変更されません。

条件を満たす位置と満たさない位置を1・0で表す

両方の値に数値を指定することもできます。

flags = np.where(a >= 40, 1, 0)

print(flags)

# 実行結果:
# [0 0 0 1 1 1]

40以上の位置が1、それ以外の位置が0になります。条件に合うかどうかを、数値の目印として持たせたいときに使えます。

今回のように、条件と同じ形状の配列や1つの数値を組み合わせる場合は、結果も条件と同じ形状になります。異なる形状の配列を組み合わせる場合のルールは、ブロードキャストの記事で扱います。

なお、np.where(条件式)のように条件だけを指定すると、条件に合う位置のインデックスを返す別の使い方になります。ここで扱う、引数を3つ指定する書き方と区別しておきましょう。

条件抽出・直接代入・np.where()の違い

ここまでの3つの方法は、いずれも条件式を使いますが、結果や元の配列への影響が異なります。

次の表は、それぞれ新しく作ったa = np.array([10, 20, 30, 40, 50, 60])に対して実行した場合です。

書き方得られる結果元の配列aへの影響
a[a >= 40]条件に合う値だけの[40 50 60]変更しない
a[a >= 40] = 0aが[10 20 30 0 0 0]になる選んだ位置を変更する
np.where(a >= 40, 0, a)新しい配列[10 20 30 0 0 0]変更しない

必要な値だけを取り出したい場合は条件抽出、現在の配列を更新したい場合は直接代入を使います。

元の配列を残しながら、条件に応じた値の並びを新しく作りたい場合は、np.where()が便利です。

2次元配列:条件に合う要素や行を取り出す

行と列を持つ2次元配列にも、条件式を使用できます。

ただし、要素ごとに選ぶ場合と、行全体を選ぶ場合では、マスクの形状が異なります。

条件に合う要素を取り出す

まずは、次の2行3列の配列から、40以上の値だけを取り出してみましょう。

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

print(data >= 40)
print(data[data >= 40])
print(data[data >= 40].shape)

# 実行結果:
# [[False  True False]
#  [ True False  True]]
# [40 50 60]
# (3,)

data >= 40は、元の配列と同じ2行3列のマスクです。これを使うと、各位置のTrueに対応する要素が取り出されます。

この場合、取り出した結果は1次元配列になります。行・列の配置を残すのではなく、上の行から順に、各行を左から右へ見て該当する値を並べています。

元の形状を保って値を置き換えたい場合は、次のようにnp.where()を使えます。

print(np.where(data >= 40, 0, data))

# 実行結果:
# [[10  0 20]
#  [ 0 30  0]]

この結果は、元の配列と同じ2行3列になっています。

特定の列を条件にして、行全体を取り出す

次に、1列目がID、2列目が点数という配列を考えてみましょう。

ここでは、点数が70以上の行だけを取り出します。

scores = np.array([
    [101, 55],
    [102, 80],
    [103, 65],
    [104, 90]
])

mask = scores[:, 1] >= 70

print(mask)
print(scores[mask, :])
print(scores[mask, :].shape)

# 実行結果:
# [False  True False  True]
# [[102  80]
#  [104  90]]
# (2, 2)

scores[:, 1]は、すべての行から2列目を取り出す書き方です。列のインデックスは0から始まるため、2列目を1で指定しています。

取り出した点数を70と比較すると、各行に対応する要素数4のマスクができます。

scores[mask, :]では、そのマスクで行を選び、列には:を指定してすべてを残しています。そのため、IDと点数をセットで取り出せます。

結果は2行2列の2次元配列です。行を選びたい場合は、今回のように行数に対応する1次元のマスクを使用します。

条件抽出・置換を行う際の注意点

and・or・notではなく、&・|・~を使う

Pythonの条件分岐ではandやorを使いますが、配列の各要素の判定を組み合わせる場合は、&や|を使います。

例えば、今回の配列で(a >= 20) and (a < 50)と書くと、配列全体を1つの真偽値として判断しようとするため、ValueErrorになります。

また、20 <= a < 50という比較のつなげ方も使えません。

目的配列の条件式で使う書き方
両方を満たす(条件1) & (条件2)
少なくとも一方を満たす(条件1) | (条件2)
判定を反転する~(条件)

丸括弧を省略すると、演算子の優先順位によって意図しない順番で計算される場合があります。比較ごとに丸括弧を付ける形で覚えておきましょう。

抽出した配列の変更と、元の配列への直接代入を区別する

ブールインデックスで抽出した配列は、元の配列とデータを共有するビューではなく、コピーになります。

a = np.array([10, 20, 30, 40, 50, 60])
picked = a[a >= 40]
picked[0] = 99

print(picked)
print(a)

# 実行結果:
# [99 50 60]
# [10 20 30 40 50 60]

pickedの先頭を変更しても、aは変わりません。

一方、代入先としてa[条件式]を指定すると、元の配列が更新されます。

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

print(a)

# 実行結果:
# [10 20 30 99 99 99]

また、a[a >= 40][0] = 99のように[]を続けると、一度抽出したコピーを変更することになり、元の配列は更新されません。元の配列を変更したい場合は、代入先を確認しましょう。

np.where()では、選ばれない側の計算も実行される

np.where()は、要素ごとに実行する処理を切り替えるif文とは異なります。

例えば、np.where(x >= 0, np.sqrt(x), 0)と書いても、np.sqrt(x)はnp.where()へ渡される前に、配列全体について計算されます。実数の配列xに負の値があると、標準設定では警告が出ます。

計算対象を限定したい場合は、先に条件で取り出してから計算できます。

x = np.array([-1.0, 0.0, 4.0, 9.0])
result = np.zeros(x.shape)
mask = x >= 0
result[mask] = np.sqrt(x[mask])

print(result)

# 実行結果:
# [0. 0. 2. 3.]

np.zeros(x.shape)で同じ形状の0の配列を作り、0以上の値だけを取り出して平方根を計算しています。これなら、負の値に対して平方根を計算せずに済みます。

なお、配列の要素への代入では、結果を代入先のデータ型で保存します。小数を残したい場合は、この例のresultのように、浮動小数点数を保持できる配列を用意しましょう。

NumPyを基礎から学びたい方へ

今回は、条件式を使って、NumPy配列のデータを抽出・置換する方法を紹介しました。

条件抽出を覚えると、必要なデータを選んでから計算する、といった処理を書けるようになります。さらに、集計や配列の形状の変更を組み合わせると、データ分析でできることが広がります。

こうしたNumPyの基本操作を体系的に学びたい方向けに、Kindle書籍『機械学習・データ分析のためのNumPy入門 ― 配列操作と数値計算の基本をPythonで学ぶ ―』を執筆しました。

本書では、配列の作成やインデックス・スライス、条件抽出、集計、ブロードキャストなどを、コード例と演習問題を通じて学べる構成にしています。

「条件や数値を変えて、自分でコードを書けるようになりたい」「データ分析や機械学習に向けてNumPyの基本を身につけたい」という方は、ぜひチェックしてみてください!

まとめ

今回は、NumPyで条件に合うデータを抽出・置換する方法について解説しました。

今回紹介した主な書き方をまとめると、次のようになります。

書き方用途・特徴
a >= 40各要素が40以上か判定する
a[a >= 40]40以上の値だけを取り出す
a[(a >= 20) & (a < 50)]20以上50未満の値を取り出す
a[(a < 20) | (a >= 50)]20未満または50以上の値を取り出す
a[~(a >= 40)]40以上という条件を満たさない値を取り出す
a[a >= 40] = 0元の配列の40以上の値を0に置き換える
np.where(a >= 40, 0, a)40以上を0、それ以外を元の値とする新しい配列を作る
scores[scores[:, 1] >= 70, :]2列目が70以上の行を取り出す

条件式でTrue・Falseの配列を作り、その結果を使って対象を選ぶことが基本です。

また、値だけを取り出したいのか、元の配列を変更したいのか、新しい配列を作りたいのかによって、使う書き方が変わります。

まずは今回紹介したコードを実際に動かして、条件の数値や比較演算子を変えると、どの値が選ばれるかを確認してみてください!

スポンサーリンク
タイトルとURLをコピーしました