【python】groupbyが遅くってェ…2


# やりたいこと
前回：https://qiita.com/wagahaiCAT/items/353de5aed90ff562cb14

あるグループの**特定範囲の**平均と標準偏差を求めて、同グループにまとめて代入したい。
条件Xの計算結果をグループ全体に入れるというか、最初だけ計算したいときのパターンというか。  
```python
df1.columns  # ['クラス','番号','名前','性別','科目','点数','Xbar','s']
```
↑こんなかんじのdfを使う。
行数は約5,000行。


# 第1案
for使ってる版。

```python
import pandas as pd
from decimal import Decimal, getcontext
def return_cll(df,c_side,r_num=2):
    
    avg = df.mean(numeric_only=True).round(r_num)
    sgm = df.std(numeric_only=True).round(r_num)
    three = Decimal(sgm)*3

    if    c_side=='u':    c_std = (Decimal(avg)+three)
    elif  c_side=='l':    c_std = (Decimal(avg)-three)
    return f.format(c_std)

st_tm1 = time.time()
df_g = pd.DataFrame()
for group, df_group in df1.groupby(['学年','クラス','科目']).__iter__():
    
    # 番号10番台だけを計算
    df_first = df_group[df_group['番号']<=10]
    
    if len(df_first)==0:
        df_group['Xbar-CL'] = float('nan')
        df_group['Xbar-UCL'] = float('nan')
        df_group['Xbar-LCL'] = float('nan')
        df_group['s-CL'] = float('nan')
        df_group['s-UCL'] = float('nan')
        df_group['s-LCL'] = float('nan')
    else:   
        # 計算
        lll = df_first['点数'].tolist()
        df_group['Xbar-CL'] = np.mean(lll) 
        df_group['Xbar-UCL'] = cll.return_cll(df_first["点数"],'u',2)
        df_group['Xbar-LCL'] = cll.return_cll(df_first["点数"],'l',2)
        lll = df_first['s'].tolist()
        df_group['s-CL'] = np.mean(lll) 
        df_group['s-UCL'] = cll.return_cll(df_first["s"],'u',2)
        df_group['s-LCL'] = cll.return_cll(df_first["s"],'l',2)
    
    # 結合
    df_g = pd.concat([df_g, df_group], axis=0)

logger.info("Elapsed time: {:,.4f} sec".format(time.time() - st_tm1)) 
# 結果
# Elapsed time: 28.0595 sec
```

おそいですね。

# 第2案
transformとlambdaを使うのはどうだろうという挑戦。

```python
st_tm1 = time.time()

df_first = df1[df1['番号']<=10]
gcol_1 = ['学年','クラス', '科目','点数']
gcol_2 = ['学年','クラス', '科目','s']
gcol = ['学年','クラス', '科目','Xbar-UCL','Xbar-LCL','s-UCL','s-LCL']

# Xbar UCL/LCL
df_first[gcol[3]] = df_first[gcol_1].groupby(gcol_1[:3]).transform(lambda x: x.mean()+(x.std()*3))[gcol_1[3]]
df_first[gcol[4]] = df_first[gcol_1].groupby(gcol_1[:3]).transform(lambda x: x.mean()-(x.std()*3))[gcol_1[3]]
# s UCL/LCL
df_first[gcol[5]] = df_first[gcol_2].groupby(gcol_2[:3]).transform(lambda x: x.mean()+(x.std()*3))[gcol_2[3]]
df_first[gcol[6]] = df_first[gcol_2].groupby(gcol_2[:3]).transform(lambda x: x.mean()-(x.std()*3))[gcol_2[3]]
 
df_f = df_first[~df_first.duplicated(subset=gcol[:3])][gcol] 
df1 = pd.merge(df1, df_f,on = gcol[:3])

logger.info("Elapsed time: {:,.4f} sec".format(time.time() - st_tm1)) 

# 結果
# Elapsed time: 13.7416 sec
```
そこまで速くならないなあと悩む。


# 第3案

apply版。

```python
st_tm1 = time.time()

df_first = df1[df1['番号']<=10]
gcol0 = ['学年','クラス', '科目','点数','s']
gcol = ['学年','クラス', '科目','Xbar-UCL','Xbar-LCL','s-UCL','s-LCL']

a= df_first[gcol0].groupby(gcol0[:3]).apply(lambda x: x.mean(numeric_only=True)+(x.std(numeric_only=True)*3)).rename(columns={gcol0[3]:gcol[3],gcol0[4]:gcol[5]}) 
b= df_first[gcol0].groupby(gcol0[:3]).apply(lambda x: x.mean(numeric_only=True)-(x.std(numeric_only=True)*3)).rename(columns={gcol0[3]:gcol[4],gcol0[4]:gcol[6]})
            
df1 = pd.merge(pd.merge(df1, a,on = gcol[:3]), b,on = gcol[:3])
logger.info("Elapsed time: {:,.4f} sec".format(time.time() - st_tm1)) 

# 結果
# Elapsed time: 6.4771 sec
```
applyにしても一行3秒かかるのは変わってない模様。  

# 第4案
pandarallel(並列実行)を使う版。

```python
# pip install pandarallel

from pandarallel import pandarallel
pandarallel.initialize()

st_tm1 = time.time()

df_first = df1[df1['番号']<=10]
gcol0 = ['学年','クラス', '科目','点数','s']
gcol = ['学年','クラス', '科目','Xbar-UCL','Xbar-LCL','s-UCL','s-LCL']

a= df_first[gcol0].groupby(gcol0[:3]).parallel_apply(lambda x: x.mean(numeric_only=True)+(x.std(numeric_only=True)*3)).rename(columns={gcol0[3]:gcol[3],gcol0[4]:gcol[5]})
b= df_first[gcol0].groupby(gcol0[:3]).apply(lambda x: x.mean(numeric_only=True)-(x.std(numeric_only=True)*3)).rename(columns={gcol0[3]:gcol[4],gcol0[4]:gcol[6]})

df1 = pd.merge(pd.merge(df1, a,on = gcol[:3]), b,on = gcol[:3])
logger.info("Elapsed time: {:,.4f} sec".format(time.time() - st_tm1)) 

# 結果
#  Elapsed time: 10.7422 sec
```
行数が多くないと逆に遅くなるとのことなので、おそらくそれかなと。


# 第5案
swifter版。

```python
# pip install -U swifter[groupby]
import swifter
st_tm1 = time.time()

df_first = df1[df1['番号']<=10]
gcol0 = ['学年','クラス', '科目','点数','s']
gcol = ['学年','クラス', '科目','Xbar-UCL','Xbar-LCL','s-UCL','s-LCL']

a= df_first[gcol0].swifter.groupby(gcol0[:3]).apply(lambda x: x.mean(numeric_only=True)+(x.std(numeric_only=True)*3)).rename(columns={gcol0[3]:gcol[3],gcol0[4]:gcol[5]})
b= df_first[gcol0].swifter.groupby(gcol0[:3]).apply(lambda x: x.mean(numeric_only=True)-(x.std(numeric_only=True)*3)).rename(columns={gcol0[3]:gcol[4],gcol0[4]:gcol[6]})

df1 = pd.merge(pd.merge(df1, a,on = gcol[:3]), b,on = gcol[:3])
logger.info("Elapsed time: {:,.4f} sec".format(time.time() - st_tm1)) 

# 結果
# Elapsed time: 13.9716 sec
```
一番速くなるかなと思ったのですがそんなことはなかった！

# 結論
第3案がベターザンなので、とりあえず第3案を採用しようと思いました。
3秒の壁を越えたい……。