pandasのidxmaxが遅い

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

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

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


```python
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())
```
<table>
<tr>
<td valign="top"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332317/36466f92-bd85-d953-1dfe-9f61bea17ff1.png"></td>
<td valign="top"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332317/cf91c5b7-0dc6-a669-b17a-3ce09dd184b5.png"></td>
<td valign="top"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332317/2dfce222-239b-dd18-c4d8-bb5da73f1d47.png"></td>
</tr>
</table>

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

という関数です。

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

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

```python
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　= <font color="Red">__1.78倍__</font>
処理が遅いことがわかりました。

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

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

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

```python
import inspect
print(inspect.getsource(pd.DataFrame.idxmax))
```
こちらを実行して、帰ってきたソースコードが以下になります。

```python
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()の処理時間測定

```python
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　= <font color="Red">__16.11倍__</font>
処理が遅いことがわかりました。


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

がわかりました。

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

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