import pandas as pd
import numpy as np
import xgboost as xgb
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import confusion_matrix, mean_squared_error
from sklearn import metrics




def buildClf(max_depth=2,learning_rate=0.1, n_estimators=5000, gamma=0,
                  min_child_weight=1, max_delta_step=0, subsample=0.8, colsample_bytree=0.8, reg_alpha=0, reg_lambda=1,
                  scale_pos_weight=1, base_score=0.5):
    '''
    创建 XGBClassifier instance
    :param max_depth:叶子节点深度,值越大越容易过拟合。可使用CV 进行调节-- booster 参数
    :param learning_rate:学习率，alias eta-- booster 参数
    :param n_estimators:number of trees
    :param verbosity:0:silent;3:debug replace silent 是否输出模型迭代信息-- 通用参数
    :param objective:目标学习函数-- 学习目标参数
        binary:logistic 二分类的逻辑回归，返回预测的概率(不是类别)。
        multi:softmax 使用softmax的多分类器，返回预测的类别(不是概率)。 需要设置num_class(类别数目)。
        multi:softprob 和multi:softmax参数一样，但是返回的是每个数据属于各个类别的概率
    :param booster:gbtree  gblinear dart-- 通用参数
    :param n_jobs: replaces nthread 进程数-- 通用参数
    :param gamma:如果损失函数下降，则分裂节点。控制最小损失函数下降值-- booster 参数
    :param min_child_weight:最小叶子节点样本权重和。避免过拟合，使用cv进行调整，值大，防过拟合，亦可能欠拟合-- booster 参数
    :param max_delta_step:限制每棵树权重改变的最大步长。0：无约束，>0 保守-- booster 参数
    :param subsample:这个参数控制对于每棵树，随机采样的比例
    :param colsample_bytree:用来控制每棵随机采样的列数的占比(每一列是一个特征)。
    :param reg_alpha:L1 正则项参数
    :param reg_lambda:L2 正则项参数
    :param scale_pos_weight:一般为负样本数/正样本数
    :param base_score:
    :param random_state: replace seed,统一设置为7，仅为随机可复现
    :return:XGBClassifier
    '''
    return xgb.XGBClassifier(max_depth=max_depth,learning_rate=learning_rate,n_estimators=n_estimators,
                             verbosity=0,silent=0,objective='binary:logistic',
                             booster='gbtree',n_jobs=2,nthread=2,gamma=gamma,min_child_weight=min_child_weight,
                             max_delta_step=max_delta_step,subsample=subsample,colsample_bytree=colsample_bytree,
                             reg_alpha=reg_alpha,reg_lambda=reg_lambda,scale_pos_weight=scale_pos_weight,
                             base_score=base_score,random_state=7,seed=7
                             )

def automodelfit(clf,param_grid,dftrain,features,resp, kfold=10,scoring='roc_auc'):
    '''
    模型自动调参
    :param clf : XGBClassifier
    :param param_grid : dict,调参的区间设定
    :param scoring : 调参 评估标准 默认 roc_auc
    :param dftrain:
    :param features:
    :param resp:
    :param kfold:
    :return:
    '''
    # kflod=StratifiedKFold(n_splits=kfold,shuffle=True,random_state=7)
    grid_search=GridSearchCV(clf,param_grid,scoring=scoring,n_jobs=2,cv=kfold,verbose=2,iid=True,refit=True)
    #== 模型训练
    grid_search.fit(dftrain[features],dftrain[resp])
    #== 获取最优参数
    return grid_search


def modelfit(clf, dftrain, features, resp,useTrainCV = True, kfold=10, eval_metric='auc',early_stopping_rounds=20):
    '''
    模型训练
    :type useTrainCV: object
    :param clf:XGBClassifier
    :param dftrain:训练集
    :param features: 特征
    :param resp:label
    :param useTrainCV:if True  call cv function,目的是调节参数 n_estimators
    :param cv_folds: N 折交叉验证
    :param early_stopping_rounds:添加数loss变化不大这个状态持续的轮数，达到这个数就退出训练过程
    :param eval_metric 同 目标函数 objective 有关，取值https://xgboost.readthedocs.io/en/latest/python/python_api.html#
    :return:
    '''
    if dftrain[features].shape[0]==0:
        raise(' NO train data !!!! ')
    if useTrainCV:
        # kflod = StratifiedKFold(n_splits=kfold, shuffle=True, random_state=7)
        xgb_param = clf.get_xgb_params()
        xgtrain = xgb.DMatrix(dftrain[features], label=dftrain[resp])
        cvresult = xgb.cv(xgb_param, xgtrain, num_boost_round=clf.get_params()['n_estimators'], nfold=kfold,
            metrics=eval_metric, early_stopping_rounds=early_stopping_rounds,verbose_eval=True)
        clf.set_params(n_estimators=cvresult.shape[0])

    clf.fit(dftrain[features], dftrain[resp],eval_metric=eval_metric)
    return clf



def predict(clf,df,features):
    '''
    计算预测值
    :param clf:
    :param df:
    :param features:
    :return:
    '''
    df['predict']=clf.predict(df[features])
    df['predict_proba']=clf.predict_proba(df[features])[:,1]
    return df

def auc(clf,df,features,label):
    #== 计算准确率，auc等指标
    df=predict(clf,df,features)
    accu=metrics.accuracy_score(df[label].values, df['predict'].values)
    auc=metrics.roc_auc_score(df[label],df['predict_proba'])
    return {'accuracy':accu,'auc':auc}


def featureImportance(clf,features):
    '''
    获取模型 特征权重
    :param clf:
    :param features:
    :return:
    '''
    # Print Feature Importance:
    feat_imp = pd.Series(clf.get_booster().get_fscore(), features).sort_values(ascending=False, na_position='last')
    feat_imp = feat_imp[feat_imp > 0]
    feat_imp=feat_imp.to_frame().reset_index()
    feat_imp.columns=['feature','weight']
    return feat_imp
