"""
## GroupKfoldを行なった後にstock_id系統のカラムを追加した場合の精度を調査
"""
# 並列化
# CPUを複数使ってくれるようになるらしい。これがあるのと無いのとでは処理の時間が全然違う
from joblib import Parallel, delayed

import pandas as pd
import numpy as np
import scipy as sc

# 交差検証Kfold
from sklearn.model_selection import KFold
# lgbm
import lightgbm as lgb
# 警告文を出さないようにする
import warnings
from sklearn.cluster import KMeans
warnings.filterwarnings('ignore')

pd.set_option('max_columns', 300)
# WAPをbidとaskの最大値最小値を使って計算したものと2番目のものを使った場合の関数
def calc_wap1(df):
    wap = (df['bid_price1'] * df['ask_size1'] + df['ask_price1'] * df['bid_size1']) / (df['bid_size1'] + df['ask_size1'])
    return wap

def calc_wap2(df):
    wap = (df['bid_price2'] * df['ask_size2'] + df['ask_price2'] * df['bid_size2']) / (df['bid_size2'] + df['ask_size2'])
    return wap

# Log returnを計算する関数
def log_return(series):
    return np.log(series).diff()

# 目的変数であるボラティリティを計算する関数
def realized_volatility(series):
    return np.sqrt(np.sum(series**2))

# seriesのユニークな値の個数をカウントする関数
def count_unique(series):
    return len(np.unique(series))
# pathを設定
data_dir = '../input/optiver-realized-volatility-prediction/'
# 訓練データとテストデータを読み込むための関数
def read_train_test():
    train = pd.read_csv(data_dir + 'train.csv')
    test = pd.read_csv(data_dir + 'test.csv')
    # オーダーブックとトレードデータと結合するためにidとなるカラムを作成
    train['row_id'] = train['stock_id'].astype(str) + '-' + train['time_id'].astype(str)
    test['row_id'] = test['stock_id'].astype(str) + '-' + test['time_id'].astype(str)
    # 関数が実行されたらtrainデータの行数をprintするように設定
    print(f'Our training set has {train.shape[0]} rows')
    return train, test

# オーダーブックデータの前処理を行う関数
# 今回行う前処り
def book_preprocessor(file_path):
    df = pd.read_parquet(file_path)
    # WAP1,2を計算した結果カラムとして追加
    df['wap1'] = calc_wap1(df)
    df['wap2'] = calc_wap2(df)
    # time_idごとのlog return1,2を計算した結果カラムとして追加
    df['log_return1'] = df.groupby(['time_id'])['wap1'].apply(log_return)
    df['log_return2'] = df.groupby(['time_id'])['wap2'].apply(log_return)
    # WAP1とWAP2の絶対値の差を取得してカラムとして追加
    df['wap_balance'] = abs(df['wap1'] - df['wap2'])
    # spreadを計算
    # price_spreadは下記のように計算
    # df['ask_price1'] - df['bid_price1']は最高売値 - 最低買値の差
    # ((df['ask_price1'] + df['bid_price1']) / 2)は最高売値と最低買値の平均値
    # 双方の値段が離れるほど大きくなる離れ度合い的な意味合い
    df['price_spread'] = (df['ask_price1'] - df['bid_price1']) / ((df['ask_price1'] + df['bid_price1']) / 2)
    df['price_spread2'] = (df['ask_price2'] - df['bid_price2']) / ((df['ask_price2'] + df['bid_price2']) / 2)
    # 最大と二番目のそれぞれの差
    # 値段が詰まっているほど人気？ → これが小さいほど良い市場と言えるのではないか
    df['bid_spread'] = df['bid_price1'] - df['bid_price2']
    df['ask_spread'] = df['ask_price1'] - df['ask_price2']
    # spreadの分子。spreadと同じくこれが大きいと良い市場
    df["bid_ask_spread"] = abs(df['bid_spread'] - df['ask_spread'])
    # askとbidの一番と二番目の予約の合計 → これが大きいということは売買が盛んということ（買いたい人と売りたい人が多い）
    df['total_volume'] = (df['ask_size1'] + df['ask_size2']) + (df['bid_size1'] + df['bid_size2'])
    # 買い手と売り手どっちの差の絶対値 → これが大きいとどちらかに需要が偏りがあるから良くない？
    df['volume_imbalance'] = abs((df['ask_size1'] + df['ask_size2']) - (df['bid_size1'] + df['bid_size2']))
    # 需要と供給の割合を追加
    df['bid_ask_ratio'] = (df['ask_size1']+df['ask_size2'])/(df['bid_size1']+df['bid_size2'])
    
    # 辞書型でカラム名と操作を用意
    create_feature_dict = {
        'wap1': [np.sum, np.mean, np.std],
        'wap2': [np.sum, np.mean, np.std],
        'log_return1': [np.sum, realized_volatility, np.mean, np.std],
        'log_return2': [np.sum, realized_volatility, np.mean, np.std],
        'wap_balance': [np.sum, np.mean, np.std],
        'price_spread':[np.sum, np.mean, np.std],
        'price_spread2':[np.sum, np.mean, np.std],
        'bid_spread':[np.sum, np.mean, np.std],
        'ask_spread':[np.sum, np.mean, np.std],
        'total_volume':[np.sum, np.mean, np.std],
        'volume_imbalance':[np.sum, np.mean, np.std],
        'bid_ask_ratio':[np.sum, np.mean, np.std],
        "bid_ask_spread":[np.sum, np.mean, np.std],
    }
    
    # データ数が多いのでtime_idごとに集計する関数を作成
    def get_stats_window(seconds_in_bucket, add_suffix = False):
        # 関数で引数に設定したsconds_in_bucketより後のデータだけをtime_idでgroup byした状態で取得
        df_feature = df[df['seconds_in_bucket'] >= seconds_in_bucket].groupby(['time_id']).agg(create_feature_dict).reset_index()
        # ケツにカラム名を追加
        df_feature.columns = ['_'.join(col) for col in df_feature.columns]
        # ケツにそれぞれの「_seconds_in_bucket名」とカラムに追加
        if add_suffix:
            df_feature = df_feature.add_suffix('_' + str(seconds_in_bucket))
        return df_feature
    
    # 上の関数を用いてtime_idごとに集計したテーブルをさらにseconds_in_bucketで区切ったDataframeを作成
    df_feature = get_stats_window(seconds_in_bucket = 0, add_suffix = False)
    # add_suffix = Trueとするとカラム名の最後に「_450」って付く
    df_feature_450 = get_stats_window(seconds_in_bucket = 450, add_suffix = True)
    df_feature_300 = get_stats_window(seconds_in_bucket = 300, add_suffix = True)
    df_feature_150 = get_stats_window(seconds_in_bucket = 150, add_suffix = True)
    # 参考にしたnotebookには100秒ごとに同じ作業をした跡が残っていた。150秒ごとの方がいいと判断したのか
    # df_feature_500 = get_stats_window(seconds_in_bucket = 500, add_suffix = True)
    # df_feature_400 = get_stats_window(seconds_in_bucket = 400, add_suffix = True)
    # df_feature_200 = get_stats_window(seconds_in_bucket = 200, add_suffix = True)

    # 上で作成したDataFrameを一つに結合する
    df_feature = df_feature.merge(df_feature_450, how = 'left', left_on = 'time_id_', right_on = 'time_id__450')
    df_feature = df_feature.merge(df_feature_300, how = 'left', left_on = 'time_id_', right_on = 'time_id__300')
    df_feature = df_feature.merge(df_feature_150, how = 'left', left_on = 'time_id_', right_on = 'time_id__150')
    # 結合で使用したid用のカラムを削除
    df_feature.drop(['time_id__450', 'time_id__300', 'time_id__150'], axis = 1, inplace = True)
    
    # stock_idごとに別のフォルダにファイルが保存されているため、file_pathからstock_idを読み取った上で結合する
    # file_pathが「./input/optiver-realized-volatility-prediction/book_train.parquet/stock_id=0/...」だったらstock_idを0と認識した上で結合
    stock_id = file_path.split('=')[1]
    df_feature['row_id'] = df_feature['time_id_'].apply(lambda x: f'{stock_id}-{x}')
    # この時点で「time_id_0」などのvalueが入っている 
    df_feature.drop(['time_id_'], axis = 1, inplace = True)
    return df_feature
# トレードデータをまとめて前処理
def trade_preprocessor(file_path):
    df = pd.read_parquet(file_path)
    # 実際に取引が行われた時の値段でlog_returnを計算した結果を「log_return」カラムに代入
    df['log_return'] = df.groupby('time_id')['price'].apply(log_return)
    
    # 辞書型でカラム名と操作を用意
    create_feature_dict = {
        'log_return':[realized_volatility],
        'seconds_in_bucket':[count_unique],
        'size':[np.sum, realized_volatility, np.mean, np.std, np.max, np.min],
        'order_count':[np.mean,np.sum,np.max],
    }
    
    # データ数が多いのでtime_idごとに集計する関数を作成
    def get_stats_window(seconds_in_bucket, add_suffix = False):
        # 関数で引数に設定したsconds_in_bucketより後のデータだけをtime_idでgroup byした状態で取得
        df_feature = df[df['seconds_in_bucket'] >= seconds_in_bucket].groupby(['time_id']).agg(create_feature_dict).reset_index()
        # ケツにカラム名を追加
        df_feature.columns = ['_'.join(col) for col in df_feature.columns]
        # ケツにそれぞれの「_seconds_in_bucket名」とカラムに追加
        if add_suffix:
            df_feature = df_feature.add_suffix('_' + str(seconds_in_bucket))
        return df_feature
    

    # 上の関数を用いてtime_idごとに集計したテーブルをさらにseconds_in_bucketで区切ったDataframeを作成
    df_feature = get_stats_window(seconds_in_bucket = 0, add_suffix = False)
    df_feature_450 = get_stats_window(seconds_in_bucket = 450, add_suffix = True)
    df_feature_300 = get_stats_window(seconds_in_bucket = 300, add_suffix = True)
    df_feature_150 = get_stats_window(seconds_in_bucket = 150, add_suffix = True)
    # オーダーブック同様100秒ごとより150秒ごとの方が結果が良かったらしい
    # df_feature_500 = get_stats_window(seconds_in_bucket = 500, add_suffix = True)
    # df_feature_400 = get_stats_window(seconds_in_bucket = 400, add_suffix = True)
    # df_feature_200 = get_stats_window(seconds_in_bucket = 200, add_suffix = True)
    
    # scoreに影響しているらしい
    
    # time_idごとにpriceがどれくらい変動してsizeがどれくらい行われたか
    def tendency(price, vol):    
        df_diff = np.diff(price)
        val = (df_diff/price[1:])*100
        power = np.sum(val*vol[1:])
        return(power)
    
    # time_idごとに最大値や最小値を計算した配列を用意
    # このやり方だとリークしている
    lis = []
    
    for n_time_id in df['time_id'].unique():
        df_id = df[df['time_id'] == n_time_id]
        
        # 上で作ったpowerを計算。time_idごとにpriceがどれくらい変動したか、取引がどれくらい行われたか
        tendencyV = tendency(df_id['price'].values, df_id['size'].values)      
        
        # 平均以上と以下でpriceの合計値を取得
        # 意図は分からないがとりあえず残す
        f_max = np.sum(df_id['price'].values > np.mean(df_id['price'].values))
        f_min = np.sum(df_id['price'].values < np.mean(df_id['price'].values))
        
        # 正か負かで分けた前後priceの差分の合計値
        # こちらも意図は分からず
        df_max =  np.sum(np.diff(df_id['price'].values) > 0)
        df_min =  np.sum(np.diff(df_id['price'].values) < 0)
        
        ## ここからはpriceの話
        # 偏差の中央値
        abs_diff = np.median(np.abs(df_id['price'].values - np.mean(df_id['price'].values)))  
        # 価格の二乗の平均値
        energy = np.mean(df_id['price'].values**2)
        # 第3四分位-第１四分位
        iqr_p = np.percentile(df_id['price'].values,75) - np.percentile(df_id['price'].values,25)
        
        ## ここからはsizeの話
        # それぞれ偏差の中央値、価格の二乗の平均値、第3四分位-第１四分位を計算
        abs_diff_v = np.median(np.abs(df_id['size'].values - np.mean(df_id['size'].values)))        
        energy_v = np.sum(df_id['size'].values**2)
        iqr_p_v = np.percentile(df_id['size'].values,75) - np.percentile(df_id['size'].values,25)
        
        # まとめてlisにappend
        lis.append({'time_id':n_time_id,'tendency':tendencyV,'f_max':f_max,'f_min':f_min,'df_max':df_max,'df_min':df_min,
                   'abs_diff':abs_diff,'energy':energy,'iqr_p':iqr_p,'abs_diff_v':abs_diff_v,'energy_v':energy_v,'iqr_p_v':iqr_p_v})

    # lisをDataFrameに変換  
    df_lr = pd.DataFrame(lis)
   
    # dif_lrを結合
    df_feature = df_feature.merge(df_lr, how = 'left', left_on = 'time_id_', right_on = 'time_id')
    
    # sconds_in_bucketで分けたDataFrameも全て結合
    df_feature = df_feature.merge(df_feature_450, how = 'left', left_on = 'time_id_', right_on = 'time_id__450')
    df_feature = df_feature.merge(df_feature_300, how = 'left', left_on = 'time_id_', right_on = 'time_id__300')
    df_feature = df_feature.merge(df_feature_150, how = 'left', left_on = 'time_id_', right_on = 'time_id__150')
    # df_feature = df_feature.merge(df_feature_300, how = 'left', left_on = 'time_id_', right_on = 'time_id__300')
    # df_feature = df_feature.merge(df_feature_100, how = 'left', left_on = 'time_id_', right_on = 'time_id__100')
    
    # 結合用に使ったカラムを削除
    df_feature.drop(['time_id__450', 'time_id__300', 'time_id__150','time_id'], axis = 1, inplace = True)
    
    # オーダーブックと見分けをつけるためにカラムの先頭に「trade_」をつける
    df_feature = df_feature.add_prefix('trade_')
    stock_id = file_path.split('=')[1]
    df_feature['row_id'] = df_feature['trade_time_id_'].apply(lambda x:f'{stock_id}-{x}')
    df_feature.drop(['trade_time_id_'], axis = 1, inplace = True)
    return df_feature
# rmspeを計算するための関数
def rmspe(y_true, y_pred):
    return np.sqrt(np.mean(np.square((y_true - y_pred) / y_true)))

# rmspeの値がぶれて来たら停止するための関数
def feval_rmspe(y_pred, lgb_train):
    y_true = lgb_train.get_label()
    return 'RMSPE', rmspe(y_true, y_pred), False
# stock_idとtime_idごとにデータをまとめるための関数
def get_time_stock(df):
    # ボラティリティ周りにカラムを配列に用意
    vol_cols = ['log_return1_realized_volatility', 'log_return2_realized_volatility', 'log_return1_realized_volatility_450', 'log_return2_realized_volatility_450', 
                'log_return1_realized_volatility_300', 'log_return2_realized_volatility_300', 'log_return1_realized_volatility_150', 'log_return2_realized_volatility_150', 
                'trade_log_return_realized_volatility', 'trade_log_return_realized_volatility_450', 'trade_log_return_realized_volatility_300', 'trade_log_return_realized_volatility_150']

    # stock_idごとにまとめる
    df_stock_id = df.groupby(['stock_id'])[vol_cols].agg(['mean', 'std', 'max', 'min', ]).reset_index()
    # stock_idごとにまとめた時に計算したmeanなどとわかるようにケツに「_stock」とつける
    df_stock_id.columns = ['_'.join(col) for col in df_stock_id.columns]
    df_stock_id = df_stock_id.add_suffix('_' + 'stock')

    # time_idごとにまとめる（作業はstock_idの時と一緒）
    df_time_id = df.groupby(['time_id'])[vol_cols].agg(['mean', 'std', 'max', 'min', ]).reset_index()
    df_time_id.columns = ['_'.join(col) for col in df_time_id.columns]
    df_time_id = df_time_id.add_suffix('_' + 'time')
    
    # 元のDataFrameに今作った2つを結合
    df = df.merge(df_stock_id, how = 'left', left_on = ['stock_id'], right_on = ['stock_id__stock'])
    df = df.merge(df_time_id, how = 'left', left_on = ['time_id'], right_on = ['time_id__time'])
    df.drop(['stock_id__stock', 'time_id__time'], axis = 1, inplace = True)
    return df
    
# それぞれの前処理を並列して行えるようにするための関数
def preprocessor(list_stock_ids, is_train = True):
    
    # Parrallel for loop
    def for_joblib(stock_id):
        # is_train=Trueと指定された場合trainデータの方へのpathを作成
        if is_train:
            file_path_book = data_dir + "book_train.parquet/stock_id=" + str(stock_id)
            file_path_trade = data_dir + "trade_train.parquet/stock_id=" + str(stock_id)
        # is_train=Falseつまりtestデータならこちらへのpathを作成
        else:
            file_path_book = data_dir + "book_test.parquet/stock_id=" + str(stock_id)
            file_path_trade = data_dir + "trade_test.parquet/stock_id=" + str(stock_id)
    
        # bookとtradeを前処理した上で結合する
        # 些細な変化も全てレコードとして記録されているbookの方にrow_idを基準にtradeを外部結合する
        df_tmp = pd.merge(book_preprocessor(file_path_book), trade_preprocessor(file_path_trade), on = 'row_id', how = 'left')
        
        # 結合したDataFrameを返す
        return df_tmp
    
    # 並列APIを使用してfor_joblib関数を呼び出す
    # n_jobs:最大同時実行ジョブ数。-1とすると全てのCPUが使用される
    # verbose:ログの出力レベル（冗長性）。デフォルトでは何も出力されない。値を大きくすると出力レベルが上がる（冗長性が増す）。10より大きいとすべてのログが出力され、50以上だとstdout（標準出力）に出力される。
    # delayed(<実行する関数>)(<関数への引数>) for 変数名 in イテラブル
    # 実行する関数（bookとtradeを前処理して結合）をstock_idごとに行う
    df = Parallel(n_jobs = -1, verbose = 1)(delayed(for_joblib)(stock_id) for stock_id in list_stock_ids)
    # Parallelから返されるすべてのDataFrameを結合
    # ignore_index=Trueでindexがconcat前のindexを無視して連番で振られる
    df = pd.concat(df, ignore_index = True)
    return df

# RMSPE(平均平方二乗誤差率)
def rmspe(y_true, y_pred):
    return np.sqrt(np.mean(np.square((y_true - y_pred) / y_true)))

# RMSPEで早期停止する関数
def feval_rmspe(y_pred, lgb_train):
    y_true = lgb_train.get_label()
    return 'RMSPE', rmspe(y_true, y_pred), False
# trainデータとtestデータの読み込み
train, test = read_train_test()

# ユニークなstockのidを取得する
train_stock_ids = train['stock_id'].unique()
# 並列処理に使うために前処理を行う
# 今回は並列化してbookとtradeをstock_idごとに処理をした上で結合する
train_ = preprocessor(train_stock_ids, is_train = True)
# row_idを基準にleft joinする
train = train.merge(train_, on = ['row_id'], how = 'left')

# テストデータに対しても同じ処理を行う
test_stock_ids = test['stock_id'].unique()
test_ = preprocessor(test_stock_ids, is_train = False)
test = test.merge(test_, on = ['row_id'], how = 'left')

# time_idとstock_idが一緒になったデータを取得
# get_time_stockはstock_idとtime_idごとの集計を行う関数
train = get_time_stock(train)
test = get_time_stock(test)
display(train.head())
display(train.shape)
display(test.head())
display(test.shape)
# paramsを設定
seed = 42
params = {
    'learning_rate': 0.1,        
    'lambda_l1': 2,
    'lambda_l2': 7,
    'num_leaves': 800,
    'min_sum_hessian_in_leaf': 20,
    'feature_fraction': 0.8,
    'feature_fraction_bynode': 0.8,
    'bagging_fraction': 0.9,
    'bagging_freq': 42,
    'min_data_in_leaf': 700,
    'max_depth': 4,
    'seed': seed,
    'feature_fraction_seed': seed,
    'bagging_seed': seed,
    'drop_seed': seed,
    'data_random_seed': seed,
    'objective': 'rmse',
    'boosting': 'gbdt',
    'verbosity': -1,
    'n_jobs': -1,
} 
# 重要度解析
# 特徴量の重要度（feature_importance）の計算方法は以下の2つ
# - 頻度: モデルでその特徴量が使用された回数（初期値）「importance_type = 'split'」
# - ゲイン: その特徴量が使用する分岐からの目的関数の減少（こちらがおすすめらしい）「importance_type = 'gain'」
# 今回はgainを選択。目的関数の減少に関わっている特徴量 → つまり重要な特徴量が降順でわかる
def calc_model_importance(model, feature_names=None, importance_type='gain'):
    importance_df = pd.DataFrame(model.feature_importance(importance_type=importance_type),
                                 index=feature_names,
                                 columns=['importance']).sort_values('importance')
    return importance_df

def calc_mean_importance(importance_df_list):
    mean_importance = np.mean(
        np.array([df['importance'].values for df in importance_df_list]), axis=0)
    mean_df = importance_df_list[0].copy()
    mean_df['importance'] = mean_importance
    
    return mean_df
"""
## stock_idの統計量をKfoldの後に取得する
"""
display(train.shape)
display(test.shape)
print('='*40 + 'stock_id削除後' + '='*40)
# 「_stock」とは入っているカラムは全て削除
train_drop_stock = train.drop(train.filter(like='_stock', axis=1).columns.to_list(), axis=1)
display(train_drop_stock.shape)
# 特徴量とターゲットを分割
x = train_drop_stock.drop(['row_id', 'target', 'time_id'], axis = 1)
y = train_drop_stock['target']
x_test = test.drop(['row_id', 'time_id'], axis = 1)

# 結合用でobject型で入っていたstock_idを数値型に変換
x['stock_id'] = x['stock_id'].astype(int)
x_test['stock_id'] = x_test['stock_id'].astype(int)

# xの行数分0が入った配列を用意
oof_predictions = np.zeros(x.shape[0])
display(oof_predictions)
display(oof_predictions.shape)
# テストデータに対しても行う
test_predictions = np.zeros(x_test.shape[0])
display(test_predictions)
display(test_predictions.shape)
# stock_idごとにまとめる関数をもう一度作成
def get_time_stock_2(df):
    # Get realized volatility columns
    vol_cols = ['log_return1_realized_volatility', 'log_return2_realized_volatility', 'log_return1_realized_volatility_450', 'log_return2_realized_volatility_450', 
                'log_return1_realized_volatility_300', 'log_return2_realized_volatility_300', 'log_return1_realized_volatility_150', 'log_return2_realized_volatility_150', 
                'trade_log_return_realized_volatility', 'trade_log_return_realized_volatility_450', 'trade_log_return_realized_volatility_300', 'trade_log_return_realized_volatility_150']

    # stock_idごとに集計
    df_stock_id = df.groupby(['stock_id'])[vol_cols].agg(['mean', 'std', 'max', 'min', ]).reset_index()
    df_stock_id.columns = ['_'.join(col) for col in df_stock_id.columns]
    df_stock_id = df_stock_id.add_suffix('_' + 'stock')
    
    # 元々のデータに今作成したDataFrameを作成
    df = df.merge(df_stock_id, how = 'left', left_on = ['stock_id'], right_on = ['stock_id__stock'])
    df.drop(['stock_id__stock'], axis = 1, inplace = True)
    return df
# 実行前の確認
x.shape
# 重要度解析をgainで取得したverとsplitで取得したverに分けるためにそれぞれ配列を用意
gain_importance_list = []
split_importance_list = []

# sklearnのGroupKFoldをimport
from sklearn.model_selection import GroupKFold
# ここはtime_id？
group = train['stock_id']
# trainデータを5つのfoldに分割
kf = GroupKFold(n_splits=5)
# 各foldで繰り返す
for fold, (trn_ind, val_ind) in enumerate(kf.split(x, groups=group)):
    print(f'Training fold {fold + 1}')
    x_train, x_val = x.iloc[trn_ind], x.iloc[val_ind]
    y_train, y_val = y.iloc[trn_ind], y.iloc[val_ind]
    
    # 各foldの時のデータでstock_idごとに集計する関数を適用
    x_train = get_time_stock_2(x_train)
    x_val = get_time_stock_2(x_val)
    
    # RMSPE
    train_weights = 1 / np.square(y_train)
    val_weights = 1 / np.square(y_val)
    train_dataset = lgb.Dataset(x_train, y_train, weight = train_weights)
    val_dataset = lgb.Dataset(x_val, y_val, weight = val_weights)
    model = lgb.train(params = params, 
                      train_set = train_dataset, 
                      categorical_feature = ['stock_id'],
                      valid_sets = [train_dataset, val_dataset], 
                      num_boost_round = 5000, 
                      early_stopping_rounds = 30, 
                      verbose_eval = 100,
                      feval = feval_rmspe)

    # 各foldごとにlightgbmを実装して予測値を取得
    # のちのrmspeの計算で必要
    oof_predictions[val_ind] = model.predict(x_val)
    
    # 分けた5つのfoldごとの予測値の平均値を取得
    test_predictions += model.predict(x_test) / 5

    feature_names = x_train.columns.values.tolist()
    gain_importance_df = calc_model_importance(
        model, feature_names=feature_names, importance_type='gain')
    gain_importance_list.append(gain_importance_df)

    split_importance_df = calc_model_importance(
        model, feature_names=feature_names, importance_type='split')
    split_importance_list.append(split_importance_df)
    
    x_train.drop(train.filter(like='_stock', axis=1).columns.to_list(), axis=1, inplace=True)
    x_val.drop(train.filter(like='_stock', axis=1).columns.to_list(), axis=1, inplace=True)

rmspe_score = rmspe(y, oof_predictions)
print(f'Our out of folds RMSPE is {rmspe_score}')
# 重要度解析を出力
mean_gain_df = calc_mean_importance(gain_importance_list)
mean_gain_df = mean_gain_df.reset_index().rename(columns={'index': 'feature_names'})
# csvにして全部見たい場合は下記
# mean_gain_df.to_csv('gain_importance_mean groupkfold stock_id after Kflod.csv', index=False)
mean_gain_df.head(20).sort_values(by='importance', ascending=False)
test['target'] = test_predictions
test[['row_id', 'target']].to_csv('submission.csv',index = False)