やりたいこと
前回:https://qiita.com/wagahaiCAT/items/353de5aed90ff562cb14
あるグループの特定範囲の平均と標準偏差を求めて、同グループにまとめて代入したい。
条件Xの計算結果をグループ全体に入れるというか、最初だけ計算したいときのパターンというか。
df1.columns # ['クラス','番号','名前','性別','科目','点数','Xbar','s']
↑こんなかんじのdfを使う。
行数は約5,000行。
第1案
for使ってる版。
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を使うのはどうだろうという挑戦。
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版。
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(並列実行)を使う版。
# 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版。
# 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秒の壁を越えたい……。