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ファイルのリンクはこちら。
numexpr
query()
はpandas.eval()
を使っており、pandas.eval()
では式を評価するエンジンとしてnumexprを使うことができる。
数万行を超えるような大規模なデータを処理する場合numexprを使うと速くなる(らしい)。
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の条件指定のように2つの比較演算子で範囲を指定可能。
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
==
とリストでもOK。
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
でもどちらでもOK。
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
でもどちらでもOK。
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
でもどちらでもOK。
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
日本語はOK。
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 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
以下のように列名columns
の.
, スペースを一括でアンダースコア_
に置き換えることもできる。
df.columns = [str(s).replace(' ', '_').replace('.', '_') for s in df.columns]
print(df)
# 名前 age_year state_name 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
.
やスペース以外にも・
などエラーになる文字があるので注意。
引数inplaceで元のオブジェクトを更新
これまでの例ではquery()
で行を抽出した新たなpandas.DataFrame
が返され、元のオブジェクトはそのままだったが、引数inplace=True
とすると元のオブジェクト自体が変更される。
df.query('age_year > 25', inplace=True)
print(df)
# 名前 age_year state_name point
# 1 Bob 42 CA 92
# 3 Dave 68 TX 70
# 5 Frank 30 NY 57