# importing the required packages
import pandas as pd 
import numpy as np
from matplotlib import pyplot as plt
import optuna
import warnings
warnings.filterwarnings("ignore")
from xgboost import XGBRegressor
import lightgbm as lgb
import catboost as cat
from sklearn.ensemble import RandomForestRegressor 
from sklearn.linear_model import LogisticRegression,LinearRegression
from sklearn.model_selection import StratifiedKFold,train_test_split
from sklearn.metrics import roc_curve,auc
from sklearn.preprocessing import QuantileTransformer
from sklearn.pipeline import Pipeline
from sklearn.experimental import enable_iterative_imputer  
from sklearn.impute import IterativeImputer
# reading the data
train = pd.read_csv('../input/song-popularity-prediction/train.csv')
test = pd.read_csv('../input/song-popularity-prediction/test.csv')
train = train[~((train['energy'].isnull()) & (train['liveness'].isnull()) ) ].reset_index(drop=True)
train = train[~((train['acousticness'].isnull()) & (train['instrumentalness'].isnull()) ) ].reset_index(drop=True)
train = train[~((train['loudness'].isnull()) & (train['danceability'].isnull()) ) ].reset_index(drop=True)
train = train[~((train['liveness'].isnull()) & (train['danceability'].isnull()) ) ].reset_index(drop=True)
X = train.drop('song_popularity', axis=1).copy()
y = train['song_popularity'].copy()
X.head()
X.info()
X.describe(include='all')
# droping the 'id' column from train and test
X.drop('id', axis=1, inplace=True)
test.drop('id', axis=1, inplace=True)

# replacing the -ve values with 0
X.acousticness[X.acousticness<0] = 0
X.instrumentalness[X.instrumentalness<0] = 0
# pipeline to impute the null value and data transformation
pipeline = Pipeline([
    ('impute', IterativeImputer(max_iter=10,random_state=42,add_indicator=False)),
    ('transform_', QuantileTransformer()) 
])
kf = StratifiedKFold(n_splits=10, shuffle=True, random_state=42) # stratified k fold 

x_test = test.copy()
x = X.copy()

# dataframe to store OOF predictions
valid_pred_df = pd.DataFrame()
test_pred_df = pd.DataFrame()
test_pred_df['id'] = x_test.index
valid_pred_df['id'] = X.index

x = pd.DataFrame(pipeline.fit_transform(x),columns=x.columns)
x_test = pd.DataFrame(data=pipeline.transform(x_test),columns=x.columns)
"""
# Stacking

>> 3 layers of stacking done with 10 fold validation

>> all the hypeparameters were tuned using optuna
"""
"""
## L0
"""
"""
### XGBoost_0
"""
temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

xgb_params = {'max_depth': 8,
 'n_estimators': 9600,
 'learning_rate': 0.010729802684564086,
 'subsample': 0.2,
 'colsample_bytree': 0.7,
 'colsample_bylevel': 0.4,
 'reg_lambda': 12.411155548777836,
 'reg_alpha': 0.6685496387870691,
 'gamma': 0.0001373432756811366}

for fold, (idx_train, idx_valid) in enumerate(kf.split(x, y)):
    x_train, y_train = x.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = x.iloc[idx_valid], y.iloc[idx_valid] 

    model = XGBRegressor(**xgb_params, booster= 'gbtree',
                        objective= 'binary:logistic',
                        eval_metric = 'auc',
                        tree_method= 'gpu_hist',
                        predictor="gpu_predictor",
                        random_state=0, 
                        use_label_encoder=False,
                        )
    
    model.fit(x_train,y_train,
            eval_set=[(x_valid,y_valid)],
            early_stopping_rounds=300,
            verbose=False)
    
    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict(x_test)
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_xgb'})
valid_pred_df = valid_pred_df.merge(temp_df,on = 'id')
test_pred_df['pred_xgb'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")
"""
### LightGBM_0
"""
temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

lgb_params = {'max_depth': 69,
 'n_estimators': 19600,
 'learning_rate': 0.09074604767677533,
 'subsample': 0.9,
 'colsample_bytree': 0.6000000000000001,
 'reg_lambda': 10.637736685175083,
 'reg_alpha': 12.732914587118874,
 'boosting_type': 'gbdt',
 'num_leaves': 10}

for fold, (idx_train, idx_valid) in enumerate(kf.split(x, y)):
    x_train, y_train = x.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = x.iloc[idx_valid], y.iloc[idx_valid] 

    model = lgb.LGBMRegressor(**lgb_params, 
                               metric= 'auc', 
                               random_state= 0,
                               )  
    
    model.fit(x_train, y_train,
            eval_set=[(x_valid, y_valid)],
            early_stopping_rounds=300,
            verbose=False
           )
    
    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict(x_test)
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_lgb'})
valid_pred_df = valid_pred_df.merge(temp_df,on = 'id')
test_pred_df['pred_lgb'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")

"""
### RandomForest_0
"""

temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

rf_params = {'max_depth': 54,
 'n_estimators': 100,
 'max_features': 'log2',
 'max_leaf_nodes': 128,
 'min_samples_split': 3,
 'min_samples_leaf': 10}

for fold, (idx_train, idx_valid) in enumerate(kf.split(x, y)):
    x_train, y_train = x.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = x.iloc[idx_valid], y.iloc[idx_valid] 

    model = RandomForestRegressor(**rf_params, 
                                   random_state= 0,
                                   n_jobs = -1)  
    
    model.fit(x_train, y_train)
    
    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict(x_test)
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_rf'})
valid_pred_df = valid_pred_df.merge(temp_df,on = 'id')
test_pred_df['pred_rf'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")
"""
### CatBoost_0
"""
temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

cat_params = {'iterations': 13254,
 'od_wait': 1174,
 'learning_rate': 0.010958320637930964,
 'reg_lambda': 0.01860946566788274,
 'subsample': 0.5,
 'random_strength': 5.132433821132644,
 'depth': 4,
 'grow_policy': 'Depthwise',
 'min_child_samples': 16,
 'border_count': 31,
 'bagging_temperature': 0.7740200804146857}

for fold, (idx_train, idx_valid) in enumerate(kf.split(x, y)):
    x_train, y_train = x.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = x.iloc[idx_valid], y.iloc[idx_valid]

    model = cat.CatBoostRegressor(
        random_state=0,
        eval_metric='AUC',
        **cat_params,
    )
    
    model.fit(x_train,y_train,
              eval_set=[(x_valid,y_valid)], 
              early_stopping_rounds=300, 
              verbose=False,
              use_best_model=True)
    
    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict(x_test)
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_cat'})
valid_pred_df = valid_pred_df.merge(temp_df,on = 'id')
test_pred_df['pred_cat'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")
"""
## L1

>> uses the prediction of layer 0 to predict further

>> all the hyperparameters here are differnet from layer 0 
"""
# dataframe to store the OOF prediction
valid_pred_1_df = pd.DataFrame()
test_pred_1_df = pd.DataFrame()

valid_pred_1_df['id'] = valid_pred_df['id']
test_pred_1_df['id'] = test_pred_df['id']

df_val = valid_pred_df.drop('id',axis = 1)
df_test = test_pred_df.drop('id',axis = 1)

df_test.columns = df_val.columns
"""
### LogisticRegresion_1
"""
temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

for fold, (idx_train, idx_valid) in enumerate(kf.split(df_val, y)):
    x_train, y_train = df_val.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = df_val.iloc[idx_valid], y.iloc[idx_valid]  

    model = LogisticRegression()
    
    model.fit(x_train,y_train)
    
    valid_pred = model.predict_proba(x_valid)[:,1]
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict_proba(df_test)[:,1]
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_lr'})
valid_pred_1_df = valid_pred_1_df.merge(temp_df,on = 'id')
test_pred_1_df['pred_lr'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")
"""
### LinearRegression_1
"""
temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

for fold, (idx_train, idx_valid) in enumerate(kf.split(df_val, y)):
    x_train, y_train = df_val.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = df_val.iloc[idx_valid], y.iloc[idx_valid]   

    model = LinearRegression()
    
    model.fit(x_train,y_train)
    
    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict(df_test)
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_lr_1'})
valid_pred_1_df = valid_pred_1_df.merge(temp_df,on = 'id')
test_pred_1_df['pred_lr_1'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")
"""
### CatBoost_1
"""
temp_dict = {}
temp_df = pd.DataFrame()
valid_preds,test_preds,scores = [],[],[]

cat_params = {'iterations': 3990,
 'od_wait': 632,
 'learning_rate': 0.05403922778627244,
 'reg_lambda': 0.2835239884107686,
 'subsample': 0.8,
 'random_strength': 3.224920718394828,
 'depth': 7,
 'grow_policy': 'SymmetricTree',
 'min_child_samples': 71,
 'border_count': 87,
 'bagging_temperature': 0.8797092144468472}

for fold, (idx_train, idx_valid) in enumerate(kf.split(df_val, y)):
    x_train, y_train = df_val.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = df_val.iloc[idx_valid], y.iloc[idx_valid]

    model = cat.CatBoostRegressor(
        random_state=0,
        eval_metric='AUC',
        **cat_params,
    )
    
    model.fit(x_train,y_train,
              eval_set=[(x_valid,y_valid)], 
              early_stopping_rounds=300, 
              verbose=False,
              use_best_model=True)
    
    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    temp_dict.update(dict(zip(x_valid.index,valid_pred)))
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)
    
    test_pred = model.predict(df_test)
    test_preds.append(test_pred)
    
temp_df = pd.DataFrame.from_dict(temp_dict,orient='index').reset_index().rename(columns = {'index':'id',0:'valid_cat'})
valid_pred_1_df = valid_pred_1_df.merge(temp_df,on = 'id')
test_pred_1_df['pred_cat'] = pd.DataFrame(np.column_stack(test_preds).mean(axis = 1))

print(f"Overall Validation Score : {np.mean(scores)}")
"""
## L2
"""
"""
### LinearRegression_2
"""
valid_preds,test_preds,scores = [],[],[]

df_val_final = valid_pred_1_df.drop(['id'],axis = 1)
df_test_final = test_pred_1_df.drop(['id'],axis = 1)
df_test_final.columns = df_val_final.columns

for fold, (idx_train, idx_valid) in enumerate(kf.split(df_val_final, y)):
    x_train, y_train = df_val_final.iloc[idx_train], y.iloc[idx_train]
    x_valid, y_valid = df_val_final.iloc[idx_valid], y.iloc[idx_valid]
    
    model = LinearRegression()
    
    model.fit(x_train,y_train)

    valid_pred = model.predict(x_valid)
    valid_preds.append(valid_pred)

    fpr, tpr, _ = roc_curve(y_valid, valid_pred)
    score = auc(fpr, tpr)
    scores.append(score)
    
    print(f"Fold: {fold + 1} Score: {score}")
    print('--'*25)

    test_pred = model.predict(df_test_final)
    test_preds.append(test_pred)
    
print(f"Overall Validation Score : {np.mean(scores)}")
"""
# Final submission
"""
# saving the submission
submission = pd.read_csv('../input/song-popularity-prediction/sample_submission.csv')
predictions = np.mean(np.column_stack(test_preds),axis=1)
submission['song_popularity'] = predictions
submission.to_csv('submission.csv', index=False)
submission