当前位置:   article > 正文

金融风控训练营task3-赛题理解学习笔记_data[fea].apply(lambda x:str('异常值') if x > upper_r

data[fea].apply(lambda x:str('异常值') if x > upper_rule or x < lower_rule e

特征工程

一、学习知识点概要

二、学习内容

1. 导入包并读取数据

pip install catboost --user

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import MinMaxScaler
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostRegressor
import warnings
from sklearn.model_selection import StratifiedKFold, KFold
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
warnings.filterwarnings('ignore')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2. 特征预处理

内容:对一些EDA阶段分析出来的问题进行处理
【首先照task2的方法查找出数据中的对象特征和数值特征】

numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns)
category_fea = list(filter(lambda x: x not in numerical_fea,list(data_train.columns)))
label = 'isDefault'
numerical_fea.remove(label)
  • 1
  • 2
  • 3
  • 4
2.1 缺失值填充
  • 把所有缺失值替换为指定的值0data_train = data_train.fillna(0)
  • 纵向用缺失值上面的值替换缺失值
    data_train = data_train.fillna(axis=0,method='ffill')
  • 纵向用缺失值下面的值替换缺失值,且设置最多只填充两个连续的缺失值
    data_train = data_train.fillna(axis=0,method='bfill',limit=2)

【数据填充函数fillna()】

  • inplace参数的取值:True、False(是否直接修改原对象)
  • method参数的取值 : {‘pad’, ‘ffill’,‘backfill’, ‘bfill’, None}, default None
  • pad/ffill:用前一个非缺失值去填充该缺失值
  • backfill/bfill:用下一个非缺失值填充该缺失值
  • None:指定一个值去替换缺失值
  • limit参数:限制填充个数
  • axis参数:修改填充方向

两种填充示例如下:

#按照平均数填充数值型特征
data_train[numerical_fea] = data_train[numerical_fea].fillna(data_train[numerical_fea].median())
data_test_a[numerical_fea] = data_test_a[numerical_fea].fillna(data_train[numerical_fea].median())
#按照众数填充类别型特征
data_train[category_fea] = data_train[category_fea].fillna(data_train[category_fea].mode())
data_test_a[category_fea] = data_test_a[category_fea].fillna(data_train[category_fea].mode())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
2.2 时间格式处理
#转化成时间格式
for data in [data_train, data_test_a]:
    data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d')
    startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
    #构造时间特征
    data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
    #用数据集中的日期减去开始日期,这时候得到的日期是整数,即直接表明第n天

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
data_train['employmentLength'].value_counts(dropna=False).sort_index()
#dropna参数表示是否去掉nan,默认是true
  • 1
  • 2
def employmentLength_to_int(s):
    if pd.isnull(s):
        return s
    else:
        return np.int8(s.split()[0])
for data in [data_train, data_test_a]:
    data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)
    data['employmentLength'].replace('< 1 year', '0 years', inplace=True)
    data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
2.3 类别特征处理

(1) 用uninque求类型数
原理是data[feature].unique()

(2) 像等级这种类别特征,是有优先级的可以labelencode或者自映射

for data in [data_train, data_test_a]:
    data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
  • 1
  • 2
#类型数在2之上,又不是高维稀疏的,且纯分类特征
for data in [data_train, data_test_a]:
    data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
  • 1
  • 2
  • 3

pandas.get_dummies
是利用pandas实现one hot encode的方式,(在原有列名前面添加前缀,并将对应的值改为1、0的方式表达)

pandas.get_dummies(data, prefix=None, prefix_sep='_', dummy_na=False, columns=None, sparse=False, drop_first=False)[source]

  • prefix :string,字符串列表或字符串dict,默认为None,用于追加DataFrame列名的字符串。在DataFrame上调用get_dummies时,传递一个长度等于列数的列表。或者,前缀 可以是将列名称射到前缀的字典。
  • prefix_sep : string,默认为’_’。如果附加前缀,分隔符/分隔符要使用。或者传递与前缀一样的列表或字典。
  • dummy_na : bool,默认为False如果忽略False NaN,则添加一列以指示NaN。

3. 异常值处理

3.1步骤

分析出现异常的原因 → 然后再考虑处理方法

3.2 要求:

首先,如果这一异常值并不代表一种规律性的,而是极其偶然的现象,或者说你并不想研究这种偶然的现象,这时可以将其删除。其次,如果异常值存在且代表了一种真实存在的现象,那就不能随便删除。在现有的欺诈场景中很多时候欺诈数据本身相对于正常数据勒说就是异常的,我们要把这些异常点纳入,重新拟合模型,研究其规律。能用监督的用监督模型,不能用的还可以考虑用异常检测的算法来做。

3.3 检查异常的方法:

【补充】

  • numpy的相关函数
    • 均值np.mean()
    • 方差np.var()
    • 标准差np.std()
  • **numpy 的 .std() 和 pandas 的 .std() **辨析
    • numpy 计算的是总体(母体)标准差,参数ddof = 0。
    • pandas 计算的是样本标准差,参数ddof = 1。

(1)均方差
在统计学中,如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内。(先实现这样子的范围)

def find_outliers_by_3segama(data,fea):    data_std = np.std(data[fea])    data_mean = np.mean(data[fea])    outliers_cut_off = data_std * 3    lower_rule = data_mean - outliers_cut_off    upper_rule = data_mean + outliers_cut_off    data[fea+'_outliers'] = data[fea].apply(lambda x:str('异常值') if x > upper_rule or x < lower_rule else '正常值')    return data
  • 1

lambda学习参考

得到特征的异常值后可以进一步分析变量异常值和目标变量的关系

data_train = data_train.copy()for fea in numerical_fea:    data_train = find_outliers_by_3segama(data_train,fea)    print(data_train[fea+'_outliers'].value_counts())    print(data_train.groupby(fea+'_outliers')['isDefault'].sum())    print('*'*10)
  • 1

(例如可以看到异常值在两个变量上的分布几乎复合整体的分布,如果异常值都属于为1的用户数据里面代表,这不是异常值)??
删除异常值操作

for fea in numerical_fea:    data_train = data_train[data_train[fea+'_outliers']=='正常值']    data_train = data_train.reset_index(drop=True) 
  • 1

(2) 箱型图
概念: 是一种用作显示一组数据分散情况资料的统计图(可以用来反映一组或多组连续型定量数据分布的中心位置和散布范围),形状如箱子
总结:四分位数会将数据分为三个点和四个区间,IQR = Q3 -Q1,下触须=Q1 − 1.5x IQR,上触须=Q3 + 1.5x IQR;
绘制方法:先找出一组数据的上边缘、下边缘、中位数和两个四分位数;然后, 连接两个四分位数画出箱体;再将上边缘和下边缘与箱体相连接,中位数在箱体中间。
具体实例:(来源百度百科)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSuVDuaI-1619696698666)(C:\Users\10175\Desktop\箱型图.png)]

反映数据:

  • 最小值(minimum)=5
  • 下四分位数(Q1)=7
  • 中位数(Med–也就是Q2)=8.5
  • 上四分位数(Q3)=9
  • 最大值(maximum)=10
  • 平均值=8
  • 四分位间距(interquartile range)={\displaystyle Q3-Q1}=2 (即ΔQ)

在区间 Q3+1.5ΔQ, Q1-1.5ΔQ 之外的值被视为应忽略(farout)。

  • farout: 在图上不予显示,仅标注一个符号∇。
  • 最大值区间: Q3+1.5ΔQ
  • 最小值区间: Q1-1.5ΔQ

最大值与最小值产生于这个区间。区间外的值被视为outlier显示在图上.

  • mild outlier = 3.5
  • extreme outlier = 0.5

学习参考

4.数据分桶

4.1 简介

特征分箱的目的:
从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度。从而使模型更加稳定。
数据分桶的对象:
① 将连续变量离散化
② 将多状态的离散变量合并成少状态
分箱的原因:
数据的特征内的值跨度可能比较大,对有监督和无监督中如k-均值聚类它使用欧氏距离作为相似度函数来测量数据点之间的相似度。都会造成大吃小的影响,其中一种解决方法是对计数值进行区间量化即数据分桶也叫做数据分箱,然后使用量化后的结果。
分箱的优点:
① 处理缺失值:当数据源可能存在缺失值,此时可以把null单独作为一个分箱。
② 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
③ 业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在着非线性关系,此时可经过WOE变换。
分箱的基本原则:
(1)最小分箱占比不低于5%
(2)箱内不能全部是好客户
(3)连续箱单调

4.2.1 固定宽度分箱

当数值横跨多个数量级时,最好按照 10 的幂(或任何常数的幂)来进行分组:09、1099、100999、10009999,等等。固定宽度分箱非常容易计算,但如果计数值中有比较大的缺口,就会产生很多没有任何数据的空箱子。

# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000)
  • 1
## 通过对数函数映射到指数宽度分箱data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))
  • 1
4.2.2 分位数分箱
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
  • 1
4.2.3 卡方分箱及其他分箱方法的尝试

5. 特征交互

交互特征的构造非常简单,使用起来却代价不菲。如果线性模型中包含有交互特征对,那它的训练时间和评分时间就会从 O(n) 增加到 O(n2),其中 n 是单一特征的数量。

for col in ['grade', 'subGrade']:     temp_dict = data_train.groupby([col])['isDefault'].agg(['mean']).reset_index().rename(columns={'mean': col + '_target_mean'})    temp_dict.index = temp_dict[col].values    temp_dict = temp_dict[col + '_target_mean'].to_dict()    data_train[col + '_target_mean'] = data_train[col].map(temp_dict)    data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict)
  • 1
# 其他衍生变量 mean 和 stdfor df in [data_train, data_test_a]:    for item in ['n0','n1','n2','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:        df['grade_to_mean_' + item] = df['grade'] / df.groupby([item])['grade'].transform('mean')        df['grade_to_std_' + item] = df['grade'] / df.groupby([item])['grade'].transform('std')
  • 1

学习参考

6.特征编码

labelEncode 直接入树模型中

#label-encode:subGrade,postCode,title# 高维类别特征需要进行转换for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']):    le = LabelEncoder()    le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values))    data_train[col] = le.transform(list(data_train[col].astype(str).values))    data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values))print('Label Encoding 完成')
  • 1
逻辑回归等模型要单独增加的特征工程
  • 对特征做归一化(即将数据统一映射到[0,1]区间上),去除相关性高的特征
  • 归一化目的是让训练过程更好更快的收敛,避免特征大吃小的问题
  • 去除相关性是增加模型的可解释性,加快预测过程。
# 举例归一化过程#伪代码for fea in [要归一化的特征列表]:    data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))
  • 1

7.特征选择

特征选择技术可以精简掉无用的特征,以降低最终模型的复杂性,它的最终目的是得到一个简约模型,在不降低预测准确率或对预测准确率影响不大的情况下提高计算速度。特征选择不是为了减少训练时间(实际上,一些技术会增加总体训练时间),而是为了减少模型评分时间。

7.1 方法

1) Filter

基于特征间的关系进行筛选

  • 方差选择法
  • 相关系数法(pearson 相关系数)
  • 卡方检验
  • 互信息法

2) Wrapper (RFE)

  • 递归特征消除法

3 )Embedded

  • 基于惩罚项的特征选择法
  • 基于树模型的特征选择
7.2

本数据集中我们删除非入模特征后,并对缺失值填充,然后用计算协方差的方式看一下特征间相关性,然后进行模型训练

# 删除不需要的数据for data in [data_train, data_test_a]:    data.drop(['issueDate'], axis=1,inplace=True)
  • 1
"纵向用缺失值上面的值替换缺失值"data_train = data_train.fillna(axis=0,method='ffill')
  • 1
x_train = data_train#计算协方差data_corr = x_train.corrwith(data_train.isDefault) #计算相关性result = pd.DataFrame(columns=['features', 'corr'])result['features'] = data_corr.indexresult['corr'] = data_corr.values
  • 1
# 当然也可以直接看图data_numeric = data_train[numerical_fea]correlation = data_numeric.corr()f , ax = plt.subplots(figsize = (7, 7))plt.title('Correlation of Numeric Features with Price',y=1,size=16)sns.heatmap(correlation,square = True,  vmax=0.8)
  • 1
features = [f for f in data_train.columns if f not in ['id','issueDate','isDefault'] and '_outliers' not in f]x_train = data_train[features]x_test = data_test_a[features]y_train = data_train['isDefault']
  • 1
def cv_model(clf, train_x, train_y, test_x, clf_name):    folds = 5    seed = 2020    kf = KFold(n_splits=folds, shuffle=True, random_state=seed)    train = np.zeros(train_x.shape[0])    test = np.zeros(test_x.shape[0])    cv_scores = []    for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):        print('************************************ {} ************************************'.format(str(i+1)))        trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]        if clf_name == "lgb":            train_matrix = clf.Dataset(trn_x, label=trn_y)            valid_matrix = clf.Dataset(val_x, label=val_y)            params = {                'boosting_type': 'gbdt',                'objective': 'binary',                'metric': 'auc',                'min_child_weight': 5,                'num_leaves': 2 ** 5,                'lambda_l2': 10,                'feature_fraction': 0.8,                'bagging_fraction': 0.8,                'bagging_freq': 4,                'learning_rate': 0.1,                'seed': 2020,                'nthread': 28,                'n_jobs':24,                'silent': True,                'verbose': -1,            }            model = clf.train(params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix], verbose_eval=200,early_stopping_rounds=200)            val_pred = model.predict(val_x, num_iteration=model.best_iteration)            test_pred = model.predict(test_x, num_iteration=model.best_iteration)                        # print(list(sorted(zip(features, model.feature_importance("gain")), key=lambda x: x[1], reverse=True))[:20])                        if clf_name == "xgb":            train_matrix = clf.DMatrix(trn_x , label=trn_y)            valid_matrix = clf.DMatrix(val_x , label=val_y)                        params = {'booster': 'gbtree',                      'objective': 'binary:logistic',                      'eval_metric': 'auc',                      'gamma': 1,                      'min_child_weight': 1.5,                      'max_depth': 5,                      'lambda': 10,                      'subsample': 0.7,                      'colsample_bytree': 0.7,                      'colsample_bylevel': 0.7,                      'eta': 0.04,                      'tree_method': 'exact',                      'seed': 2020,                      'nthread': 36,                      "silent": True,                      }                        watchlist = [(train_matrix, 'train'),(valid_matrix, 'eval')]                        model = clf.train(params, train_matrix, num_boost_round=50000, evals=watchlist, verbose_eval=200, early_stopping_rounds=200)            val_pred  = model.predict(valid_matrix, ntree_limit=model.best_ntree_limit)            test_pred = model.predict(test_x , ntree_limit=model.best_ntree_limit)                         if clf_name == "cat":            params = {'learning_rate': 0.05, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type': 'Bernoulli',                      'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False}                        model = clf(iterations=20000, **params)            model.fit(trn_x, trn_y, eval_set=(val_x, val_y),                      cat_features=[], use_best_model=True, verbose=500)                        val_pred  = model.predict(val_x)            test_pred = model.predict(test_x)                    train[valid_index] = val_pred        test = test_pred / kf.n_splits        cv_scores.append(roc_auc_score(val_y, val_pred))                print(cv_scores)            print("%s_scotrainre_list:" % clf_name, cv_scores)    print("%s_score_mean:" % clf_name, np.mean(cv_scores))    print("%s_score_std:" % clf_name, np.std(cv_scores))    return train, test
  • 1
def lgb_model(x_train, y_train, x_test):
    lgb_train, lgb_test = cv_model(lgb, x_train, y_train, x_test, "lgb")
    return lgb_train, lgb_test

def xgb_model(x_train, y_train, x_test):
    xgb_train, xgb_test = cv_model(xgb, x_train, y_train, x_test, "xgb")
    return xgb_train, xgb_test

def cat_model(x_train, y_train, x_test):
    cat_train, cat_test = cv_model(CatBoostRegressor, x_train, y_train, x_test, "cat")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
lgb_train, lgb_test = lgb_model(x_train, y_train, x_test)
  • 1

三、学习问题和解答

  1. 有很多numpy和pandas的知识忘得差不多了,比如一些用来数据分析和函数和方法等,要另外打开之前的学习资料和笔记进行回顾
  2. 涉及到机器学习的很多知识之前都没有接触过,但是上网找了参考资料,也对参考代码进行层层剖析,梳理几次之后还是能够差不多看懂的

四、学习思考与总结

学习文档里面有提到“特征工程是机器学习,甚至是深度学习中最为重要的一部分”,而这恰恰是耗时最长的一步,总体来说我的学习还是比较吃力。需要在落实好了文件里面提出的常用方法之外,还要学会自己多加探索,多加尝试。争取不漏过实践用到的每一个知识点!
最后!学习时间方面要加长!!

笔记主要参考来源

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/142872
推荐阅读
相关标签
  

闽ICP备14008679号