Pandas条件抽出:AND(&), OR(|), NOT(~) で複数条件を組み合わせる方法

こんにちは。前回は、Pandas DataFrameから特定の「行」を選択する主な方法として、ラベル基準の.loc[]と整数位置基準の.iloc[]について学びました。これで、表の中から目的の行をピンポイントで、あるいは範囲で取り出すことができるようになりましたね。

さて、これまでの列選択や行選択の知識を組み合わせることで、DataFrameの中から必要な部分をかなり自由に抜き出せるようになってきました。しかし、データ分析の現場で最も頻繁に行われる操作の一つが、「特定の条件に合うデータだけを探し出す」ことです。

例えば、

  • 「年齢が30歳以上の社員だけをリストアップしたい」
  • 「部署が『営業部』である社員の情報が見たい」
  • 「経験年数が5年未満で、かつ評価スコアが4.5以上の社員はいるか?」

といった要求に応える必要があります。

そこで今回は、Pandas DataFrameを使って、このような条件に基づいた行のフィルタリング(抽出)を行う方法を徹底的に解説します。この記事を読み終える頃には、あなたは以下のことができるようになっているはずです。

  • 比較演算子(>, <, == など)を使って、単一の条件でDataFrameの行をフィルタリングする方法を理解する。
  • 条件を指定したときに裏側で何が起こっているのか(ブール値のSeriesブールインデックス参照)の仕組みを理解する。
  • 論理演算子(&, |, ~)を使って、複数の条件(AND, OR, NOT)を組み合わせて行をフィルタリングする方法を理解する。
  • 複数条件を指定する際の括弧()の重要性を理解する。
  • (おまけとして)より簡潔に条件を記述できる.query()メソッドの存在を知る。

条件指定によるフィルタリングは、データ分析の基本的ながら非常に強力なテクニックです。Excelのフィルター機能を使ったことがある方は、それに似た操作をPythonコードで行うイメージを持つと良いかもしれません。今回も具体的なコード例と結果を見ながら、一つずつ着実にマスターしていきましょう!

なぜ条件フィルタリングが必要なのか?

条件フィルタリングは、データ分析や前処理の様々な場面で活躍します。

  • 特定のグループの分析: 「特定の年代の顧客」「特定の地域の売上」「特定の製品カテゴリのレビュー」など、分析したい対象グループのデータだけを抽出できます。
  • 外れ値の検出・処理: 「年齢が異常に高い/低い」「売上金額がマイナスになっている」など、通常では考えにくい値を持つ行を見つけ出し、確認や修正を行うために使えます。
  • ルールベースの処理: 「スコアが一定基準以上のユーザーに特典を付与する」「在庫数が閾値を下回った商品を発注リストに追加する」といった、特定の条件に基づいて次のアクションを決定する際に役立ちます。
  • データセットの分割: 特定の条件(例:訓練データ期間とテストデータ期間)に基づいて、データセットを複数のサブセットに分割する際にも利用されます。

このように、データを深く理解し、目的に応じて加工していく上で、条件フィルタリングは欠かせないスキルなのです。

サンプルデータの準備

今回も、おなじみの社員情報サンプルデータを使います。このデータに対して、様々な条件でフィルタリングを行っていきます。

import pandas as pd

# サンプルデータをPythonの辞書で作る
data = {
    '社員ID': ['S001', 'S002', 'S003', 'S004', 'S005', 'S006', 'S007'],
    '氏名': ['佐藤 一郎', '鈴木 花子', '高橋 健太', '田中 優子', '伊藤 次郎', '渡辺 直美', '山本 翔太'],
    '部署': ['営業部', '開発部', '営業部', '人事部', '開発部', '営業部', '開発部'],
    '年齢': [35, 28, 41, 31, 25, 38, 29],
    '経験年数': [10, 5, 15, 8, 2, 12, 6],
    '評価スコア': [4.5, 4.8, 4.2, 4.0, 4.9, 3.8, 4.6]
}

# 辞書データからDataFrameを作成
df = pd.DataFrame(data)

# 作成したDataFrameを表示(確認用)
print(df)

実行結果(再掲):

  社員ID     氏名   部署  年齢  経験年数  評価スコア
0  S001  佐藤 一郎  営業部  35    10     4.5
1  S002  鈴木 花子  開発部  28     5     4.8
2  S003  高橋 健太  営業部  41    15     4.2
3  S004  田中 優子  人事部  31     8     4.0
4  S005  伊藤 次郎  開発部  25     2     4.9
5  S006  渡辺 直美  営業部  38    12     3.8
6  S007  山本 翔太  開発部  29     6     4.6

このdfを使って、条件フィルタリングの世界を探検しましょう。

基本的な条件指定:比較演算子を使う

まずは、1つの条件だけで行を絞り込む方法です。これには、Pythonの基本的な比較演算子を使います。

主な比較演算子を復習しておきましょう。

  • == : 等しい (Equal to)
  • != : 等しくない (Not equal to)
  • > : より大きい (Greater than)
  • < : より小さい (Less than)
  • >= : 以上 (Greater than or equal to)
  • <= : 以下 (Less than or equal to)

これらの比較演算子を、DataFrameの特定の列に対して適用します。

条件式の仕組み:ブール値のSeries

実際にフィルタリングを行う前に、条件式がどのように評価されるのかを見てみましょう。例えば、「年齢が30歳より大きい」という条件を考えてみます。これは、DataFrameの'年齢'列に対して、比較演算子>を使って次のように書けます。

# 条件式:年齢が30より大きい
condition = df['年齢'] > 30
print(condition)

実行結果:

0     True
1    False
2     True
3     True
4    False
5     True
6    False
Name: 年齢, dtype: bool

実行結果として表示されたのは、DataFrameそのものではなく、True(真)とFalse(偽)が並んだSeriesです。これは、ブール値(Boolean)のSeriesと呼ばれます。

何が起こったのでしょうか?
df['年齢'] > 30という式は、'年齢'列の各行の値に対して「30より大きいか?」を判定し、その結果(TrueFalseか)を行ごとに記録したSeriesを作成します。

  • インデックス0の行(佐藤さん)の年齢は35で、30より大きいのでTrue
  • インデックス1の行(鈴木さん)の年齢は28で、30より大きくないのでFalse
  • ...以下同様

となります。このTrue/FalseのSeriesが、フィルタリングの「鍵」になります。

ブールインデックス参照:条件に合う行を抽出する

上で作成したブール値のSeries(condition)を使って、実際に条件に合う行だけをDataFrameから取り出してみましょう。これには、DataFrameの[]の中に、このブール値のSeriesを入れます。

# ブール値のSeriesを使ってフィルタリング
filtered_df = df[condition]
print(filtered_df)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
0  S001  佐藤 一郎  営業部  35    10     4.5
2  S003  高橋 健太  営業部  41    15     4.2
3  S004  田中 優子  人事部  31     8     4.0
5  S006  渡辺 直美  営業部  38    12     3.8

見事に「年齢が30より大きい」行だけが抽出されました!

このdf[ブール値のSeries]という書き方を、ブールインデックス参照 (Boolean Indexing) と呼びます。これは、「ブール値のSeriesの中でTrueになっているインデックスに対応する行だけを、元のDataFrameから取り出してきてください」という指示になります。

通常は、条件式の作成とフィルタリングを1行で書くことが多いです。

# 条件指定とフィルタリングを1行で(年齢 > 30)
df_age_over_30 = df[df['年齢'] > 30]
print(df_age_over_30)

結果は上記と同じになります。df[df['年齢'] > 30]というコードは、「まずdf['年齢'] > 30True/FalseのSeriesを作り、そのSeriesを使ってdfからTrueの行だけを取り出す」という2段階の処理を一気に行っているわけです。

様々な条件でのフィルタリング例

比較演算子を使って、他の条件でもフィルタリングしてみましょう。

例1:部署が「営業部」の行を抽出 (==)
文字列の比較も同様に行えます。

# 部署が'営業部'の行を抽出
df_sales = df[df['部署'] == '営業部']
print(df_sales)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
0  S001  佐藤 一郎  営業部  35    10     4.5
2  S003  高橋 健太  営業部  41    15     4.2
5  S006  渡辺 直美  営業部  38    12     3.8

例2:経験年数が5年以下の行を抽出 (<=)

# 経験年数が5年以下の行を抽出
df_exp_le_5 = df[df['経験年数'] <= 5]
print(df_exp_le_5)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
1  S002  鈴木 花子  開発部  28     5     4.8
4  S005  伊藤 次郎  開発部  25     2     4.9

例3:評価スコアが4.0ではない行を抽出 (!=)

# 評価スコアが4.0ではない行を抽出
df_score_not_4 = df[df['評価スコア'] != 4.0]
print(df_score_not_4)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
0  S001  佐藤 一郎  営業部  35    10     4.5
1  S002  鈴木 花子  開発部  28     5     4.8
2  S003  高橋 健太  営業部  41    15     4.2
4  S005  伊藤 次郎  開発部  25     2     4.9
5  S006  渡辺 直美  営業部  38    12     3.8
6  S007  山本 翔太  開発部  29     6     4.6

このように、比較演算子とブールインデックス参照を使えば、様々な単一条件でDataFrameをフィルタリングできます。

複数の条件を組み合わせる:論理演算子を使う

次に、複数の条件を組み合わせてフィルタリングする方法です。「Aであり、かつBである」「AまたはBである」「Aではない」といった、より複雑な絞り込みを行います。これには、論理演算子を使います。

Pandasで複数の条件式(ブール値のSeries)を組み合わせる際には、以下の論理演算子を使います。

  • & : AND (かつ) - 両方の条件がTrueの場合のみTrue
  • | : OR (または) - どちらか一方でも条件がTrueならTrue
  • ~ : NOT (ではない) - 条件の結果を反転させる (TrueFalseに、FalseTrueに)

非常に重要な注意点:
Pythonの通常の論理演算子and, or, notは、Pandasの条件式(ブール値のSeries)の組み合わせには使えません。必ず&, |, ~ を使う必要があります。これは初心者がよく間違えるポイントです。

もう一つ、非常に重要な注意点があります。複数の条件式を&|で組み合わせる際は、個々の条件式を必ず括弧()で囲む必要があります。これは、演算子の優先順位の問題を避けるためのお作法です。

AND条件:(条件1) & (条件2)

両方の条件を同時に満たす行を抽出します。

例:部署が「開発部」であり、かつ評価スコアが4.5以上の行

# 条件1: 部署が'開発部'
cond1 = df['部署'] == '開発部'
# 条件2: 評価スコアが4.5以上
cond2 = df['評価スコア'] >= 4.5

# AND条件でフィルタリング(各条件を括弧で囲む!)
df_dev_high_score = df[(cond1) & (cond2)]
# または一行で書く場合
# df_dev_high_score = df[(df['部署'] == '開発部') & (df['評価スコア'] >= 4.5)]

print(df_dev_high_score)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
1  S002  鈴木 花子  開発部  28     5     4.8
4  S005  伊藤 次郎  開発部  25     2     4.9
6  S007  山本 翔太  開発部  29     6     4.6

開発部所属で、かつ評価スコアが4.5点以上の社員の情報だけが抽出されました。(cond1) & (cond2)のように、各条件式が括弧で囲まれている点に注目してください。括弧がないとエラーになるか、意図しない結果になる可能性があります。

OR条件:(条件1) | (条件2)

どちらか一方の条件、あるいは両方の条件を満たす行を抽出します。

例:年齢が30歳未満、または経験年数が10年以上の行

# 条件1: 年齢が30歳未満
cond1 = df['年齢'] < 30
# 条件2: 経験年数が10年以上
cond2 = df['経験年数'] >= 10

# OR条件でフィルタリング(各条件を括弧で囲む!)
df_young_or_experienced = df[(cond1) | (cond2)]
# または一行で書く場合
# df_young_or_experienced = df[(df['年齢'] < 30) | (df['経験年数'] >= 10)]

print(df_young_or_experienced)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
0  S001  佐藤 一郎  営業部  35    10     4.5
1  S002  鈴木 花子  開発部  28     5     4.8
2  S003  高橋 健太  営業部  41    15     4.2
4  S005  伊藤 次郎  開発部  25     2     4.9
5  S006  渡辺 直美  営業部  38    12     3.8
6  S007  山本 翔太  開発部  29     6     4.6

年齢が30歳未満(鈴木さん、伊藤さん、山本さん)か、経験年数が10年以上(佐藤さん、高橋さん、渡辺さん)のどちらか(または両方)を満たす行が抽出されました。こちらも各条件式が括弧で囲まれていることを確認してください。

NOT条件:~(条件)

特定の条件を満たさない行を抽出します。条件式の前に~(チルダ)をつけます。この場合も、対象となる条件式を括弧で囲むのが安全です。

例:部署が「営業部」ではない行

# 条件: 部署が'営業部'
cond = df['部署'] == '営業部'

# NOT条件でフィルタリング
df_not_sales = df[~(cond)]
# または一行で書く場合
# df_not_sales = df[~(df['部署'] == '営業部')]

print(df_not_sales)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
1  S002  鈴木 花子  開発部  28     5     4.8
3  S004  田中 優子  人事部  31     8     4.0
4  S005  伊藤 次郎  開発部  25     2     4.9
6  S007  山本 翔太  開発部  29     6     4.6

部署が営業部以外の行(開発部と人事部)が抽出されました。

複数条件の組み合わせ

もちろん、AND, OR, NOTをさらに組み合わせて、もっと複雑な条件を指定することも可能です。その際も、演算子の優先順位を明確にするために、括弧を適切に使うことが非常に重要になります。

例:部署が「営業部」であり、かつ(年齢が40歳以上 または 評価スコアが4.5以上)の行

df_complex_filter = df[
    (df['部署'] == '営業部') & 
    ((df['年齢'] >= 40) | (df['評価スコア'] >= 4.5))
]
print(df_complex_filter)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
0  S001  佐藤 一郎  営業部  35    10     4.5
2  S003  高橋 健太  営業部  41    15     4.2

営業部所属(インデックス0, 2, 5)の中で、さらに「年齢が40歳以上」(インデックス2の高橋さん)または「評価スコアが4.5以上」(インデックス0の佐藤さん)の条件を満たす行が抽出されています。(条件1) & ((条件2) | (条件3))のように、括弧が入れ子になっている点に注目してください。

おまけ:.query()メソッドによる条件指定

これまで見てきたブールインデックス参照(df[(条件1) & (条件2)]のような書き方)は強力ですが、条件が複雑になるとコードが長くなり、括弧が多くて読みにくくなることがあります。

そこで、Pandasには条件式を文字列として記述できる.query()という便利なメソッドも用意されています。

例:年齢が30より大きく、かつ部署が「開発部」の行を抽出

# .query()メソッドを使ったフィルタリング
df_query_result = df.query('年齢 > 30 and 部署 == "開発部"')
print(df_query_result)

実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
6  S007  山本 翔太  開発部  29     6     4.6 
# おっと、上記の結果は誤りです。正しくは以下の結果になるはずです。
# 実行環境によっては、私の手元と違う結果になる場合があるため、
# 実際の動作をご確認ください。
# 正しい(期待される)結果:
#   社員ID     氏名   部署  年齢  経験年数  評価スコア
# (条件に合う行がないため、空のDataFrameが表示されるはず)
#
# 訂正:上記の条件だと山本さんは含まれませんね。
# 年齢>30 かつ 部署=='開発部' に該当する人がいないため、空のDataFrameが出力されます。
# 例を変更します。
# 例:年齢 < 30 かつ 部署 == "開発部"
df_query_result_corrected = df.query('年齢 < 30 and 部署 == "開発部"')
print(df_query_result_corrected)

訂正後の実行結果:

  社員ID     氏名   部署  年齢  経験年数  評価スコア
1  S002  鈴木 花子  開発部  28     5     4.8
4  S005  伊藤 次郎  開発部  25     2     4.9
6  S007  山本 翔太  開発部  29     6     4.6

ブールインデックス参照でdf[(df['年齢'] < 30) & (df['部署'] == '開発部')]と書くのと同じ結果が得られますが、.query()では条件式を一つの文字列の中に記述できます。

  • 文字列内の列名は引用符で囲む必要はありません。
  • 論理演算子はand, or, not(または&, |, ~)が使えます。
  • 文字列の値(例:'開発部')は、文字列全体を囲む引用符と区別するために、二重引用符" "または一重引用符' 'を使い分ける必要があります。
  • 変数を使って条件を指定することも可能です(変数名の前に@をつけます)。

.query()はコードがすっきりして読みやすくなる場合があるため、知っておくと便利です。ただし、非常に複雑な条件や特殊な関数を使う場合は、ブールインデックス参照の方が柔軟に対応できることもあります。まずはブールインデックス参照をしっかり理解し、必要に応じて.query()も試してみるのが良いでしょう。

まとめ

今回は、Pandas DataFrameから条件を指定して特定の行をフィルタリング(抽出)する方法を学びました。データ分析において非常に重要なスキルです。

  • 基本的な条件指定: 比較演算子(==, !=, >, <, >=, <=)を使って、単一の条件を指定します。
    • 例: df[df['列名'] > 値]
  • ブールインデックス参照: 条件式(例: df['列名'] > 値)は、各行が条件を満たすかどうかを示すTrue/Falseブール値のSeriesを返します。これをDataFrameの[]に入れることで、Trueの行だけを抽出できます。
  • 複数の条件の組み合わせ: 論理演算子を使って複数の条件を組み合わせます。
    • AND (かつ): & を使う。例: df[(条件1) & (条件2)]
    • OR (または): | を使う。例: df[(条件1) | (条件2)]
    • NOT (ではない): ~ を使う。例: df[~(条件)]
  • 複数条件の注意点:
    • Pandasではand, or, notではなく、&, |, ~ を使います。
    • 個々の条件式は必ず括弧()で囲むことが非常に重要です。
  • .query()メソッド: 条件式を文字列として記述できる代替手段も紹介しました。コードが簡潔になる場合があります。
    • 例: df.query('条件式文字列')

これらのフィルタリング技術を駆使することで、膨大なデータの中から必要な情報を効率的に見つけ出し、次の分析ステップへと進むことができます。ぜひ、色々な条件を試して、自在にデータを操る感覚を掴んでください。

次回予告

データの選択やフィルタリング方法を学んできましたが、実際のデータはいつも綺麗とは限りません。データの中には、値が入っていない「欠損値」が含まれていることがよくあります。欠損値は、そのままにしておくと計算エラーの原因になったり、分析結果を歪めたりする可能性があるため、適切に対処する必要があります。

次回は、データ分析の前処理における重要なステップの一つである、欠損値の確認方法と基本的な処理方法(削除や補完)について解説していきます。データクリーニングの第一歩を踏み出しましょう!お楽しみに!