pandas で条件抽出するとき、通常は df[df['col'] > 0] のようにブールインデックスを使います。query メソッドを使うと、より読みやすい文字列で同じ処理を書けます。
query の基本
import pandas as pd import numpy as np df = pd.DataFrame({ 'name': ['田中', '佐藤', '鈴木', '高橋'], 'age': [25, 30, 35, 28], 'score': [85, 72, 90, 68] }) # 通常の方法 result = df[df['age'] >= 30] # query を使う result = df.query('age >= 30') print(result) # name age score # 1 佐藤 30 72 # 2 鈴木 35 90
複雑な条件になるほど、query の可読性が際立ちます。
# 通常の方法(括弧が多くて読みにくい) result = df[(df['age'] >= 25) & (df['age'] <= 35) & (df['score'] >= 70)] # query を使う result = df.query('25 <= age <= 35 and score >= 70')
複数条件の指定
and / or を使って複数条件を組み合わせられます。
# AND 条件 df.query('age >= 30 and score >= 80') # OR 条件 df.query('age < 25 or score >= 90') # NOT 条件 df.query('not (age >= 30)')
Python の演算子 & | ~ の代わりに、英単語 and or not が使えるのも query の利点です。
& | ~ を使う。各条件を () で囲む必要がある。
and or not を使う。数学的な比較演算も直感的に書ける。
変数の参照
query 内で Python の変数を参照するには @ を付けます。
min_age = 30 min_score = 80 # 変数を参照 result = df.query('age >= @min_age and score >= @min_score') print(result) # name age score # 2 鈴木 35 90
リストを使った in 演算も @ で参照できます。
target_names = ['田中', '鈴木'] # リストに含まれるか result = df.query('name in @target_names') print(result) # name age score # 0 田中 25 85 # 2 鈴木 35 90
文字列の条件
文字列の比較には引用符を使います。
# 文字列の完全一致 df.query('name == "田中"') # シングルクォートでも可 df.query("name == '田中'")
ただし、文字列メソッド(startswith, contains など)は query 内では使えません。その場合は通常のブールインデックスを使います。
# これはエラーになる # df.query('name.str.startswith("田")') # 通常の方法を使う df[df['name'].str.startswith('田')]
スペースを含む列名
列名にスペースや特殊文字が含まれる場合、バッククォートで囲みます。
df = pd.DataFrame({ 'first name': ['田中', '佐藤'], 'total score': [85, 72] }) # バッククォートで囲む result = df.query('`total score` >= 80') print(result) # first name total score # 0 田中 85
index の参照
index を条件に使うこともできます。
df = pd.DataFrame({ 'value': [10, 20, 30, 40] }, index=['a', 'b', 'c', 'd']) # index を条件に使う result = df.query('index in ["a", "c"]') print(result) # value # a 10 # c 30
数値インデックスの場合も同様です。
df = pd.DataFrame({ 'value': [10, 20, 30, 40] }) # 数値インデックス result = df.query('index >= 2') print(result) # value # 2 30 # 3 40
パフォーマンス
query は内部で numexpr ライブラリを使っており、大きなデータセットでは通常のブールインデックスより高速になることがあります。
import pandas as pd import numpy as np import time # 大きなデータ df = pd.DataFrame({ 'a': np.random.randn(1_000_000), 'b': np.random.randn(1_000_000) }) # ブールインデックス start = time.time() for _ in range(100): result = df[(df['a'] > 0) & (df['b'] > 0)] print(f"ブールインデックス: {time.time() - start:.2f}秒") # query start = time.time() for _ in range(100): result = df.query('a > 0 and b > 0') print(f"query: {time.time() - start:.2f}秒")
ただし、小さなデータセットでは query の方がオーバーヘッドで遅くなることもあります。
| データサイズ | 推奨 |
|---|---|
| 小(〜1万行) | どちらでも可。可読性で選ぶ |
| 中(1万〜100万行) | query が有利なことが多い |
| 大(100万行〜) | query + numexpr で高速化 |
inplace オプション
query は新しい DataFrame を返しますが、inplace=True で元の DataFrame を直接変更できます。
# 通常は新しい DataFrame を返す result = df.query('a > 0') # inplace で元を変更(非推奨) # df.query('a > 0', inplace=True)
ただし、inplace は将来廃止される可能性があり、使用は推奨されません。
query が使えないケース
いくつかの操作は query ではできません。
str.contains(), str.startswith() などは使えない
isna(), notna() は使えない。代わりに col == col(NaN でない行)を使う
# NaN を除外するトリック df = pd.DataFrame({'a': [1, np.nan, 3]}) # a == a は NaN のとき False になる df.query('a == a') # NaN を除外
query は可読性とパフォーマンスの両面でメリットがあります。複雑な条件抽出を書くときは、まず query で書けないか検討してみてください。