# coding: utf-8
#!/usr/bin/python
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

import sys
import pandas as pd
import numpy as np
import xgboost as xgb
import argparse
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.externals import joblib
from sklearn.cross_validation import train_test_split
from sklearn.metrics import roc_curve
from sklearn.metrics import roc_auc_score
import math

#----------------------------------------------------------------------------------
'''
xgb_train：封装了常规的xgboost的训练流程
参数设置：
save_path(str):保存模型的路径，不要添加模型的名字，模型会命名为xgb_model和lr_model
dtrain(DMatrixs):训练集数据
dtest(DMatrixs):测试集数据
parameter(dict):xgboost的参数集，存在默认设置
train_round(int):训练轮数，默认设置100轮
early_stopping(int):提前终止轮数，默认设置10
lr_flag(bool):是否融合LR模型标志位
y_train(list/Serise):LR训练数据的标签，本函数中是dtrain的标签

返回值：
bst(xgboost model):xgboost模型文件
lr_clf(LR model)：LR模型文件
'''
def xgb_train(save_path, dtrain, dtest, parameter = None, train_round = 100, early_stopping=10, lr_flag = False, y_train = None):
    if parameter:
        param = parameter
    else:
        param = {'max_depth':3, 
                 'eta':0.05,
                 'min_child_weight':4,
                 'gamma':0, 
                 'subsample':0.8,
                 'colsample_bytree':0.8,
                 'scale_pos_weight':0.8,
                 'silent':1, 
                 'objective':'binary:logistic',
                 'eval_metric': 'auc'
                  }
    param['nthread'] = -1
    
    plst = param.items()
    
    watchlist = [ (dtrain,'train') ,(dtest,'test')]  
    
    #训练保存模型
    bst = xgb.train(plst, dtrain, train_round, watchlist, early_stopping_rounds=early_stopping, verbose_eval=True)
    bst.save_model(save_path + 'xgb_model')
    if lr_flag:
        #每个样本在每棵树上的叶子的ID
        train_xgb_pred_mat = bst.predict(dtrain,pred_leaf =True)
        #编码成One-hot编码
        one_hot_encoder = OneHotEncoder()
        train_lr_feature_mat = one_hot_encoder.fit_transform(train_xgb_pred_mat)
        
        #入LR模型进行训练
        lr_clf = LogisticRegression(penalty='l1',C= 0.05, n_jobs=4)
        lr_clf.fit(train_lr_feature_mat,y_train)
        #保存lr模型
        joblib.dump(lr_clf, save_path + 'lr_model')
        return bst, lr_clf
        print ('----------------xgb&lr trained successfully!----------------')
    print ('----------------xgb trained successfully!----------------')
    return bst

#----------------------------------------------------------------------------------
    

#----------------------------------------------------------------------------------
'''
cal_auc_ks：封装了计算模型预测的auc和ks的功能
参数设置：
y_true(list):待测试样本的标签list
y_pred(list):待测试样本经过模型的预测list
name(str):标识结果的字段
save(bool)：保存标识位

返回值：
auc(double):模型分auc
ks(double)：模型分ks
'''
def cal_ks(label,score):
    fpr,tpr,thresholds= roc_curve(label,score)
    return max(tpr-fpr)
    
def cal_auc_ks(y_true, y_pred, name = None, save=False):
    sample = name + " Sample : %s" % len(y_true)
    auc = name + ' test_set auc : %0.3f' % roc_auc_score(y_true, y_pred)
    ks = name + ' test_set ks  : %0.3f' % cal_ks(y_true,y_pred) 
    print (sample)
    print (auc)
    print (ks)
    print ('----------------cal_auc_ks process successfully!----------------')
    if save:
        with open(name + '_auc&ks.txt', 'a+') as f:
            f.write(sample + '\n' + auc + '\n' + ks + '\n' + '------------------------------------' + '\n' )
            print ('----------------cal_auc_ks save successfully!----------------')
    return roc_auc_score(y_true, y_pred), cal_ks(y_true,y_pred) 
#----------------------------------------------------------------------------------

#----------------------------------------------------------------------------------
'''
imp_report:封装了输出特征重要程度的功能
参数设置：
model(xgboost model file):输入模型，不能使模型文件的地址，必须先读入模型后传入
trainData(DataFrame)：输入到模型的数据，必须带有表头，否则输出的也得对应的列编号
whole_column(array/Serise):数据集的全部列名，大多数时候输入到模型的数据和源数据表维度上有差异，因此设置这个参数，当然，如果没有差异也可以传入trainData.columns

返回值：
imp(list):按照whole_column顺序的特征重要度，没选中的重要度是0
'''
def imp_report(model, trainData, whole_column):
    #模型输出的特征编号和特征表的是不对应的，他是从0开始的连续性index，需要做一个转换对应
    #modelFeat的index是模型输出特征的index，而对应的value是特征表的列名
    column = list(whole_column)
    modelFeat=[]
    for colName in trainData.columns:
        modelFeat.append(colName)
    
    #输出重要变量得分
    #xgb.plot_importance(bst)
    imp = model.get_fscore()
    imp_temp = {}
    
    #把重要的特征的key转换为特征表的key
    for key, value in imp.items():
        k = int(key[1:])
        imp_temp[modelFeat[k]] = value
    imp = imp_temp
    temp = []
    
    for index, value in enumerate(column):
        if value in imp.keys():
            temp.append(imp[value])
        else:
            temp.append(0)
    
#    imp = np.array(temp)
    imp = temp
    print ('----------------imp_feat process successfully!----------------')
    return imp    
#----------------------------------------------------------------------------------

#----------------------------------------------------------------------------------
'''    
cal_feat_ks:封装了计算单变量ks的功能
参数设置：
df(DataFrame):读入所有变量数据的DataFrame
valueCol(str)：df中要计算ks的列名，这里只支持单值传入，计算多个变量请用list的for循环传入
labelCol(str):df中的标签列名
seg(int):ks分组数，默认10组

返回值：
ksList(list):各分组的ks中间计算值
ks(double):单变量ks的值
'''
def cal_feat_ks(df, valueCol, labelCol, seg=10):
    segPoint=[(1/float(seg))*i for i in range(1,seg+1)]
    segValue=[]
    for point in segPoint:
        segValue.append(df[valueCol].quantile(point))
    #tp、fp每个分段内累计1和0的样本个数
    #这里必须加float，不然会整数除法
    tp=[float(df[(df[valueCol]<=a)&(df[labelCol]==1)][labelCol].count()) for a in segValue]
    fp=[float(df[(df[valueCol]<=a)&(df[labelCol]==0)][labelCol].count()) for a in segValue]
    #left：分段内累计样本数
    left=[df[(df[valueCol]<=a)][labelCol].count() for a in segValue]
    #tp_r：大于分段的逾期为1的样本数
    tp_r=[df[(df[valueCol]>a)&(df[labelCol]==1)][labelCol].count() for a in segValue]
    #right：大于分段所有样本
    right=[df[(df[valueCol]>a)][labelCol].count() for a in segValue]
    #所有逾期=1的样本数
    ts=df[df[labelCol]==1][labelCol].count()
    #所有逾期=0的样本数
    fs=df[df[labelCol]==0][labelCol].count()    
    
    ksList = abs((tp/ts)-(fp/fs))
    ks = max(ksList)
    return ksList,ks

#----------------------------------------------------------------------------------

#----------------------------------------------------------------------------------
'''    
cal_feat_iv:封装了计算单变量ks的功能
参数设置：
df(DataFrame):读入所有变量数据的DataFrame
valueCol(str)：df中要计算ks的列名，这里只支持单值传入，计算多个变量请用list的for循环传入
labelCol(str):df中的标签列名
seg(int):ks分组数，默认10组

返回值：
woe(list):单变量woe值
ks(double):单变量ks的值
'''

def cal_feat_iv(df, valueCol, labelCol, seg=10):
    segPoint=[(1/float(seg))*i for i in range(1,seg+1)]
    segValue=[]
    for point in segPoint:
        segValue.append(df[valueCol].quantile(point))
    #tp、fp每个分段内累计1和0的样本个数
    #这里必须加float，不然会整数除法
    tp=[float(df[(df[valueCol]<=a)&(df[labelCol]==1)][labelCol].count()) for a in segValue]
    fp=[float(df[(df[valueCol]<=a)&(df[labelCol]==0)][labelCol].count()) for a in segValue]
    #left：分段内累计样本数
    left=[df[(df[valueCol]<=a)][labelCol].count() for a in segValue]
    #tp_r：大于分段的逾期为1的样本数
    tp_r=[df[(df[valueCol]>a)&(df[labelCol]==1)][labelCol].count() for a in segValue]
    #right：大于分段所有样本
    right=[df[(df[valueCol]>a)][labelCol].count() for a in segValue]
    #所有逾期=1的样本数
    ts=df[df[labelCol]==1][labelCol].count()
    #所有逾期=0的样本数
    fs=df[df[labelCol]==0][labelCol].count()    
    
    weight = (tp/ts)-(fp/fs)
    woe = []
    for i in range(seg):
       woe.append(math.log((tp[i]/ts)/(fp[i]/fs)))
    iv = 0
    for i in range(seg):
        iv += weight[i] * woe[i]
    return woe,iv
#----------------------------------------------------------------------------------