pandas.DataFrameの行を条件で抽出するquery

pandas.DataFrameの列の値に対する条件に応じて行を抽出するにはquery()メソッドを使います。比較演算子や文字列メソッドを使った条件指定、複数条件の組み合わせなどをかなり簡潔に記述できて便利。

pandas.DataFrame.query — pandas 0.23.0 documentation Indexing and Selecting Data — pandas 0.23.0 documentation

ここでは以下の内容について説明します。

numexpr 比較演算子で条件指定 in演算子で条件指定(isin()での条件指定と同等) 文字列メソッドで条件指定 欠損値NaNがある場合の注意

index列に対する条件 変数を使う 複数条件を指定 query()メソッドの注意点 引数inplaceで元のオブジェクトを更新

以下のサンプルコードはpandasバージョン0.23.0。以前のバージョンではエラーになる可能性もあるので注意。 ブールインデックスを使ったレガシーな条件指定については以下の記事を参照。query()の場合、選択範囲に新たな値を代入することはできないので、条件で指定した範囲の値を変更したい場合はブールインデックスを使います。またpandas.Seriesにはquery()メソッドはないので、pandas.Seriesの条件抽出もブールインデックスを使います。

以下のpandas.DataFrameを例とします。

import pandas as pd

df = pd.read_csv('data/src/sample_pandas_normal.csv')

print(df)
#       name  age state  point
# 0    Alice   24    NY     64
# 1      Bob   42    CA     92
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70
# 4    Ellen   24    CA     88
# 5    Frank   30    NY     57

サンプルのcsvファイルのリンクはこちら。

sample_pandas_normal.csv

numexpr

query()はpandas.eval()を使っており、pandas.eval()では式を評価するエンジンとしてnumexprを使うことができます。

pandas.eval — pandas 0.23.0 documentation

数万行を超えるような大規模なデータを処理する場合numexprを使うと速くなる(らしい)。

Enhancing Performance — pandas 0.23.0 documentation

numexprはpipでインストールできます。(環境によってはpip3)

$ pip install numexpr

query()の引数engineで'python'か'numexpr'かを選択できます。デフォルトはNoneでnumexprがインストールされていればnumexprが、無ければpythonが使われる。 文字列メソッドはengine='python'でないとquery()メソッドで使えない。後述。

比較演算子で条件指定

pandasでは比較演算子を使って以下のように行を抽出できます。

print(df[df['age'] < 25])
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

query()メソッドを使うと文字列で同様の条件を指定できます。列名に対する条件を文字列で指定します。

print(df.query('age < 25'))
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

否定はnot。

print(df.query('not age < 25'))
#     name  age state  point
# 1    Bob   42    CA     92
# 3   Dave   68    TX     70
# 5  Frank   30    NY     57

Pythonの条件指定のように範囲を指定可能。

print(df.query('24 <= age < 50'))
#     name  age state  point
# 0  Alice   24    NY     64
# 1    Bob   42    CA     92
# 4  Ellen   24    CA     88
# 5  Frank   30    NY     57

列と列との比較や、算術演算子で計算して比較することもできます。

print(df.query('age < point / 3'))
#       name  age state  point
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

一致、不一致は==, !=。文字列内の文字列も引用符で囲む必要があるので注意。

print(df.query('state == "CA"'))
#       name  age state  point
# 1      Bob   42    CA     92
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

print(df.query('state != "CA"'))
#     name  age state  point
# 0  Alice   24    NY     64
# 3   Dave   68    TX     70
# 5  Frank   30    NY     57

in演算子で条件指定(isinでの条件指定と同等)

isin()は列の要素が引数に渡したリストの要素に含まれているかをbool値(True, False)で返すメソッド。これを利用して、ある列の要素が特定の値に一致する行のみを抽出できます。

print(df[df['state'].isin(['NY', 'TX'])])
#     name  age state  point
# 0  Alice   24    NY     64
# 3   Dave   68    TX     70
# 5  Frank   30    NY     57

query()メソッドではinを使って同等の処理が可能。

print(df.query('state in ["NY", "TX"]'))
#     name  age state  point
# 0  Alice   24    NY     64
# 3   Dave   68    TX     70
# 5  Frank   30    NY     57

==とリストでも問題ありません。

print(df.query('state == ["NY", "TX"]'))
#     name  age state  point
# 0  Alice   24    NY     64
# 3   Dave   68    TX     70
# 5  Frank   30    NY     57

文字列メソッドで条件指定

文字列が完全一致する条件は==やinで指定できるが、部分一致する条件は文字列メソッドstr.xxx()を使います。

str.contains(): 特定の文字列を含む str.endswith(): 特定の文字列で終わる str.startswith(): 特定の文字列で始まる str.match(): 正規表現のパターンに一致する

以下の記事を参照。

これらの文字列メソッドもquery()で使用可能。 ただし、numexprのバージョン2.6.5、pandasのバージョン0.23.0では、引数engine='python'としないとエラーになった。numexprがインストールされていなければ問題ないがインストールされている場合はデフォルトでnumexprが使われるので注意。

print(df.query('name.str.endswith("e")', engine='python'))
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70

print(df.query('name.str.contains("li")', engine='python'))
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70

print(df.query('name.str.match(".*i.*e")', engine='python'))
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70

文字列以外の型dtypeの列はastype()で文字列型strに変換することで文字列メソッドを使える。これもquery()で指定可能。

print(df.query('age.astype("str").str.endswith("8")', engine='python'))
#       name  age state  point
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70

欠損値NaNがある場合の注意

Noneや欠損値NaNがある列に対して文字列メソッドを適用して条件とするとエラーになるので注意。

df.at[0, 'name'] = None
print(df)
#       name  age state  point
# 0     None   24    NY     64
# 1      Bob   42    CA     92
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70
# 4    Ellen   24    CA     88
# 5    Frank   30    NY     57

# print(df.query('name.str.endswith("e")', engine='python'))
# ValueError: cannot index with vector containing NA / NaN values

文字列メソッドの多くは引数naにNoneや欠損値NaNに対する結果を置き換える値を指定できます。これにTrueを指定すると欠損値の行も抽出され、Falseを指定すると欠損値の行は抽出されない。

print(df[df['name'].str.endswith('e', na=False)])
#       name  age state  point
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70

が、query()では引数を指定できない。(何かやり方があるのかもしれないがドキュメントを見てもよく分からない。)

# print(df.query('name.str.endswith("e", na=False)', engine='python'))
# AttributeError: 'dict' object has no attribute 'append'

query()ではなくブールインデックスを使った方法で条件を指定するか、fillna()で欠損値を埋める必要があります。ここでは以降の例のためにもとの値で置換しています。

df['name'].fillna('Alice', inplace=True)
print(df)
#       name  age state  point
# 0    Alice   24    NY     64
# 1      Bob   42    CA     92
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70
# 4    Ellen   24    CA     88
# 5    Frank   30    NY     57

index列に対する条件

index列(行名)に対する条件はindexで指定可能。

print(df.query('index % 2 == 0'))
#       name  age state  point
# 0    Alice   24    NY     64
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

index列に名前が付いている場合はその名前でもindexでもどちらでも問題ありません。

df_name = df.set_index('name')
print(df_name)
#          age state  point
# name                     
# Alice     24    NY     64
# Bob       42    CA     92
# Charlie   18    CA     70
# Dave      68    TX     70
# Ellen     24    CA     88
# Frank     30    NY     57

print(df_name.query('name.str.endswith("e")', engine='python'))
#          age state  point
# name                     
# Alice     24    NY     64
# Charlie   18    CA     70
# Dave      68    TX     70

print(df_name.query('index.str.endswith("e")', engine='python'))
#          age state  point
# name                     
# Alice     24    NY     64
# Charlie   18    CA     70
# Dave      68    TX     70

columns(列名)に対する条件で列を抽出したい場合は以下の記事を参照。

変数を使う

query()メソッドの条件文字列の中で変数を使用するには変数名の前に@をつける。

val = 80
print(df.query('point > @val'))
#     name  age state  point
# 1    Bob   42    CA     92
# 4  Ellen   24    CA     88

複数条件を指定

query()メソッドを使わずに複数条件を指定する場合は以下のように記述します。

print(df[(df['age'] < 25) & (df['point'] > 65)])
#       name  age state  point
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

query()メソッドだと以下のように書ける。条件ごとの括弧は必要なく、「かつ」は&でもandでもどちらでも問題ありません。

print(df.query('age < 25 & point > 65'))
#       name  age state  point
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

print(df.query('age < 25 and point > 65'))
#       name  age state  point
# 2  Charlie   18    CA     70
# 4    Ellen   24    CA     88

「または」も|でもorでもどちらでも問題ありません。

print(df.query('age < 25 | point > 65'))
#       name  age state  point
# 0    Alice   24    NY     64
# 1      Bob   42    CA     92
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70
# 4    Ellen   24    CA     88

print(df.query('age < 25 or point > 65'))
#       name  age state  point
# 0    Alice   24    NY     64
# 1      Bob   42    CA     92
# 2  Charlie   18    CA     70
# 3     Dave   68    TX     70
# 4    Ellen   24    CA     88

否定はnot。

print(df.query('not age < 25 and not point > 65'))
#     name  age state  point
# 5  Frank   30    NY     57

3つ以上での条件も同様だが、andのほうがorより優先順位が高いなど順番によって結果が異なるので、先に処理したいまとまりを括弧で囲んだほうが無難。

print(df.query('age == 24 | point > 80 & state == "CA"'))
#     name  age state  point
# 0  Alice   24    NY     64
# 1    Bob   42    CA     92
# 4  Ellen   24    CA     88

print(df.query('(age == 24 | point > 80) & state == "CA"'))
#     name  age state  point
# 1    Bob   42    CA     92
# 4  Ellen   24    CA     88

queryメソッドの注意点

query()メソッドを使う上で列名に注意が必要。 以下のように列名を変更します。

df.columns = ['名前', 'age.year', 'state name', 3]
print(df)
#         名前  age.year state name   3
# 0    Alice        24         NY  64
# 1      Bob        42         CA  92
# 2  Charlie        18         CA  70
# 3     Dave        68         TX  70
# 4    Ellen        24         CA  88
# 5    Frank        30         NY  57

日本語は問題ありません。

print(df.query('名前 == ["Alice", "Dave"]'))
#       名前  age.year state name   3
# 0  Alice        24         NY  64
# 3   Dave        68         TX  70

print(df.query('名前.str.contains("li")', engine='python'))
#         名前  age.year state name   3
# 0    Alice        24         NY  64
# 2  Charlie        18         CA  70

.やスペースが含まれていたり、数値だったりするとNG。

# print(df.query('age.year < 25'))
# UndefinedVariableError: name 'age' is not defined

# print(df.query('state name == "CA"'))
# SyntaxError: invalid syntax

# print(df.query('3 > 75'))
# KeyError: False

ブールインデックスを使った条件指定であれば問題ない。

print(df[df['age.year'] < 25])
#         名前  age.year state name   3
# 0    Alice        24         NY  64
# 2  Charlie        18         CA  70
# 4    Ellen        24         CA  88

print(df[df['state name'] == 'CA'])
#         名前  age.year state name   3
# 1      Bob        42         CA  92
# 2  Charlie        18         CA  70
# 4    Ellen        24         CA  88

print(df[df[3] > 75])
#       名前  age.year state name   3
# 1    Bob        42         CA  92
# 4  Ellen        24         CA  88

列名はrename()メソッドで変更可能。

df.rename(columns={'3': 'point'}, inplace=True)
print(df)
#         名前  age.year state name   3
# 0    Alice        24         NY  64
# 1      Bob        42         CA  92
# 2  Charlie        18         CA  70
# 3     Dave        68         TX  70
# 4    Ellen        24         CA  88
# 5    Frank        30         NY  57

以下のように列名columnsの., スペースを一括でアンダースコア_に置き換えることもできます。

df.columns = [str(s).replace(' ', '_').replace('.', '_') for s in df.columns]
print(df)
#         名前  age_year state_name   3
# 0    Alice        24         NY  64
# 1      Bob        42         CA  92
# 2  Charlie        18         CA  70
# 3     Dave        68         TX  70
# 4    Ellen        24         CA  88
# 5    Frank        30         NY  57

.やスペース以外にも・などエラーになる文字があるので注意。

引数inplaceで元のオブジェクトを更新

これまでの例ではquery()で行を抽出した新たなpandas.DataFrameが返され、元のオブジェクトはそのままだったが、引数inplace=Trueとすると元のオブジェクト自体が変更される。

df.query('age_year > 25', inplace=True)
print(df)
#       名前  age_year state_name   3
# 1    Bob        42         CA  92
# 3   Dave        68         TX  70
# 5  Frank        30         NY  57

シェア

関連カテゴリー

Python pandas

pandasで窓関数を適用するrollingを使って移動平均などを算出 『Python Data Science Handbook』(英語の無料オンライン版あり) pandas.Seriesのmapメソッドで列の要素を置換 pandasの時系列データにおける頻度(引数freq)の指定方法 pandasの要素としてリストを格納し処理 pandas.DataFrameを結合するmerge, join(列・インデックス基準) pandasで要素、行、列に関数を適用するmap, applymap, apply pandasのMultiindexの指定・追加・解除・ソート・レベル変更 pandasのcrosstabでクロス集計(カテゴリ毎の出現回数・頻度を算出) pandas.DataFrameの行と列を入れ替える(転置) pandasの文字列から正規表現で抽出して新たな列を生成 pandasで複数条件のand, or, notから行を抽出(選択) pandasのjson_normalizeで辞書のリストをDataFrameに変換 pandas.DataFrameをJSON文字列・ファイルに変換・保存(to_json) pandasで時系列データのOHLC(四本値)を算出・ダウンサンプリング

Last Updated: 6/26/2019, 10:34:03 PM