前回はセルの値を条件にして抽出する方法をまとめました。
今回は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メソッドを使う上での注意点
- 15,000レコード以上のデータセットでないと逆にパフォーマンスが悪くなるとpandasの公式ページで見解が述べられています。
ループ処理などで使わない限り、15,000レコード以下のデータセットで使用しても問題ないとは個人的には思います。
- NumExprを使うことによって数値計算を高速化していると思われるので、当然ですが文字列に対する処理にはNumExprは使用されません。こちらのページに詳しく載っています。
ボストンの住宅価格データセットでevalメソッドとqueryメソッドを使ってみる
NumExprライブラリをインストールしていない場合はインストールする
$ /Users/hinomaruc/Desktop/notebooks/my-venv/bin/python3 -m pip install numexpr
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
ボストンの住宅価格データセットの読み込み
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
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メソッドを使ってみる (文字列データのカラム)
df.eval("TOWN == 'Swampscott'")
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で返却されます。
df[df.eval("TOWN == 'Swampscott'")]
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")
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")]
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メソッドを使ってみる (文字列と数値データカラムの複合条件)
df.eval("TOWN == 'Swampscott' and RM >= 7")
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")]
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メソッドの使い方 (文字列データのカラム)
df.query("TOWN == 'Swampscott'")
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メソッドの使い方 (数値データのカラム)
df.query("RM >= 7.5 and RM <= 8")
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メソッドの使い方 (文字列と数値データカラムの複合条件)
df.query("TOWN == 'Swampscott' and RM >= 7")
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