自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

[pandas] その3 evalとqueryでのデータ抽出方法をまとめてみた

Python
Python
ヒノマルク
ヒノマルク

前回はセルの値を条件にして抽出する方法をまとめました。
今回はpandasのevalメソッドとqueryメソッドでの抽出方法をまとめてみたいと思います。

スポンサーリンク

evalメソッドについて

evalメソッドはdf.eval("抽出条件")のように使います。

抽出条件はSQLのWHERE句のように書くことができるのでSQLに慣れている方であれば、とっつきやすいかもしれません。

戻り値はboolean vectorなので、df[df.eval("抽出条件")]のように書いてあげると抽出条件に合致する行を抽出することができます。

スポンサーリンク

queryメソッドについて

queryメソッドはdf.query("抽出条件")のように使います。

戻り値はboolean vectorではなく、DataFrameでした。(Seriesに使うとSeriesが返ってくるかもしれません。)

evalメソッドとは違い、df.query("抽出条件")と書いてあげればフィルタされたDataFrameが返ってきました。

スポンサーリンク

evalメソッドとqueryメソッドの使い分けについて

調べてみたのですが、情報としては検索できなかったので自分でpandasのコードを確認して調査してみました。

pandasのframe.pyでqueryメソッドが定義されているようなのですが、内部処理ではevalメソッドを呼び出しているようです。

def query(self, expr: str, inplace: bool = False, **kwargs):
    ・・・省略・・・
    This method uses the top-level :func:`eval` function to
    evaluate the passed query.
    ・・・省略・・・
    res = self.eval(expr, **kwargs)
    ・・・省略・・・
引用: https://github.com/pandas-dev/pandas/blob/main/pandas/core/frame.py

コメントでもqueryメソッドがevalを使ってると書かれていました。コードでもself.eval()と呼び出しています。

ですので、evalメソッドもqueryメソッドも本質的にはどちらも変わらないものになるのかなと思います。

私はシンプルに書けるqueryメソッドの方が好きですが、pandasの使い方を検索しているとevalメソッドについての情報が多めだったかなという印象です。

スポンサーリンク

NumExprについて

evalメソッドは内部の数値計算エンジンとしてNumExprというライブラリをデフォルトで使用するようにしています。

numexprをインストールしていなくても動作しますが、使うと数値データを扱う処理は高速になるようです。

なぜ高速になるのかというと少ないメモリを使うようにしたりマルチスレッドで計算させることにより実現しているようです。

詳しい説明はNumExprのGitHubに記載されているので確認してみてください。

スポンサーリンク

evalメソッドとqueryメソッドを使う上での注意点

  1. 15,000レコード以上のデータセットでないと逆にパフォーマンスが悪くなるとpandasの公式ページで見解が述べられています。

ループ処理などで使わない限り、15,000レコード以下のデータセットで使用しても問題ないとは個人的には思います。

  1. NumExprを使うことによって数値計算を高速化していると思われるので、当然ですが文字列に対する処理にはNumExprは使用されません。こちらのページに詳しく載っています。
スポンサーリンク

ボストンの住宅価格データセットでevalメソッドとqueryメソッドを使ってみる

NumExprライブラリをインストールしていない場合はインストールする

NumExprのインストール
$ /Users/hinomaruc/Desktop/notebooks/my-venv/bin/python3 -m pip install numexpr
Out[0]

Collecting numexpr
  Downloading numexpr-2.8.1-cp39-cp39-macosx_10_9_x86_64.whl (98 kB)
     |████████████████████████████████| 98 kB 1.3 MB/s
Requirement already satisfied: packaging in ./Desktop/notebooks/my-venv/lib/python3.9/site-packages (from numexpr) (21.3)
Requirement already satisfied: numpy>=1.13.3 in ./Desktop/notebooks/my-venv/lib/python3.9/site-packages (from numexpr) (1.22.1)
Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in ./Desktop/notebooks/my-venv/lib/python3.9/site-packages (from packaging->numexpr) (3.0.7)
Installing collected packages: numexpr
Successfully installed numexpr-2.8.1

ボストンの住宅価格データセットの読み込み

ボストンの住宅価格データをDataFrameに読み込む
import pandas as pd
df = pd.read_csv("http://lib.stat.cmu.edu/datasets/boston_corrected.txt", encoding='Windows-1252',skiprows=9,sep="\t")
データセットのカラムを確認
df.columns
Out[0]
Index(['OBS.', 'TOWN', 'TOWN#', 'TRACT', 'LON', 'LAT', 'MEDV', 'CMEDV', 'CRIM',
       'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX',
       'PTRATIO', 'B', 'LSTAT'],
      dtype='object')

evalメソッドを使ってみる (文字列データのカラム)

Swampscottのレコードを抽出
df.eval("TOWN == 'Swampscott'")
Out[0]
0      False
1       True
2       True
3      False
4      False
       ...
501    False
502    False
503    False
504    False
505    False
Length: 506, dtype: bool

Boolean vectorで返却されます。

DataFrameを抽出する
df[df.eval("TOWN == 'Swampscott'")]
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
1 2 Swampscott 1 2021 -70.950 42.2875 21.6 21.6 0.02731 0.0 ... 0 0.469 6.421 78.9 4.9671 2 242 17.8 396.90 9.14
2 3 Swampscott 1 2022 -70.936 42.2830 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03

2 rows × 21 columns

evalメソッドを使ってみる (数値データのカラム)

数値データのカラムに対してevalメソッドを使ってみます。
ついでに複合条件にしてみました。平均部屋数が7.5以上で8以下のデータを抽出します。

数値データに適用
df.eval("RM >= 7.5 and RM <= 8")
Out[0]
0      False
1      False
2      False
3      False
4      False
       ...
501    False
502    False
503    False
504    False
505    False
Length: 506, dtype: bool
データフレームを抽出
df[df.eval("RM >= 7.5 and RM <= 8")]
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
98 99 Winchester 23 3384 -71.0982 42.2685 43.8 43.8 0.08187 0.0 ... 0 0.4450 7.820 36.9 3.4952 2 276 18.0 393.53 3.57
162 163 Cambridge 28 3541 -71.0770 42.2250 50.0 50.0 1.83377 0.0 ... 1 0.6050 7.802 98.2 2.0407 5 403 14.7 389.61 1.92
166 167 Cambridge 28 3545 -71.0770 42.2305 50.0 50.0 2.01019 0.0 ... 0 0.6050 7.929 96.2 2.0459 5 403 14.7 369.30 3.70
180 181 Belmont 30 3572 -71.0995 42.2345 39.8 39.8 0.06588 0.0 ... 0 0.4880 7.765 83.3 2.7410 3 193 17.8 395.56 7.56
186 187 Belmont 30 3578 -71.1115 42.2442 50.0 50.0 0.05602 0.0 ... 0 0.4880 7.831 53.6 3.1992 3 193 17.8 392.63 4.45
195 196 Lincoln 33 3602 -71.1890 42.2525 50.0 50.0 0.01381 80.0 ... 0 0.4220 7.875 32.0 5.6484 4 255 14.4 394.23 2.97
202 203 Wayland 36 3662 -71.2140 42.2180 42.3 42.3 0.02177 82.5 ... 0 0.4150 7.610 15.7 6.2700 2 348 14.7 395.38 3.11
203 204 Weston 37 3671 -71.1990 42.2320 48.5 48.5 0.03510 95.0 ... 0 0.4161 7.853 33.2 5.1180 4 224 14.7 392.78 3.81
228 229 Newton 40 3739 -71.1100 42.1850 46.7 46.7 0.29819 0.0 ... 0 0.5040 7.686 17.0 3.3751 8 307 17.4 377.51 3.92
261 262 Brookline 45 4005 -71.0790 42.2020 43.1 43.1 0.53412 20.0 ... 0 0.6470 7.520 89.4 2.1398 5 264 13.0 388.37 7.26
273 274 Dedham 46 4025 -71.1170 42.1510 35.2 35.2 0.22188 20.0 ... 1 0.4640 7.691 51.8 4.3665 3 223 18.6 390.77 6.58
280 281 Wellesley 48 4042 -71.1660 42.1870 45.4 45.4 0.03578 20.0 ... 0 0.4429 7.820 64.5 4.6947 5 216 14.9 387.31 3.76
282 283 Wellesley 48 4044 -71.1775 42.1735 46.0 46.0 0.06129 20.0 ... 1 0.4429 7.645 49.7 5.2119 5 216 14.9 377.07 3.01
283 284 Dover 49 4051 -71.1730 42.1475 50.0 50.0 0.01501 90.0 ... 1 0.4010 7.923 24.8 5.8850 1 198 13.6 395.52 3.16

14 rows × 21 columns

evalメソッドを使ってみる (文字列と数値データカラムの複合条件)

町名がSwampscottかつ平均部屋数が7以上
df.eval("TOWN == 'Swampscott' and RM >= 7")
Out[0]
0      False
1      False
2       True
3      False
4      False
       ...
501    False
502    False
503    False
504    False
505    False
Length: 506, dtype: bool
データフレーム抽出
df[df.eval("TOWN == 'Swampscott' and RM >= 7")]
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
2 3 Swampscott 1 2022 -70.936 42.283 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03

1 rows × 21 columns

queryメソッドの使い方 (文字列データのカラム)

町名がSwampscottを抽出
df.query("TOWN == 'Swampscott'")
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
1 2 Swampscott 1 2021 -70.950 42.2875 21.6 21.6 0.02731 0.0 ... 0 0.469 6.421 78.9 4.9671 2 242 17.8 396.90 9.14
2 3 Swampscott 1 2022 -70.936 42.2830 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03

2 rows × 21 columns

queryメソッドの使い方 (数値データのカラム)

平均部屋数が7.5以上8以下の抽出
df.query("RM >= 7.5 and RM <= 8")
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
98 99 Winchester 23 3384 -71.0982 42.2685 43.8 43.8 0.08187 0.0 ... 0 0.4450 7.820 36.9 3.4952 2 276 18.0 393.53 3.57
162 163 Cambridge 28 3541 -71.0770 42.2250 50.0 50.0 1.83377 0.0 ... 1 0.6050 7.802 98.2 2.0407 5 403 14.7 389.61 1.92
166 167 Cambridge 28 3545 -71.0770 42.2305 50.0 50.0 2.01019 0.0 ... 0 0.6050 7.929 96.2 2.0459 5 403 14.7 369.30 3.70
180 181 Belmont 30 3572 -71.0995 42.2345 39.8 39.8 0.06588 0.0 ... 0 0.4880 7.765 83.3 2.7410 3 193 17.8 395.56 7.56
186 187 Belmont 30 3578 -71.1115 42.2442 50.0 50.0 0.05602 0.0 ... 0 0.4880 7.831 53.6 3.1992 3 193 17.8 392.63 4.45
195 196 Lincoln 33 3602 -71.1890 42.2525 50.0 50.0 0.01381 80.0 ... 0 0.4220 7.875 32.0 5.6484 4 255 14.4 394.23 2.97
202 203 Wayland 36 3662 -71.2140 42.2180 42.3 42.3 0.02177 82.5 ... 0 0.4150 7.610 15.7 6.2700 2 348 14.7 395.38 3.11
203 204 Weston 37 3671 -71.1990 42.2320 48.5 48.5 0.03510 95.0 ... 0 0.4161 7.853 33.2 5.1180 4 224 14.7 392.78 3.81
228 229 Newton 40 3739 -71.1100 42.1850 46.7 46.7 0.29819 0.0 ... 0 0.5040 7.686 17.0 3.3751 8 307 17.4 377.51 3.92
261 262 Brookline 45 4005 -71.0790 42.2020 43.1 43.1 0.53412 20.0 ... 0 0.6470 7.520 89.4 2.1398 5 264 13.0 388.37 7.26
273 274 Dedham 46 4025 -71.1170 42.1510 35.2 35.2 0.22188 20.0 ... 1 0.4640 7.691 51.8 4.3665 3 223 18.6 390.77 6.58
280 281 Wellesley 48 4042 -71.1660 42.1870 45.4 45.4 0.03578 20.0 ... 0 0.4429 7.820 64.5 4.6947 5 216 14.9 387.31 3.76
282 283 Wellesley 48 4044 -71.1775 42.1735 46.0 46.0 0.06129 20.0 ... 1 0.4429 7.645 49.7 5.2119 5 216 14.9 377.07 3.01
283 284 Dover 49 4051 -71.1730 42.1475 50.0 50.0 0.01501 90.0 ... 1 0.4010 7.923 24.8 5.8850 1 198 13.6 395.52 3.16

14 rows × 21 columns

queryメソッドの使い方 (文字列と数値データカラムの複合条件)

町名がSwampscottで平均部屋数が7以上を抽出
df.query("TOWN == 'Swampscott' and RM >= 7")
Out[0]
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
2 3 Swampscott 1 2022 -70.936 42.283 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03

1 rows × 21 columns

スポンサーリンク

まとめ

evalメソッドとqueryメソッドの説明と使い方を調べてまとめてみました。

1つずつ着実に知識をつけていけてるなと実感します。

やっぱりブログはインプットとアウトプットをする場に最適ですね。

次はラムダ式、apply関数、map関数あたりを調べてまとめたいと思っています。
データ抽出というよりはデータ処理の話になっていくので、データ抽出に関しては一旦その3までにしておくことにします。

pandasではisinメソッドwhereメソッドなど他にも便利なメソッドが用意されているのでぜひAPIを一読してみてください。

スポンサーリンク

参照

https://stackoverflow.com/questions/55974760/pandas-query-performance-for-filtering
https://pandas.pydata.org/docs/user_guide/enhancingperf.html#enhancingperf-eval
https://pandas.pydata.org/pandas-docs/dev/reference/api/pandas.DataFrame.query.html
https://pandas.pydata.org/pandas-docs/dev/reference/api/pandas.eval.html#pandas.eval
https://devopedia.org/optimizing-pandas
https://pypi.org/project/numexpr/
https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/eval.py
https://github.com/pandas-dev/pandas/blob/main/pandas/core/frame.py

タイトルとURLをコピーしました