• 金融风控贷款预测之特征工程task3


    import pandas as pd
    import numpy as np
    import warnings
    warnings.filterwarnings('ignore')
    import matplotlib.pyplot as plt
    import seaborn as sns
    
    train = pd.read_csv('data/train.csv')
    testa = pd.read_csv('data/testA.csv')
    train_copy = train.copy()
    testa_copy = testa.copy()
    

    提取不同数据类型数据

    # 获取object对象
    # 通过pandas.dataframe的select_dtypes方法
    numerical_fea = list(train_copy.select_dtypes(exclude=['object']).columns)
    
    # 通过filter函数实现差集运算
    category_fea = list(filter(lambda x: x not in numerical_fea, list(train_copy.columns)))
    
    # 在numerical_fea变量踢去标签列isDefault
    label = 'isDefault'
    # list的remove方法删除指定值
    numerical_fea.remove(label)
    
    # 打印不同数据类型的列名
    print(numerical_fea)
    print(category_fea)
    print(len(numerical_fea), len(category_fea))
    
    ['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'employmentTitle', 'homeOwnership', 'annualIncome', 'verificationStatus', 'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years', 'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec', 'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc', 'initialListStatus', 'applicationType', 'title', 'policyCode', 'n0', 'n1', 'n2', 'n2.1', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9', 'n10', 'n11', 'n12', 'n13', 'n14']
    ['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
    41 5
    

    填充空缺值

    # 查看缺失值
    # object类型只有employmentLength存在缺失值
    for i in [numerical_fea, category_fea]:
        tmp = train_copy[i].isnull().sum()
        print(tmp[tmp>0])
        print('*'*50)
    
    employmentTitle           1
    postCode                  1
    dti                     239
    pubRecBankruptcies      405
    revolUtil               531
    title                     1
    n0                    40270
    n1                    40270
    n2                    40270
    n2.1                  40270
    n4                    33239
    n5                    40270
    n6                    40270
    n7                    40270
    n8                    40271
    n9                    40270
    n10                   33239
    n11                   69752
    n12                   40270
    n13                   40270
    n14                   40270
    dtype: int64
    **************************************************
    employmentLength    46799
    dtype: int64
    **************************************************
    
    # 暂时不处理testa
    ############################
    # numerical_fea
    # 使用存在缺失值样本对应label类别的的中位数填充, 该方法没法给testa同样的处理。后续 会改变处理方法
    # 这里仅尝试
    for i in [0,1]:
        train_copy.loc[train_copy['isDefault']==i,numerical_fea] = train_copy.loc[train['isDefault']==i,numerical_fea]
                                                        .fillna(train_copy.loc[train_copy['isDefault']==i,numerical_fea].median())
    # 查看填充后的结果
    print(train_copy.isnull().sum())
    
    id                        0
    loanAmnt                  0
    term                      0
    interestRate              0
    installment               0
    grade                     0
    subGrade                  0
    employmentTitle           0
    employmentLength      46799
    homeOwnership             0
    annualIncome              0
    verificationStatus        0
    issueDate                 0
    isDefault                 0
    purpose                   0
    postCode                  0
    regionCode                0
    dti                       0
    delinquency_2years        0
    ficoRangeLow              0
    ficoRangeHigh             0
    openAcc                   0
    pubRec                    0
    pubRecBankruptcies        0
    revolBal                  0
    revolUtil                 0
    totalAcc                  0
    initialListStatus         0
    applicationType           0
    earliesCreditLine         0
    title                     0
    policyCode                0
    n0                        0
    n1                        0
    n2                        0
    n2.1                      0
    n4                        0
    n5                        0
    n6                        0
    n7                        0
    n8                        0
    n9                        0
    n10                       0
    n11                       0
    n12                       0
    n13                       0
    n14                       0
    dtype: int64
    
    # 查看employmentLength
    # 10+的最多,缺失的数据有4w+
    print(train['employmentLength'].value_counts(dropna=False).sort_values())
    print(train['employmentLength'].value_counts(dropna=False).sort_index())
    
    9 years       30272
    7 years       35407
    8 years       36192
    6 years       37254
    NaN           46799
    4 years       47985
    5 years       50102
    1 year        52489
    3 years       64152
    < 1 year      64237
    2 years       72358
    10+ years    262753
    Name: employmentLength, dtype: int64
    
    
    
    
    
    1 year        52489
    10+ years    262753
    2 years       72358
    3 years       64152
    4 years       47985
    5 years       50102
    6 years       37254
    7 years       35407
    8 years       36192
    9 years       30272
    < 1 year      64237
    NaN           46799
    Name: employmentLength, dtype: int64
    
    # 转换employmentLength
    # 10+ -> 10 
    # 1 -> 0
    # NaN 暂时不动
    def employmentLength_to_int(s):
        if pd.isnull(s):
            return s 
        else:
            return np.int8(s.split()[0])
    # for data in [train_copy, testa_copy]: # 暂不处理testa
    train_copy['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)
    train_copy['employmentLength'].replace(to_replace='< 1 year', value='1 year', inplace=True)
    

    转换

    # employmentLength
    train_copy.employmentLength = train_copy.employmentLength.apply(employmentLength_to_int)
    print(train_copy['employmentLength'].value_counts(dropna=False).sort_values())
    
    9.0      30272
    7.0      35407
    8.0      36192
    6.0      37254
    NaN      46799
    4.0      47985
    5.0      50102
    3.0      64152
    2.0      72358
    1.0     116726
    10.0    262753
    Name: employmentLength, dtype: int64
    
    pd.set_option('max_row',1000)
    
    # 查看  issueDate值得类型  : str
    # 因此issue为标准的时间字符,先不用动
    # 最早发放贷款日期为2007-06-01; 
    type(train_copy.issueDate[0])
    (train_copy.issueDate.value_counts().sort_index())
    
    2007-06-01        1
    2007-07-01       21
    2007-08-01       23
    2007-09-01        7
    2007-10-01       26
    2007-11-01       24
    2007-12-01       55
    2008-01-01       91
    2008-02-01      105
    2008-03-01      130
    2008-04-01       92
    2008-05-01       38
    2008-06-01       33
    2008-07-01       52
    2008-08-01       38
    2008-09-01       19
    2008-10-01       62
    2008-11-01      113
    2008-12-01      134
    2009-01-01      145
    2009-02-01      160
    2009-03-01      162
    2009-04-01      166
    2009-05-01      190
    2009-06-01      191
    2009-07-01      223
    2009-08-01      231
    2009-09-01      270
    2009-10-01      305
    2009-11-01      376
    2009-12-01      362
    2010-01-01      355
    2010-02-01      394
    2010-03-01      418
    2010-04-01      481
    2010-05-01      578
    2010-06-01      600
    2010-07-01      654
    2010-08-01      677
    2010-09-01      623
    2010-10-01      670
    2010-11-01      646
    2010-12-01      765
    2011-01-01      855
    2011-02-01      812
    2011-03-01      850
    2011-04-01      917
    2011-05-01     1019
    2011-06-01     1087
    2011-07-01     1096
    2011-08-01     1139
    2011-09-01     1238
    2011-10-01     1258
    2011-11-01     1343
    2011-12-01     1310
    2012-01-01     1566
    2012-02-01     1566
    2012-03-01     1740
    2012-04-01     1951
    2012-05-01     1980
    2012-06-01     2299
    2012-07-01     2774
    2012-08-01     3265
    2012-09-01     3661
    2012-10-01     3693
    2012-11-01     3849
    2012-12-01     3551
    2013-01-01     4016
    2013-02-01     4462
    2013-03-01     4918
    2013-04-01     5627
    2013-05-01     6116
    2013-06-01     6424
    2013-07-01     7052
    2013-08-01     7490
    2013-09-01     7733
    2013-10-01     8409
    2013-11-01     8748
    2013-12-01     8948
    2014-01-01     9273
    2014-02-01     9105
    2014-03-01     9645
    2014-04-01    10830
    2014-05-01    10886
    2014-06-01     9665
    2014-07-01    16355
    2014-08-01    10648
    2014-09-01     5898
    2014-10-01    21461
    2014-11-01    13793
    2014-12-01     5528
    2015-01-01    19254
    2015-02-01    12881
    2015-03-01    13549
    2015-04-01    18929
    2015-05-01    17119
    2015-06-01    15236
    2015-07-01    24496
    2015-08-01    18750
    2015-09-01    14950
    2015-10-01    25525
    2015-11-01    19453
    2015-12-01    23245
    2016-01-01    16792
    2016-02-01    20571
    2016-03-01    29066
    2016-04-01    14248
    2016-05-01    10680
    2016-06-01    12270
    2016-07-01    12835
    2016-08-01    13301
    2016-09-01    10165
    2016-10-01    11245
    2016-11-01    11172
    2016-12-01    11562
    2017-01-01     9757
    2017-02-01     8057
    2017-03-01    10068
    2017-04-01     7746
    2017-05-01     9620
    2017-06-01     9005
    2017-07-01     8861
    2017-08-01     9172
    2017-09-01     8100
    2017-10-01     7129
    2017-11-01     7306
    2017-12-01     5915
    2018-01-01     5176
    2018-02-01     3995
    2018-03-01     4228
    2018-04-01     4160
    2018-05-01     3933
    2018-06-01     2878
    2018-07-01     2550
    2018-08-01     2108
    2018-09-01     1427
    2018-10-01     1252
    2018-11-01      962
    2018-12-01      746
    Name: issueDate, dtype: int64
    
    # 查看 earliesCreditLine
    # 共720个不同值
    print(len(train_copy.earliesCreditLine.value_counts()))
    print(train_copy.earliesCreditLine.value_counts().head())
    # 使用calendar模块,将月份英文简写转换为数字在拼接如200108
    # calendar属性month_abbr可以得到月份英文简写和数字得转换
    import calendar
    month_abbr = list(calendar.month_abbr)
    
    720
    Aug-2001    5567
    Sep-2003    5403
    Aug-2002    5403
    Oct-2001    5258
    Aug-2000    5246
    Name: earliesCreditLine, dtype: int64
    
    train_copy.earliesCreditLine[0].split('-')
    
    ['Aug', '2001']
    
    def transform_earliesCreditLine(s):
        tmp = s.strip().split('-')
        tmp[0] = str(month_abbr.index(tmp[0]))
        # 将1位得数字,如8,前面补零。 08
        if len(tmp[0]) == 1:
            tmp[0] = '0' + tmp[0]
        
        return ''.join([tmp[1],tmp[0]])
    #  这样转换是便于后面时间上得特征构造
    train_copy.earliesCreditLine = train_copy.earliesCreditLine.apply(transform_earliesCreditLine)
    
    train_copy[['earliesCreditLine','issueDate']].head()
    
    earliesCreditLine issueDate
    0 200108 2014-07-01
    1 200205 2012-08-01
    2 200605 2015-10-01
    3 199905 2015-08-01
    4 197708 2016-03-01
    # 类别型数据还剩grade ,subGrade 没处理
    # 另外 employmentLength 得空缺值还未处理
    # 这样先将grade, subGrade 标签化
    # grade明显有等级区别
    train_copy.grade = train_copy.grade.map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
    
    # subGrade也有等级区别
    # 查看subGrade
    # a-g, 每个等级有5个小等级区别,因此准备转换,如 c1,c2 -> 3.1,3.2
    # train_copy.subGrade.value_counts()
    transform_subGrade_map = {'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7}
    def transform_subGrade(s):
        return float(str(transform_subGrade_map.get(s.strip()[0])) + '.' + s[1:])
    
    train_copy.subGrade = train_copy.subGrade.apply(transform_subGrade)
    
    print(train_copy.subGrade.head())
    
    0    5.2
    1    4.2
    2    4.3
    3    1.4
    4    3.2
    Name: subGrade, dtype: float64
    
    pd.set_option('max_column', 1000)
    train_copy.head()
    
    id loanAmnt term interestRate installment grade subGrade employmentTitle employmentLength homeOwnership annualIncome verificationStatus issueDate isDefault purpose postCode regionCode dti delinquency_2years ficoRangeLow ficoRangeHigh openAcc pubRec pubRecBankruptcies revolBal revolUtil totalAcc initialListStatus applicationType earliesCreditLine title policyCode n0 n1 n2 n2.1 n4 n5 n6 n7 n8 n9 n10 n11 n12 n13 n14
    0 0 35000.0 5 19.52 917.97 5 5.2 320.0 2.0 2 110000.0 2 2014-07-01 1 1 137.0 32 17.05 0.0 730.0 734.0 7.0 0.0 0.0 24178.0 48.9 27.0 0 0 200108 1.0 1.0 0.0 2.0 2.0 2.0 4.0 9.0 8.0 4.0 12.0 2.0 7.0 0.0 0.0 0.0 2.0
    1 1 18000.0 5 18.49 461.90 4 4.2 219843.0 5.0 0 46000.0 2 2012-08-01 0 0 156.0 18 27.83 0.0 700.0 704.0 13.0 0.0 0.0 15096.0 38.9 18.0 1 0 200205 1723.0 1.0 0.0 3.0 5.0 5.0 10.0 7.0 7.0 7.0 13.0 5.0 13.0 0.0 0.0 0.0 2.0
    2 2 12000.0 5 16.99 298.17 4 4.3 31698.0 8.0 0 74000.0 2 2015-10-01 0 0 337.0 14 22.77 0.0 675.0 679.0 11.0 0.0 0.0 4606.0 51.8 27.0 0 0 200605 0.0 1.0 0.0 0.0 3.0 3.0 0.0 0.0 21.0 4.0 5.0 3.0 11.0 0.0 0.0 0.0 4.0
    3 3 11000.0 3 7.26 340.96 1 1.4 46854.0 10.0 1 118000.0 1 2015-08-01 0 4 148.0 11 17.21 0.0 685.0 689.0 9.0 0.0 0.0 9948.0 52.6 28.0 1 0 199905 4.0 1.0 6.0 4.0 6.0 6.0 4.0 16.0 4.0 7.0 21.0 6.0 9.0 0.0 0.0 0.0 1.0
    4 4 3000.0 3 12.99 101.07 3 3.2 54.0 NaN 1 29000.0 2 2016-03-01 0 10 301.0 21 32.16 0.0 690.0 694.0 12.0 0.0 0.0 2942.0 32.0 27.0 0 0 197708 11.0 1.0 1.0 2.0 7.0 7.0 2.0 4.0 9.0 10.0 15.0 7.0 12.0 0.0 0.0 0.0 4.0
    # 查看非数值类型列
    # 只有两个时间列,且值都为str, 为之后特征构造打下基础
    train_copy.select_dtypes(include=['object']).head()
    
    issueDate earliesCreditLine
    0 2014-07-01 200108
    1 2012-08-01 200205
    2 2015-10-01 200605
    3 2015-08-01 199905
    4 2016-03-01 197708

    查看异常值

    在统计学中,如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差

    范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内。

    1. 采用均方差检测异常值
      • 在这个区间外的样本为异常值[mean-3std, mean+3std]
    2. 箱型检测异常值
      • 在这个区间外的样本为异常值[q1-3 * iqr, q3+3 * iqr],iqr=q3-q1
    #  均方差检测
    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_off = data_mean - outliers_cut_off
        upper_off = data_mean + outliers_cut_off
        data[fea + '_outliers'] = data[fea].apply(lambda x:str('异常值') if x<lower_off or x>upper_off else str('正常值'))
        return data
    
    
    • 没有异常值的变量:id,loanAmnt, term ,employmentTitle ,verificationStatus ,initialListStatus ,policyCode
    • 异常值在label=1上的分布>或<整体分布一个百分点的变量:interestRate,dti ,ficoRangeLow ,ficoRangeHigh ,n14
    print('在label=1上的整体分布:',round(train_copy.isDefault.sum()/train_copy.shape[0],2))
    for fea in numerical_fea:
        train_copy = find_outliers_by_3segama(train_copy, fea)
        print(fea,'异常值在label=1上的分布:',round(train_copy.groupby(fea + '_outliers')['isDefault'].sum()/train_copy[train_copy[fea + '_outliers']=='异常值'].shape[0],2)[0])
    
    
    在label=1上的整体分布: 0.2
    id 异常值在label=1上的分布: inf
    loanAmnt 异常值在label=1上的分布: inf
    term 异常值在label=1上的分布: inf
    interestRate 异常值在label=1上的分布: 0.51
    installment 异常值在label=1上的分布: 0.27
    employmentTitle 异常值在label=1上的分布: inf
    homeOwnership 异常值在label=1上的分布: 0.21
    annualIncome 异常值在label=1上的分布: 0.13
    verificationStatus 异常值在label=1上的分布: inf
    purpose 异常值在label=1上的分布: 0.21
    postCode 异常值在label=1上的分布: 0.21
    regionCode 异常值在label=1上的分布: 0.17
    dti 异常值在label=1上的分布: 0.3
    delinquency_2years 异常值在label=1上的分布: 0.23
    ficoRangeLow 异常值在label=1上的分布: 0.07
    ficoRangeHigh 异常值在label=1上的分布: 0.07
    openAcc 异常值在label=1上的分布: 0.24
    pubRec 异常值在label=1上的分布: 0.23
    pubRecBankruptcies 异常值在label=1上的分布: 0.24
    revolBal 异常值在label=1上的分布: 0.14
    revolUtil 异常值在label=1上的分布: 0.44
    totalAcc 异常值在label=1上的分布: 0.2
    initialListStatus 异常值在label=1上的分布: inf
    applicationType 异常值在label=1上的分布: 0.25
    title 异常值在label=1上的分布: 0.16
    policyCode 异常值在label=1上的分布: inf
    n0 异常值在label=1上的分布: 0.2
    n1 异常值在label=1上的分布: 0.26
    n2 异常值在label=1上的分布: 0.29
    n2.1 异常值在label=1上的分布: 0.29
    n4 异常值在label=1上的分布: 0.22
    n5 异常值在label=1上的分布: 0.19
    n6 异常值在label=1上的分布: 0.23
    n7 异常值在label=1上的分布: 0.24
    n8 异常值在label=1上的分布: 0.21
    n9 异常值在label=1上的分布: 0.29
    n10 异常值在label=1上的分布: 0.24
    n11 异常值在label=1上的分布: 0.2
    n12 异常值在label=1上的分布: 0.23
    n13 异常值在label=1上的分布: 0.22
    n14 异常值在label=1上的分布: 0.3
    

    虽然删除异常值没标签整体分布没有影响,但还是先不删除

    # # 查看删除异常值后标签整体分布有无影响
    # 从结果看,删除异常值没有影响标签整体分布
    tmp = train_copy.copy()
    for fea in numerical_fea:
        tmp = tmp[tmp[fea + '_outliers']=='正常值']
    print(train_copy.shape)
    print(tmp.shape)
    
    
    print(train_copy.isDefault.sum()/train_copy.shape[0])
    print(tmp.isDefault.sum()/tmp.shape[0])
    
    (800000, 88)
    (612742, 88)
    0.1995125
    0.19509189838463822
    

    连续数值分箱(或者分桶)

    • 特征分箱的目的:

      • 从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度。从而使模型更加稳定。
    • 数据分桶的对象:

      • 将连续变量离散化
      • 将多状态的离散变量合并成少状态
    • 分箱的原因:

      • 数据的特征内的值跨度可能比较大,对有监督和无监督中如k-均值聚类它使用欧氏距离作为相似度函数来测量数据点之间的相似度。都会造成大吃小的影响,其中一种解决方法是对计数值进行区间量化即数据分桶也叫做数据分箱,然后使用量化后的结果。
    • 分箱的优点:

      • 处理缺失值:当数据源可能存在缺失值,此时可以把null单独作为一个分箱。
      • 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
      • 业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在着非线性关系,此时可经过WOE变换。
    • 特别要注意一下分箱的基本原则:

      • (1) 最小分箱占比不低于5%
      • (2) 箱内不能全部是好客户
      • (3) 连续箱单调
    # 
    
    # 10分位分箱
    pd.qcut(train_copy['loanAmnt'], 10, labels=False)
    # 卡方分箱
    
    0         9
    1         6
    2         4
    3         4
    4         0
             ..
    799995    8
    799996    6
    799997    1
    799998    7
    799999    3
    Name: loanAmnt, Length: 800000, dtype: int64
    
    from scipy.stats import chi2
    
    
    
    
    

    简单的特征构造(特征交互)

    • 代价是非常耗时,效果还未知;最好是基于业务和数据探索结论来做特征构造或交互,不要一上来就各种统计量堆叠。
    
    

    特征处理

    • (不同算法输入的数据要求也不同,此处特征的处理需要适应所选算法。过程会很繁琐,不要让变量混乱分不清,做好变量管理)
    
    

    特征选择

    • 特征选择方法很多。选择原则应耗时尽可能少,逻辑尽可能合理。 此处不存在哪种特征选择方法会让模型结果精度高的说法,精度取决于数据质量(就是之前关于数据各种操作)
    
    

    算法选择

    • 算法都有一个假设前提。 因此输入模型的数据需要尽可能处理成符合假设前提。
    
    
  • 相关阅读:
    Intel x86
    FPGA自计数六位共阳极数码管动态显示2(调用task的方法)
    FPGA六位共阳极数码管动态显示
    运算放大器是模拟电路的基石,模拟电路设计
    这样讲你就懂了!大牛给你介绍《信号与系统》
    电容计算公式
    fork...join的用法
    芯片电源管脚的去耦电容究竟要用多大的?
    Blog Contents
    linux grep 命令常见用法
  • 原文地址:https://www.cnblogs.com/Alexisbusyblog/p/13709487.html
Copyright © 2020-2023  润新知