4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

pandasのidxmaxが遅い

Posted at

背景

仕事でビッグデータの処理を行なっているときに、
処理が遅く困っていて犯人探しをしていて気がついたのですが、
pandasライブラリの__idxmax()__の処理が遅いことに気づきました。

もちろん、maxと比べて、そのmax値のindexを返す処理が入るので、遅くなるのは当然なのですが、
実際どのくらい遅いのか検証して見ました。

##前提知識
pandasの__max()__と__idxmax()__の処理は以下の通りです。

import pandas as pd
import numpy as np

#0以上100未満のランダムな整数の10×5のデータフレームを生成
data = np.random.randint(0,100,[10,5])
df = pd.DataFrame(data,
                  index=['A','B','C','D','E','F','G','H','I','J'],
                  columns=['a','b','c','d','e'])

print(df)
print(df.max())
print(df.idxmax())

__max()__はcolumnごとに最大値を返し、
__idxmax()__はcolumnごとに最大値のindexを返す

という関数です。

では、データフレームを大きくして、処理時間を計測してみましょう。

###max()とidxmax()の処理時間測定

import pandas as pd
import numpy as np
import time

arr = np.random.randint(0,100,[10**5,10**4],dtype='int8')
df = pd.DataFrame(arr, dtype='int8')
df.info()
#<class 'pandas.core.frame.DataFrame'>
#RangeIndex: 100000 entries, 0 to 99999
#Columns: 10000 entries, 0 to 9999
#dtypes: int8(10000)
#memory usage: 953.7 MB

ts = time.time()
df.max()
te =time.time()
print('max()_time:',te-ts)
#max()_time: 10.67

ts = time.time()
df.idxmax()
te =time.time()
print('idxmax()_time:',te-ts)
#idxmax()_time: 19.08

上記の実験は、
およそ1GBのデータフレームに対して、
__max()__と__idxmax()__の処理時間を測定した結果です。

__idxmax()__が
19.08 ÷ 10.67 = 1.78倍
処理が遅いことがわかりました。

ちなみにマシンスペックは
MacBookPro 2018モデル、プロセッサ:2.3 GHz Intel Core i5、 メモリ:8 GB 2133 MHz LPDDR3
です
(会社のWindowsPCでは6倍の時間の差があったのですが。。)

もっと早くできないのか、調べるべくidxmax()の関数の中身をみてみましょう。

#####idxmax()のソースコード

import inspect
print(inspect.getsource(pd.DataFrame.idxmax))

こちらを実行して、帰ってきたソースコードが以下になります。

def idxmax(self, axis=0, skipna=True):
    """
    Return index of first occurrence of maximum over requested axis.
    NA/null values are excluded.

    Parameters
    ----------
    axis : {0 or 'index', 1 or 'columns'}, default 0
        0 or 'index' for row-wise, 1 or 'columns' for column-wise
    skipna : boolean, default True
        Exclude NA/null values. If an entire row/column is NA, the result
        will be NA.

    Returns
    -------
    idxmax : Series

    Raises
    ------
    ValueError
        * If the row/column is empty

    See Also
    --------
    Series.idxmax

    Notes
    -----
    This method is the DataFrame version of ``ndarray.argmax``.
    """
    axis = self._get_axis_number(axis)
    indices = nanops.nanargmax(self.values, axis=axis, skipna=skipna)
    index = self._get_axis(axis)
    result = [index[i] if i >= 0 else np.nan for i in indices]
    return Series(result, index=self._get_agg_axis(axis))

ndarrayの__argmax()__関数のデータフレーム版と書かれています。
もちろん予想通りでしたので、では__max()__と__argmax()__の処理の時間を比較したいと思います。

###max()とidxmax()の処理時間測定

ts = time.time()
_max = np.max(arr,axis=0)
te =time.time()
print('max()_time:',te-ts)
#max()_time: 0.85

ts = time.time()
_argmax = np.argmax(arr,axis=0)
te =time.time()
print('argmax()_time:',te-ts)
#argmax()_time: 13.70

結果は、__argmax()__が
13.70 ÷ 0.85 = 16.11倍
処理が遅いことがわかりました。

##考察
・両者とも、データフレームの時より処理が早くなっていること
・maxはidxmaxに比べて、ndarrayの時の処理速度が圧倒的に早くなっていること

がわかりました。

原因として、
idxmaxは同じ最大値があった場合、最初のデータのインデックスのみ返す、という処理になっているから、
その分処理時間が時長しているような気がします。

データフレームの乱数の範囲やサイズを変化させると、この倍率もかなり変わっていくと思われるので、
定量的な話は難しいですが、
一つ言えることは、データフレーム特有の処理(__groupby__など)を使用しないのであれば、
むやみにデータフレーム化しない方がいいですね。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?