Pandas 行フィルタリング:Python で条件によってデータを選択する
Updated on
pandas DataFrame の行をフィルタリングすることは、データ分析における最も一般的な操作の 1 つです。データのクリーニング、パターンの探索、機械学習用のデータセットの準備など、条件に基づいて特定の行を選択する必要があります。課題は、pandas の複数のフィルタリングアプローチの中から適切な方法を選択することです。それぞれの方法には、異なる構文、パフォーマンス特性、およびユースケースがあります。
多くのデータサイエンティストは、特に大規模なデータセットや複雑な条件を扱う場合、フィルタリングの効率に苦労しています。間違った方法を使用すると、分析が桁違いに遅くなる可能性があります。ブール型インデックスと .query() または .loc[] をいつ使用するかを理解することは、数秒で実行されるスクリプトと数分かかるスクリプトの違いを意味する可能性があります。
このガイドでは、実用的な例、パフォーマンス比較、ベストプラクティスを含むすべての pandas フィルタリング方法をカバーしています。ブール型インデックス、.query() メソッド、.loc[] 選択、.where() 関数、および .filter() メソッドを学習します。最後には、あらゆるフィルタリングシナリオに対してどのアプローチを使用するべきかを正確に理解できます。
Pandas 行フィルタリング方法の理解
Pandas は DataFrame 行をフィルタリングするための 5 つの主要な方法を提供しており、それぞれ異なるシナリオに適しています。
| メソッド | 最適な用途 | 構文例 | パフォーマンス |
|---|---|---|---|
| ブール型インデックス | シンプルな条件、可読性 | df[df['age'] > 25] | 小〜中規模データで高速 |
.query() | 複雑な条件、文字列ベース | df.query('age > 25 and city == "NYC"') | 大規模データでより高速 |
.loc[] | ラベルベース、条件付き | df.loc[df['age'] > 25, ['name', 'age']] | 柔軟な列選択 |
.where() | 構造を保持、値を置換 | df.where(df['age'] > 25, np.nan) | DataFrame の形状を保持 |
.filter() | 列/インデックス名でフィルタ | df.filter(like='total', axis=1) | 列/インデックス名のパターン |
各方法を詳細な例で探求しましょう。
ブール型インデックス:最も一般的なアプローチ
ブール型インデックスは、ブールマスク(True/False 値)を作成し、それを DataFrame に適用することで行をフィルタリングします。これは初心者にとって最も直感的な方法です。
import pandas as pd
import numpy as np
# サンプル DataFrame を作成
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['NYC', 'LA', 'NYC', 'Chicago', 'LA'],
'salary': [70000, 80000, 90000, 75000, 85000]
})
# age が 30 より大きい行をフィルタリング
filtered = df[df['age'] > 30]
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# ブールマスクを表示
print(df['age'] > 30)
# 0 False
# 1 False
# 2 True
# 3 False
# 4 Trueブール型インデックスは、条件 df['age'] > 30 を評価することで機能し、True/False 値の Series を返します。このマスクを df[mask] で DataFrame に渡すと、pandas はマスクが True である行のみを返します。
複数の条件でフィルタリング
論理演算子を使用して複数の条件を組み合わせます。重要:Python の and、or、not キーワードの代わりに &(and)、|(or)、~(not)を使用してください。各条件を常にカッコで囲んでください。
# AND での複数条件
filtered = df[(df['age'] > 25) & (df['city'] == 'NYC')]
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# OR での複数条件
filtered = df[(df['age'] > 30) | (df['salary'] > 80000)]
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# NOT 演算子
filtered = df[~(df['city'] == 'NYC')]
print(filtered)
# name age city salary
# 1 Bob 30 LA 80000
# 3 David 28 Chicago 75000
# 4 Eve 32 LA 85000複数の値に .isin() を使用
.isin() を使用して、列がリスト内のいずれかの値と一致する行をフィルタリングします。
# city が NYC または LA の行をフィルタリング
cities = ['NYC', 'LA']
filtered = df[df['city'].isin(cities)]
print(filtered)
# name age city salary
# 0 Alice 25 NYC 70000
# 1 Bob 30 LA 80000
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# 逆:リストにない都市
filtered = df[~df['city'].isin(cities)]
print(filtered)
# name age city salary
# 3 David 28 Chicago 75000.between() でフィルタリング
.between() メソッドは範囲内の値をフィルタリングします(デフォルトで境界を含む)。
# 28 から 32 の間の年齢をフィルタリング
filtered = df[df['age'].between(28, 32)]
print(filtered)
# name age city salary
# 1 Bob 30 LA 80000
# 3 David 28 Chicago 75000
# 4 Eve 32 LA 85000
# 境界を除外
filtered = df[df['age'].between(28, 32, inclusive='neither')]
print(filtered)
# name age city salary
# 1 Bob 30 LA 80000.query() メソッド:文字列ベースのフィルタリング
.query() メソッドは文字列式を受け入れ、複雑な条件に対して読みやすくなります。numexpr を使用した最適化により、大規模な DataFrame で特に効率的です。
# シンプルなクエリ
filtered = df.query('age > 30')
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# 複数の条件
filtered = df.query('age > 25 and city == "NYC"')
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# @ シンボルで変数を使用
min_age = 30
filtered = df.query('age > @min_age')
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000高度な .query() 式
# query で .isin() を使用
cities = ['NYC', 'LA']
filtered = df.query('city in @cities')
print(filtered)
# 範囲条件
filtered = df.query('28 <= age <= 32')
print(filtered)
# 文字列メソッド
filtered = df.query('city.str.contains("LA")', engine='python')
print(filtered)ラベルベースのフィルタリング用の .loc[]
.loc[] インデクサーは行のフィルタリングと列の選択を組み合わせます。フィルタリングされた行から特定の列が必要な場合に使用します。
# 行をフィルタリングして列を選択
filtered = df.loc[df['age'] > 30, ['name', 'age']]
print(filtered)
# name age
# 2 Charlie 35
# 4 Eve 32
# 複数の条件
filtered = df.loc[(df['age'] > 25) & (df['salary'] > 75000), ['name', 'salary']]
print(filtered)
# name salary
# 1 Bob 80000
# 2 Charlie 90000
# 4 Eve 85000
# すべての列
filtered = df.loc[df['city'] == 'NYC', :]
print(filtered).where() メソッド:条件付き値置換
他の方法とは異なり、.where() は条件を満たさない値を NaN(または指定された値)に置き換えることで DataFrame の形状を保持します。
# age > 30 の値を保持し、他を NaN に置換
result = df.where(df['age'] > 30)
print(result)
# name age city salary
# 0 NaN NaN NaN NaN
# 1 NaN NaN NaN NaN
# 2 Charlie 35.0 NYC 90000.0
# 3 NaN NaN NaN NaN
# 4 Eve 32.0 LA 85000.0
# カスタム値で置換
result = df.where(df['age'] > 30, 'FILTERED')
print(result)
# .where() の後に NaN を含む行を削除
result = df.where(df['age'] > 30).dropna()
print(result).filter() メソッド:列/インデックス名でフィルタリング
.filter() メソッドは、値ではなくラベル(名前)によって列または行をフィルタリングします。パターンベースの列選択に使用します。
# 複数の列を持つ DataFrame を作成
df_wide = pd.DataFrame({
'total_sales': [100, 200, 300],
'total_profit': [20, 40, 60],
'monthly_sales': [10, 20, 30],
'yearly_sales': [120, 240, 360],
'region': ['East', 'West', 'North']
})
# 'total' を含む列をフィルタリング
filtered = df_wide.filter(like='total')
print(filtered)
# total_sales total_profit
# 0 100 20
# 1 200 40
# 2 300 60
# 正規表現を使用して列をフィルタリング
filtered = df_wide.filter(regex=r'.*sales$')
print(filtered)
# total_sales monthly_sales yearly_sales
# 0 100 10 120
# 1 200 20 240
# 2 300 30 360
# 正確な名前で列をフィルタリング
filtered = df_wide.filter(items=['total_sales', 'region'])
print(filtered)文字列データのフィルタリング
Pandas は、テキスト列をフィルタリングするための .str アクセサーを通じて文字列メソッドを提供します。
# テキストデータを含む DataFrame を作成
df_text = pd.DataFrame({
'name': ['Alice Smith', 'Bob Johnson', 'Charlie Brown', 'David Lee'],
'email': ['alice@gmail.com', 'bob@yahoo.com', 'charlie@gmail.com', 'david@outlook.com']
})
# name に 'Smith' を含む行をフィルタリング
filtered = df_text[df_text['name'].str.contains('Smith')]
print(filtered)
# name email
# 0 Alice Smith alice@gmail.com
# 大文字小文字を区別しない検索
filtered = df_text[df_text['name'].str.contains('smith', case=False)]
print(filtered)
# メールドメインでフィルタリング
filtered = df_text[df_text['email'].str.endswith('gmail.com')]
print(filtered)
# name email
# 0 Alice Smith alice@gmail.com
# 2 Charlie Brown charlie@gmail.com
# 正規表現でフィルタリング
filtered = df_text[df_text['name'].str.match(r'^[A-C]')]
print(filtered)Null 値と非 Null 値のフィルタリング
欠損データに基づいてフィルタリングするには、.isna()、.notna()、.isnull()、および .notnull() を使用します。
# 欠損値を含む DataFrame を作成
df_missing = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, 7, 8],
'C': [9, 10, 11, 12]
})
# 列 A が null でない行をフィルタリング
filtered = df_missing[df_missing['A'].notna()]
print(filtered)
# A B C
# 0 1.0 5.0 9
# 1 2.0 NaN 10
# 3 4.0 8.0 12
# いずれかの列が null である行をフィルタリング
filtered = df_missing[df_missing.isna().any(axis=1)]
print(filtered)
# A B C
# 1 2.0 NaN 10
# 2 NaN 7.0 11
# すべての列が null でない行をフィルタリング
filtered = df_missing[df_missing.notna().all(axis=1)]
print(filtered)
# A B C
# 0 1.0 5.0 9
# 3 4.0 8.0 12日付範囲でのフィルタリング
datetime 列を扱う場合、標準的な比較演算子を使用して日付範囲でフィルタリングできます。
# 日付を含む DataFrame を作成
df_dates = pd.DataFrame({
'date': pd.date_range('2026-01-01', periods=10, freq='D'),
'value': range(10)
})
# 特定の日付より後の日付をフィルタリング
filtered = df_dates[df_dates['date'] > '2026-01-05']
print(filtered)
# 日付範囲をフィルタリング
start_date = '2026-01-03'
end_date = '2026-01-07'
filtered = df_dates[(df_dates['date'] >= start_date) & (df_dates['date'] <= end_date)]
print(filtered)
# 日付に .between() を使用
filtered = df_dates[df_dates['date'].between('2026-01-03', '2026-01-07')]
print(filtered)パフォーマンス比較:大規模な DataFrame
異なるフィルタリング方法には異なるパフォーマンス特性があります。100 万行の DataFrame の比較は次のとおりです。
import time
# 大規模な DataFrame を作成
np.random.seed(42)
df_large = pd.DataFrame({
'A': np.random.randint(0, 100, 1000000),
'B': np.random.randint(0, 100, 1000000),
'C': np.random.choice(['X', 'Y', 'Z'], 1000000)
})
# ブール型インデックス
start = time.time()
result = df_large[(df_large['A'] > 50) & (df_large['B'] < 30)]
print(f"ブール型インデックス: {time.time() - start:.4f} 秒")
# .query() メソッド
start = time.time()
result = df_large.query('A > 50 and B < 30')
print(f".query() メソッド: {time.time() - start:.4f} 秒")
# .loc[] メソッド
start = time.time()
result = df_large.loc[(df_large['A'] > 50) & (df_large['B'] < 30)]
print(f".loc[] メソッド: {time.time() - start:.4f} 秒")パフォーマンスの洞察:
- ブール型インデックス:シンプルな条件では高速、複雑なマルチ条件フィルターでは低速
- .query():複数の条件を持つ大規模な DataFrame で最速(numexpr 最適化を使用)
- .loc[]:ブール型インデックスに類似しているが、列選択においてより柔軟
- .where():DataFrame 全体の走査により低速、形状を保持する必要がある場合のみ使用
複数の条件を持つ 100,000 行以上のデータセットの場合、.query() は通常、ブール型インデックスを 20〜40% 上回るパフォーマンスを発揮します。
一般的なミスとその回避方法
ミス 1:'&' の代わりに 'and' を使用
# 間違い - ValueError が発生
# filtered = df[df['age'] > 25 and df['city'] == 'NYC']
# 正しい
filtered = df[(df['age'] > 25) & (df['city'] == 'NYC')]ミス 2:カッコを忘れる
# 間違い - 演算子の優先順位の問題
# filtered = df[df['age'] > 25 & df['city'] == 'NYC']
# 正しい - 各条件を囲む
filtered = df[(df['age'] > 25) & (df['city'] == 'NYC')]ミス 3:元の DataFrame を変更
# フィルタリングはコピーではなくビューを作成
filtered = df[df['age'] > 30]
# 間違い - SettingWithCopyWarning
# filtered['new_col'] = 100
# 正しい - 明示的なコピーを作成
filtered = df[df['age'] > 30].copy()
filtered['new_col'] = 100ミス 4:NaN を処理せずに文字列フィルタリング
df_with_nan = pd.DataFrame({
'name': ['Alice', np.nan, 'Charlie']
})
# 間違い - NaN が存在する場合エラーが発生
# filtered = df_with_nan[df_with_nan['name'].str.contains('li')]
# 正しい - na パラメータで NaN を処理
filtered = df_with_nan[df_with_nan['name'].str.contains('li', na=False)]PyGWalker でフィルタリングされたデータを可視化
pandas DataFrame をフィルタリングした後、結果を可視化することでパターンと洞察を明らかにするのに役立ちます。PyGWalker (opens in a new tab) は、フィルタリングされた DataFrame を Python ノートブック内で直接 Tableau のようなインタラクティブなインターフェースに変換します。データをエクスポートしたり、複雑なプロットコードを書いたりする必要はありません。
PyGWalker は、フィルタリングされたデータセットを探索する際に特に便利です。次のことができます。
- 列をドラッグアンドドロップして瞬時にチャートを作成
- コードを書かずに視覚的に追加のフィルターを適用
- チャートタイプ(棒、線、散布図、ヒートマップ)を数秒で切り替え
- レポートやプレゼンテーション用に可視化をエクスポート
import pygwalker as pyg
# DataFrame をフィルタリング
filtered_df = df[(df['age'] > 25) & (df['salary'] > 75000)]
# インタラクティブな可視化を起動
pyg.walk(filtered_df)これにより、フィルタリングされた列をチャートビルダーにドラッグして可視化を作成できるインタラクティブなインターフェースが開きます。複数のフィルター条件を扱うデータアナリストにとって、PyGWalker はフィルター → プロット → 調整 → 再プロットの反復サイクルを排除します。
よくある質問
条件によって pandas DataFrame の行をフィルタリングするにはどうすればよいですか?
シンプルな条件には、df[df['column'] > value] でブール型インデックスを使用します。複数の条件の場合は、カッコ付きで &(and)、|(or)、~(not)を使用します:df[(df['A'] > 10) & (df['B'] < 20)]。または、読みやすい構文には .query() を使用します:df.query('A > 10 and B < 20')。
.loc[] とブール型インデックスの違いは何ですか?
ブール型インデックス(df[df['col'] > 5])は、フィルタリングされた行のすべての列を返します。.loc[] は同時に列選択を可能にします:df.loc[df['col'] > 5, ['col1', 'col2']]。両方の方法は同様のパフォーマンスを持ちますが、.loc[] はより明示的で、ラベルベースの行選択をサポートします。
複数の条件で pandas DataFrame をフィルタリングするにはどうすればよいですか?
&(and)、|(or)、~(not)演算子を使用して条件を組み合わせます。常に各条件をカッコで囲んでください:df[(df['age'] > 25) & (df['city'] == 'NYC') | (df['salary'] > 80000)]。複雑な条件には .query() を使用します:df.query('age > 25 and (city == "NYC" or salary > 80000)')。
ブール型インデックスの代わりに .query() をいつ使用すべきですか?
複数の条件を持つ大規模な DataFrame(>100k 行)には .query() を使用します。numexpr 最適化により 20〜40% 高速です。複雑な条件にも読みやすいです。シンプルなフィルターや最大の互換性が必要な場合はブール型インデックスを使用します(.query() はデバッグが難しい文字列式を必要とします)。
列の値がリストにある行をフィルタリングするにはどうすればよいですか?
.isin() メソッドを使用します:df[df['city'].isin(['NYC', 'LA', 'Chicago'])]。逆(リストにない)の場合は、~ 演算子を使用します:df[~df['city'].isin(['NYC', 'LA'])]。これは、大きなリストに対して複数の | 条件を連結するよりも効率的です。
結論
pandas DataFrame の行をフィルタリングすることは、Python でのデータ分析における基本的なスキルです。シンプルさのためのブール型インデックス、パフォーマンスのための .query()、柔軟な列選択のための .loc[]、値の置換のための .where()、列名パターンのための .filter() の 5 つのフィルタリング方法を理解しました。
重要なポイント:
- 小〜中規模のデータセットでの迅速で読みやすいフィルターにはブール型インデックスを使用
- 複雑な条件を持つ大規模なデータセットには .query() を選択(20〜40% 高速)
- 行フィルタリングと列選択の両方が必要な場合は .loc[] を適用
and、or、notの代わりに&、|、~演算子を使用することを忘れずに- 演算子の優先順位の問題を回避するために、常に条件をカッコで囲む
これらのフィルタリング技術をマスターすれば、あらゆるデータ選択シナリオを効率的に処理できます。フィルタリングされたデータのインタラクティブな探索には、追加のプロットコードを書くことなく結果を可視化するために PyGWalker の使用を検討してください。