ablog

不器用で落着きのない技術者のメモ

pandas で df[df[列番号]=='文字列'] と df[df[列番号].isin(['文字列'])] のどちらが速いか(文字列編)

pandas で df[df[列番号]==数値] と df[df[列番号].isin([数値])] のどちらが速いか(数値編) - ablog の文字列版。

サマリー

14MBの CSV ファイルを読んで 1,000 回 df[df[列番号]=='文字列'] または df[df[列番号].isin(['文字列'])] の実行時間を計測すると、isin のほうが 4.599s(=8.057s-3.458s)速い結果になった。cProfile でプロファイリングすると df[df[列番号]=='文字列'] の場合、pandas._libs.ops.scalar_compare で 79%(6.378s) の時間を消費している。一方、df[df[列番号].isin(['文字列'])]algorithms.py:457(isin) が最も時間を消費している( 1.934s)。

テストコードでは 100,000行(14MB)の CSV ファイル(|区切り)を pandas で読み込んで DataFrame を生成して df[df[3]==10] または df[df[3].isin([10])] を行っている。これを 1,000 回繰り返している。

コード 実行時間
df[df[1]=='Supplier#000100000']]] 8.057s
df[df[1].isin(['Supplier#000100000'])] 3.458s

計測結果

  • df[df[列番号]=='文字列']
$ time python -m cProfile -o pandas_equal_operator_string.prof pandas_equal_operator_string.py 
real    0m8.057s
user    0m7.920s
sys     0m0.173s
  • df[df[列番号].isin(['文字列'])]
$ time python -m cProfile -o pandas_isin_string.prof pandas_isin_string.py
real    0m3.458s
user    0m3.431s
sys     0m0.130s

ボトルネック分析

  • tottime: 関数で消費した時間(sub-function の呼び出しで消費した時間を含まない)
  • cumttime: 関数で消費した時間(sub-function の呼び出しで消費した時間を含む)
tottime でソート
  • cProfile でプロファイリングした結果(バイナリ)をテキストに変換
$ python cprofile2txt.py pandas_equal_operator_string.prof tottime > cprof_pandas_equal_operator_string.txt
$ python cprofile2txt.py pandas_isin_string.prof tottime > cprof_pandas_isin_string.txt
  • cprof_pandas_equal_operator_string.txt
Thu Oct 12 04:27:05 2023    pandas_equal_operator_string.prof

         741298 function calls (716789 primitive calls) in 7.870 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1000    6.378    0.006    6.378    0.006 {pandas._libs.ops.scalar_compare} ★ここに時間がかかっている
        1    0.324    0.324    0.325    0.325 {method 'read_low_memory' of 'pandas._libs.parsers.TextReader' objects}
      408    0.061    0.000    0.061    0.000 {built-in method marshal.loads}
     1000    0.046    0.000    0.046    0.000 {method 'nonzero' of 'numpy.ndarray' objects}
  • cprof_pandas_isin_string.txt
Thu Oct 12 04:27:32 2023    pandas_isin_string.prof

         723298 function calls (698789 primitive calls) in 3.280 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1000    1.934    0.002    1.997    0.002 algorithms.py:457(isin)
        1    0.268    0.268    0.269    0.269 {method 'read_low_memory' of 'pandas._libs.parsers.TextReader' objects}
      408    0.059    0.000    0.059    0.000 {built-in method marshal.loads}
cumtime でソート
  • cProfile でプロファイリングした結果(バイナリ)をテキストに変換
$ python cprofile2txt.py pandas_equal_operator_string.prof cumtime > cprof_pandas_equal_operator_string_cumtime.txt
$ python cprofile2txt.py pandas_isin_string.prof cumtime > cprof_pandas_isin_string_cumtime.txt
  • cprof_pandas_equal_operator_string_cumtime.txt
Thu Oct 12 04:27:05 2023    pandas_equal_operator_string.prof

         741298 function calls (716789 primitive calls) in 7.870 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    427/1    0.005    0.000    7.870    7.870 {built-in method builtins.exec}
        1    0.011    0.011    7.870    7.870 pandas_equal_operator_string.py:1(<module>)
     1000    0.002    0.000    6.586    0.007 common.py:62(new_method)
     1000    0.002    0.000    6.581    0.007 arraylike.py:38(__eq__)
     1000    0.007    0.000    6.579    0.007 series.py:5790(_cmp_method)
     1000    0.008    0.000    6.414    0.006 array_ops.py:290(comparison_op)
     1000    0.006    0.000    6.390    0.006 array_ops.py:115(comp_method_OBJECT_ARRAY)
     1000    6.378    0.006    6.378    0.006 {pandas._libs.ops.scalar_compare}
       66    0.002    0.000    1.286    0.019 __init__.py:1(<module>)
    524/2    0.004    0.000    0.456    0.228 <frozen importlib._bootstrap>:1165(_find_and_load)
  • cprof_pandas_isin_string_cumtime.txt
Thu Oct 12 04:27:32 2023    pandas_isin_string.prof

         723298 function calls (698789 primitive calls) in 3.280 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    427/1    0.005    0.000    3.280    3.280 {built-in method builtins.exec}
        1    0.009    0.009    3.280    3.280 pandas_isin_string.py:1(<module>)
     1000    0.009    0.000    2.121    0.002 series.py:5273(isin)
     1000    1.934    0.002    1.997    0.002 algorithms.py:457(isin)
       66    0.002    0.000    1.272    0.019 __init__.py:1(<module>)
    524/2    0.004    0.000    0.449    0.224 <frozen importlib._bootstrap>:1165(_find_and_load)

計測プログラム

  • pandas_equal_operator_string.py
#!/usr/bin/env python3

import pandas as pd

df = pd.read_csv('csv/supplier.tbl', delimiter='|', header=None)

for i in range(1000):
    df[df[1]=='Supplier#000100000']
  • pandas_isin_string.py
#!/usr/bin/env python3

import pandas as pd

df = pd.read_csv('csv/supplier.tbl', delimiter='|', header=None)

for i in range(1000):
    df[df[1].isin(['Supplier#000100000'])]

cProfile のプロファイリング結果をテキストに変換するスクリプト

  • cprofile2txt.py
#!/usr/bin/env python3
import sys
import pstats

cprof_binary_file = sys.argv[1]
cprof_sort_key = sys.argv[2]

sts = pstats.Stats(cprof_binary_file)
sts.strip_dirs().sort_stats(cprof_sort_key).print_stats()