kaggle比赛案例:Elo Merchant Category Recommendation(2)

三、数据清洗

其实数据解读、数据探索、数据清洗、特征工程都属于数据预处理流程,一般都是边探索边清洗了。上面我之所以只探索没清洗是因为,一是想建立一个探索数据的完整流程的一个模板,以后可以仿照参考,二是一个完整的数据探索是对项目的所有细节的挖掘,所以也是有必要单独仔细分析的,而且上面的分析代码你都搞得非常清楚了,后面再写更复杂的逻辑你就可以游刃有余了。所以我把数据探索、数据清洗、特征工程这些环节都分开了。

事实上,数据清洗和特征工程也是联系非常紧密的,所有的清洗工作都是为特征工程准备的。所以这两个流程其实是要前后综合考虑的。比如一个object类型的特征,你完全可以在清洗流程中,把它转化为有序特征或者one-hot特征,但是当你到特征工程阶段后,你又发现其实你转化的意义并不大,或者做的都是无用功,因为,很可能你在特征工程阶段,经过深思熟虑后,你只想给这个特征的取值计个数,那清洗阶段你的类型转化就是多余的。所以本步骤是综合考虑了后面特征工程的需要而进行的清洗工作。也所以在这个过程中,我反反复复重做了很多遍!真是很多遍!因为,你此时的考虑肯定不能特别好的兼顾后来特征工程。也所以得出一个经验:对于数据预处理反反复复是常态。一个项目中可能最大的工作量就是预处理了。

下面是处理各张表的重复值、缺失值、溢出值,以及删除未来要联表时的重复列等基础操作
1、测试集:
测试集有一条样本缺失first_active_month。我们查询这条样本的card_id,发现这张卡在交易表中有很多笔交易,我们取最早的一笔交易的时间作为其填充值。

2、交易表:
把historical_transactions和new_merchant_transactions两张表纵向合并起来,成为一个表transactions。
把特征category_3的缺失值填补成‘D’。把特征category_2的缺失值填补成-1。特征merchant_id的缺失值补充成'no_merchant_id'。这些填补都是标记性填补,便于后期分析。
把特征purchase_data这个日期特征转化为年、月、日三列特征,时分秒就丢弃了。

3、商户表:
需要把商户表中的和交易表的特征重复的特征先剔除。
商户表中还有63条样本的商户id是重复的,这里也暂时删除。
该表还有极少的缺失值用均值填充了。
该表的三个特征中还有几个溢出值,就是数值太大,已经无法显示了,溢出了,显示的是inf,把这几个inf值都用所在列的最大值替换。

4、至此,数据清洗工作完毕,把处理完毕的文件都保存一下:

具体代码如下:

#导入可能需要的库
import os
import numpy as np
import pandas as pd
import gc
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import datetime
import time

#一、导入比赛项目的原始数据
start = time.time()
PATH = r'D:\pytorch-data\elo\elo-merchant-category-recommendation'  #准备文件路径
filename = os.listdir(PATH) 

pd.set_option("display.max_colwidth",1000)  #让表格完全显示的作用
explain_doc = pd.read_excel(os.path.join(PATH, filename[0]), header=2, sheet_name='merchant').head(5)     #说明文档:Data Dictionary.xlsx
summit_doc = pd.read_csv(os.path.join(PATH, filename[5]),header=0).head(5)     #提交文档:sample_submission.csv

train = pd.read_csv(os.path.join(PATH, filename[7]),header=0)    #训练集:train.csv
test = pd.read_csv(os.path.join(PATH, filename[6]),header=0)     #测试集:test.csv

historical_transactions = pd.read_csv(os.path.join(PATH, filename[2]), header=0)  #历史交易表:'historical_transactions.csv'  #(2911,2361 × 14)
new_transactions = pd.read_csv(os.path.join(PATH,filename[4]), header=0)   #新交易表:'new_merchant_transactions.csv'   #(196,3031 × 14)
merchant = pd.read_csv(os.path.join(PATH, filename[3]), header=0)     #表:merchants.csv  (334696,22)

exe_time = time.time()-start
print(exe_time)
#二、数据预处理  
#1、处理测试集
#测试集有一个的缺失值
test = test.fillna('2017-03')   #充测试集的11578号样本的first_active_month这一个缺失值
#2、处理交易表
start = time.time()
#(1)纵向连接两张交易表,填充category_2、category_3、merchant_id缺失值    
transactions = pd.concat([historical_transactions, new_transactions], axis=0)   #合并信用卡历史交易表和新交易表
transactions = transactions.reset_index(drop=True)   #重置索引
transactions['category_2'] = transactions['category_2'].fillna(-1)   #给特征category_2的缺失值为-1
transactions['category_3'] = transactions['category_3'].fillna('D')   #给特征category_3的缺失值填补为D
transactions['merchant_id'] = transactions['merchant_id'].fillna('no_merchant_id')   #merchant_id的缺失值填充为'no_merchant_id'
transactions = transactions.reset_index(drop=True)
del historical_transactions
del new_transactions
gc.collect()

print(time.time()-start)
start = time.time()
#(2)处理交易表中的日期特征
def date_to_ymd(series):   #第一步:写个将日期列转化为年月日列表的函数
    year = []
    month = []
    day = []
    for index, val in enumerate(series):
        l = series[index].split('-')
        year.append(l[0])
        month.append(l[1])
        day.append(l[2].split(' ')[0])
    return year, month, day


year, month, day = date_to_ymd(series = transactions['purchase_date']) #第二步:给transaction表中添加年月日三个新列,并删掉原来的列
transactions['purchase_date_year'] = year        
transactions['purchase_date_month'] = month
transactions['purchase_date_day'] = day
transactions = transactions.drop('purchase_date', axis=1)    #31075392 rows × 16 columns

print(time.time()-start)
#3、处理商户表
start = time.time()
#删除商户表中和交易表重复的特征、删除商户表中的重复行(有63条id是重复的)、补充缺失值、处理溢出值
merchant = merchant.drop(['merchant_category_id', 'subsector_id', 'category_1', 'category_2', 'city_id', 'state_id'], axis=1)  #把merchant中和transactions中的相同列去除
merchant = merchant.iloc[merchant['merchant_id'].drop_duplicates().index, :].reset_index(drop=True)  #去掉merchant_id重复的63条样本
for col in ['avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']:   #缺失13个值,补全
    merchant[col] = merchant[col].fillna(merchant[col].mean())
for col in ['avg_purchases_lag3', 'avg_purchases_lag6', 'avg_purchases_lag12']:  #有几个inf溢出值,替换成当列的最大值
    max_value = merchant[merchant[col] != np.inf][col].max()
    merchant[col] = merchant[col].replace(np.inf, max_value)
    
print(time.time()-start)
#4、将上述处理结果进行保存
start = time.time()

train.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\train_pre.csv', index=False)
test.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\test_pre.csv', index=False)
transactions.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transactions_pre.csv', index=False)   #31075392 rows × 16 columns
merchant.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\merchant_pre.csv', index=False)   ##31075392 rows × 14 columns

print(time.time()-start)

四、特征工程

数据清洗完毕后,我们就进入特征工程环节。特征工程就是特征创建和特征筛选。
(一)特征创建

本案例中,训练集是一张信用卡的id是一条样本数据, card_id不重复。但此时的交易数据是:一张信用卡card_id对应多条交易数据,有的card_id有几条交易数据,有的card_id有成百上千条交易数据。所以此时的问题就是如何把交易数据整理成:一行数据表示一张card_id的交易数据。而且也得把merchant表中得商户数据也合并过去。
所以本案例的特征创建就是,将transactions_pre中的交易数据和merchant中的商户数据合并,并按照card_id整理为一行。这个过程也就会产生很多新特征,就是特征创建。

其实特征创建的方法有很多很多,但不管你用什么方法,都得适合你的数据,都得符合你的业务背景。这里我的特征创建如下思路:
1、对交易表中的特征逐个分析:

交易表中除了card_id外,共有15个特征,其中11个取有限值的分类特征,3个特征是取值非常多的分类特征,1个特征是连续性特征。这里我们先分成两类columns1和columns2,分别破之。

2、处理columns1里面的特征:

这里我电脑算力有限,只能勉强处理1000个card_id,所以本案例我先演示1000条样本量,回头有算力资源了,再跑跑吧。

3、处理columns2里面的特征  

由于此前我是一上来就合并交易表和商户表的处理方式,结果生成了一个5G多的文件,根本读出来,无法进行下一步工作,所以当我们已经去掉columns1的特征后,数据量就非常小了,此时正是合并商户表的最佳时机,所以这里我先把商户表和columns2合并了并进行缺失值填充,然后再分析合并后的特征:  



这次的分类思路是,由于city_id和merchant_category_id的取值都是三百多个,太多。但是本案例是预测信用卡的商品匹配值,所以我认为与商品有关的特征最好不要简单舍弃,所以把这两个特征和其余的6个连续特征做特征创建,就是按照离散特征的各个取值,分别计算这6个连续特征的和,结果作为这个card_id的新特征。
category_4就单独处理,由于其取值是3个无序的分类值,所以就仿照columns1里面的离散变量的处理方式,按照card_id进行计数吧。
merchant_group_id也单独处理。因为这个特征的值也是无序的分类值,但是有十多万个取值,所以没法像category_4一样进行计数。思来想去,根据这个指标的业务背景,就是商户的商户的细分品类,其实这个特征是非常非常重要的,可以代表信用卡主对哪种商品的喜好,所以这里我就把这个特征处理成信用卡用户的“刷卡集中度”这个指标,就是用商户的种类/刷卡次数,反应客户是经常在集中的几种商品上刷卡还是分散很多很多商品上刷卡,如果集中度很高,就说明这个客户最喜欢某种商品,如果集中度很低,就说明这个客户的爱好广泛。

由于后面代码非常多,没法截图了,所以这里我就把所有的特征工程的代码一起贴下面:

#三、特征工程:创建特征和筛选特征
#特征工程的第一阶段目标就是,将transactions_pre中的交易数据和merchant中的商户数据合并,并按照card_id整理为一行

#1、将特征进行分类,分门别类的转化
columns1 = ['card_id', 'authorized_flag', 'category_1', 'category_2', 'category_3', 'state_id', 'subsector_id',
           'installments','month_lag', 'purchase_date_year', 'purchase_date_month', 'purchase_date_day']   #这些是取值种类少的离散特征  12---157

columns2 = ['card_id', 'merchant_id', 'city_id', 'merchant_category_id', 'purchase_amount']   #前面三个特征是取值很多的分类特征,最后一个特征是连续性特征

#读取预处理后的交易表\商户表
start = time.time()
transaction = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transactions_pre.csv',header=0)   #31075392 rows × 16 columns
print(time.time()-start)
#2、处理colunmns1里面的特征   
#将每个特征的所有取值进行计数
card_id = transaction['card_id'].unique().tolist()    #325540

def null_dict(columns=columns1):
    fv = []
    for i in columns[1:]:
        fv.append(transaction[i].unique().tolist())
    return fv

all_f = null_dict()
flag = 0
re = {'card_id':[]}
for p in card_id[:1000]:      #####################################先生成1000张卡的数据
    p_data = transaction[transaction['card_id']==p]
    re['card_id'].append(p)
    for f in columns1[1:]:
        p_data_f = p_data[f].value_counts().to_dict()
        all_f_v = all_f[columns1.index(f)-1]
        for j in all_f_v:
            if j in p_data_f:
                if flag==0:
                    re[f+'&'+str(j)] = [p_data_f[j]]
                else:
                    re[f+'&'+str(j)].append(p_data_f[j])
            else:
                if flag == 0:
                    re[f+'&'+str(j)] = [0]
                else:
                    re[f+'&'+str(j)].append(0)
    flag+=1           
transaction_to_oneline = pd.DataFrame(re)    #1000 rows × 157 columns

col = transaction_to_oneline.columns   #删除全是0的列  就这一列'installments&999'
for i in col:    
    if transaction_to_oneline[i].sum()==0:
        transaction_to_oneline.drop([i], axis=1, inplace=True)     # 1000 rows × 156 columns
#columns1的所有特征处理完毕后保存        
transaction_to_oneline.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_columns1.csv', index=False)   #325540 X 156
#3、处理columns2里面的特征  

#此时将columns2里面的特征和merchant表里面的特征合并。因为此时数据较少,合并不需要太多算力。  
columns2 = ['card_id', 'merchant_id', 'city_id', 'merchant_category_id', 'purchase_amount']   
transaction_columns2 = transaction[columns2]   #交易表里面的数据

#从商户表中筛选几个特征合并
merchant = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\merchant_pre.csv',header=0) 
merchant_cf = merchant[['merchant_id', 'merchant_group_id', 'numerical_1', 'numerical_2','avg_sales_lag3', 'avg_sales_lag6','avg_sales_lag12','category_4']]  #8个特征

#横向合并,并删除merchant_id列
transaction_columns2_merge_merchant = transaction_columns2.merge(merchant_cf, on='merchant_id', how='left') 
transaction_columns2_merge_merchant = transaction_columns2_merge.drop(['merchant_id'], axis=1)   #   31075392 rows × 11 columns

#此时,transaction_columns2_merge_merchant里面的7个特征有164697个缺失值,因为之前的交易表中有164697个缺失值,当时我们填充的是'no_merchant',所以merge时没有匹配的,出现缺失:
transaction_columns2_merge_merchant['merchant_group_id'] = transaction_columns2_merge_merchant['merchant_group_id'].fillna(0)  
transaction_columns2_merge_merchant['category_4'] = transaction_columns2_merge_merchant['category_4'].fillna('A')
for f in ['numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6','avg_sales_lag12']:
    transaction_columns2_merge_merchant[f] = transaction_columns2_merge_merchant[f].fillna(transaction_columns2_merge_merchant[f].mean()) 

#保存并表后的交易数据:
transaction_columns2_merge_merchant.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_columns2_merger_pre.csv', index=False)  #31075392× 11 
#(1)将特征分门别类
col2_1 = ['card_id', 'city_id', 
            'purchase_amount', 'numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']

col2_2 = ['card_id', 'merchant_category_id',
            'purchase_amount', 'numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']

col2_3 = ['card_id', 'category_4']
col2_4 = ['card_id', 'merchant_group_id']
#读取transaction_columns2_merger_pre.csv
start = time.time()
transaction_part2 = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_columns2_merger_pre.csv', header=0)   #31075392 rows × 11 columns
print(time.time()-start)
#(2)处理col2_1
col2_1 = ['card_id', 'city_id', 
            'purchase_amount', 'numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']
#把要分类的离散变量都变成列表
card_id = transaction_part2['card_id'].unique().tolist() #所有的card_id都去重变list,写入空表的第一列
city_id = transaction_part2['city_id'].unique().tolist() #
flag = 0
data = transaction_part2[col2_1]
re = {}

for cardid in card_id[:1000]:    #######################
    data_person = data[data['card_id']==cardid].iloc[:, 1:]
    data_person_sum = data_person.groupby('city_id').sum()
    
    if flag ==0:
        re['card_id']=[cardid]
    else:
        re['card_id'].append(cardid)

    for f in ['purchase_amount', 'numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']:
        city_all_dic = {}
        for i in city_id:
            city_all_dic[i]=[]
        
        person_f = data_person_sum[f].to_dict()
        for j in city_all_dic:
            if j in person_f:
                city_all_dic[j].append(person_f[j])
            else:
                city_all_dic[j].append(0)
                
            if flag ==0:
                temp = city_all_dic[j]
                re['city_id'+'_'+str(j)+'&'+f] = temp
            else:
                temp = city_all_dic[j][0]
                re['city_id'+'_'+str(j)+'&'+f].append(temp)
    flag+=1

transaction_oneline_col2_1 = pd.DataFrame(re)    # 1000 rows × 1849 columns

col = transaction_oneline_col2_1.columns   ##删除全是0的列,有36列
for i in col:    
    if transaction_oneline_col2_1[i].sum()==0:
        transaction_oneline_col2_1.drop([i], axis=1, inplace=True)     # 1000 rows × 1813 columns
#保存col2_1的to_oneline结果
transaction_oneline_col2_1.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_1.csv', index=False)   # 325540 X 1813 columns
#(3)处理col2_2
col2_2 = ['card_id', 'merchant_category_id',
            'purchase_amount', 'numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']

card_id = transaction_part2['card_id'].unique().tolist() #
merchant_category_id = transaction_part2['merchant_category_id'].unique().tolist() #

#开始循环
flag = 0
data = transaction_part2[col2_2]
re = {}
for cardid in card_id[:1000]:    ################################
    data_person = data[data['card_id']==cardid].iloc[:, 1:]
    data_person_sum = data_person.groupby('merchant_category_id').sum()
    if flag ==0:
        re['card_id']=[cardid]
    else:
        re['card_id'].append(cardid)

    for f in ['purchase_amount', 'numerical_1', 'numerical_2', 'avg_sales_lag3', 'avg_sales_lag6', 'avg_sales_lag12']:
        
        merchant_category_id_all = {}
        for i in merchant_category_id:
            merchant_category_id_all[i]=[]
        
        person_f = data_person_sum[f].to_dict()
        for j in merchant_category_id_all:
            if j in person_f:
                merchant_category_id_all[j].append(person_f[j])
            else:
                merchant_category_id_all[j].append(0)
                
            if flag ==0:
                temp = merchant_category_id_all[j]
                re['merchant_category_id'+'_'+str(j)+'&'+f] = temp
            else:
                temp = merchant_category_id_all[j][0]
                re['merchant_category_id'+'_'+str(j)+'&'+f].append(temp)
    flag+=1
transaction_oneline_col2_2 = pd.DataFrame(re)   ## 1000 X 1987 columns

#保存col2_2的to_oneline结果
transaction_oneline_col2_2.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_2.csv', index=False)   ### 325540 X 1987 columns

col = transaction_oneline_col2_2.columns   #删除全是0的列  有378列
for i in col:    
    if transaction_oneline_col2_2[i].sum()==0:
        transaction_oneline_col2_2.drop([i], axis=1, inplace=True)     # 1000 rows × 1609 columns
#保存col2_2的to_oneline结果
transaction_oneline_col2_2.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_2.csv', index=False)   ### 325540 X 1609 columns
#(4)处理col2_3
col2_3 = ['card_id', 'category_4']    # ['Y', 'N', 'A']
data = transaction_part2[col2_3]

transaction_oneline_col2_3 = {'card_id':[], 'category_4&Y':[], 'category_4&N':[], 'category_4&A':[]}
card_list = data['card_id'].unique().tolist()

for i in card_list[:1000]:   ######################################
    person = data[data['card_id']==i]
    person_value = person['category_4'].value_counts().to_dict()
    transaction_oneline_col2_3['card_id'].append(i)
    
    for f in ['Y', 'N', 'A']:
        if f in person_value:
            transaction_oneline_col2_3['category_4'+'&'+f].append(person_value[f])
        else:
            transaction_oneline_col2_3['category_4'+'&'+f].append(0)
            
transaction_oneline_col2_3 = pd.DataFrame(transaction_oneline_col2_3)      #生成3列新特征  
#(5)处理col2_4:这个特征的取值太多,我们就算每张信用卡的"刷卡集中度",就是这张卡是在很多上刷卡还是几种某几个商家
col2_4 = ['card_id', 'merchant_group_id']  #十多万个id  (0, 1.0, 112586.0)  
data = transaction_part2[col2_4]
card_list = data['card_id'].unique().tolist()
transaction_oneline_col2_4 = {'card_id':[], 'merchant_group_id&percent':[]}

for i in card_list[:1000]:
    transaction_oneline_col2_4['card_id'].append(i)
    person = data[data['card_id']==i]
    percentage = (person['merchant_group_id'].nunique())/person.shape[0]
    transaction_oneline_col2_4['merchant_group_id&percent'].append(percentage)
transaction_oneline_col2_4 = pd.DataFrame(transaction_oneline_col2_4)

transaction_oneline_col2_3_4 = transaction_oneline_col2_3.merge(transaction_oneline_col2_4, on='card_id', how='left')    #将col2_3和col2_4合并

#保存col2_3、col2_4的to_oneline结果
transaction_oneline_col2_3_4.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_3_4.csv', index=False)   ### 325540 X 5 columns

(二)特征筛选

截至目前我们生成了3579个新特征,如果把这些特征都合并到训练集和测试集,一方面是电脑肯定带不动,另一方面有些特征是无效的,除了增加计算量对模型效果贡献甚微,所以我们要来一波特征筛选。
特征筛选也有很多相关的技术和方法,但是最简单、最高级的方法是根据业务意义来筛选,就是通过你对业务的理解和判断来筛选特征。但是这里我们的新特征本身就是根据业务意义组合而来的,所以这个方法目前不适合我们,此时就只能通过技术手段来筛选特征。

所有的技术手段一般都是基于这两个目标而发明的,一是特征是否发散,如果一个特征不发散,比如都是0或者都是2,那这个特征就是不发散的,就是方差等于0的,那这个特征对样本的区分就没有任何效果,那这个特征自然是要删除的特征。二是特征是否与标签相关。因为我们是用特征预测标签的,如果特征和标签完全不相关,那用这些不相关的标签肯定是不能很好预测的。所以基于上面两个目标,特征筛选的主要手段有过滤法、嵌入法、包装法和降维这几种形式。下面我简单归纳总结一下:

下面的总结主要参考 【机器学习】特征选择方法总结_embedded特征选择-CSDN博客 这篇博文,因为它写得太好,怕以后找不到了,就部分拷贝了下来,在此先感谢那位博主!

过滤法Filter: 方差过滤、卡方检验、F检验、皮尔逊相关系数、斯皮尔曼相关系数、肯德尔等级相关系数、互信息法等
嵌入法Embedded: 后面详细介绍
包装法Wrapper:RFE、RFECV、Null Importance等
降维算法:PCA、LDA等

1、过滤法Filter
(1)方差过滤variance threshold,就是通过特征本身的方差来筛选特征。如果一个特征它本身的方差很小,就说明样本在这个特征上没有分类效果,这个特征对样本没有分类能力,所以我们首先要删除这类特征。方差过滤法对有距离计算的模型非常有效,比如KNN、线性回归、SVM、神经网络等算法非常有效,但是对像随机森林、决策树等树模型来说就效果甚微。
方差过滤是针对特征自己的。针对自己的特征选择除了方差过滤还有通过频数过滤,不过和方差过滤是异曲同工。

(2)卡方检验是专门针对离散型标签的相关性检验。如果你的任务是分类任务,也就是你的数据集的标签是分类标签,那此时你就可以用卡方检验来筛选特征和标签之间的相关性,取相关性最大的特征,删除相关性小的特征。注意如下图所示的卡方检验中,特征和标签都可以是浮点数,但是特征必须是正数,如果特征里面有负数就会报错,所以使用这个方法时,一定要先检查你的特征有没有负数。
卡方检验的数学逻辑下图的截图描述得非常清晰。卡方检验的原假设是“两组数据是相互独立的”,p值就是显著性水平,所以我们都选择p值越小的特征,就说明这个特征和标签越相关。由于CHI值不好设置,所以我们一般都设置p值=0.05或者0.01作为阈值来筛选特征。

(3)F检验又称ANOVA,又叫方差齐性检验。是用来捕捉特征与标签之间线性关系的过滤方法。F检验可以检验两列离散数据也可以检验两列连续数据。F检验要求数据服从正态分布,因此如果使用F检验过滤,要先将数据转换成服从正态分布,此时F检验效果才会比较稳定。F检验的原假设是“数据不存在显著的线性关系”,F检验的数学逻辑是:看组间方差(Mean Squared Between, MSB)是否大于组内方差(Mean Squared Error, MSE),如果组间方差>组内方差,说明存在至少一个分布相对于其他分布较远,则可以考虑拒绝零假设。p值也是显著性水平,p值越小越线性相关。

(4)皮尔逊相关系数(Pearson Correlation Coefficient),是度量两列连续数据之间的线性关系的,其假设是数据都符合正态分布,构建的统计量是:r = Cov(X, Y) / (σX * σY),所以这个统计指标主要是应用于两列连续变量,取值范围在-1到1之间。根据皮尔逊相关系数筛选特征非常简单,我们本次特征筛选就使用这个方法,具体代码实现,看后面的代码。

(5)斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)也是度量两列连续数据之间的线性关系的,只不过它没有假设特征符合任何分布,因为它是基于等级(rank)的概念去计算变量间的相关性。相关系数趋于1或-1,正负号指向正负相关关系。如果变量是顺序变量(Ordinal Feature),推荐使用Spearman相关系数。

(6)肯德尔等级相关系数(Kendall tau rank correlation coefficient):是度量分类特征和连续性标签之间的相关关系,数据逻辑如下图所示:

(7)互信息法,是捕捉特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法,因为它计算的是条件熵、联合熵这类指标。但是互信息法不能用于稀疏矩阵。和F检验相似,它既可以做回归也可以做分类,并且包含两个类feature_selection.mutual_info_classif(互信息分类)和feature_selection.mutual_info_regression(互信息回归)。互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。互信息法返回的是“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。



过滤法小结:


除了数学逻辑外,上面的方法都可以使用scikit-learn里的特征选择库sklearn.feature_selection的SelectKBest函数,选择Top K个最高得分的特征来实现:

2、嵌入法Embedded
嵌入法是将特征选择嵌入学习器的训练过程中,就是把特征选择交给模型去学习并选择。sklearn提供SelectFromModel()类可以和任何拟合后有coef_, feature_importance_属性或参数中可选L1、L2惩罚项的评估器一起使用。比如,随机森林和树模型就有feature_importance_属性,逻辑回归就有L1,L2惩罚项,线性支持向量机也支持L2惩罚项,LASSO也有L1正则惩罚项。

对于有feature_importances_的模型来说,若重要性低于提供的阈值参数,则认为这些特征不重要并被移除。而feature_importances_的取值范围是[0,1],如果设置阈值很小,比如0.001,就可以删除那些对标签预测完全没贡献的特征。如果设置得很接近1,可能只有一两个特征能够被留下。

对于使用惩罚项的模型来说,正则化惩罚项越大,特征在模型中对应的系数就会越小。当正则化惩罚项大到一定的程度的时候,部分特征系数会变成0,当正则化惩罚项继续增大到一定程度时,所有的特征系数都会趋于0。 但是我们会发现一部分特征系数会更容易先变成0,这部分系数就是可以筛掉的。也就是说,我们选择特征系数较大的特征。

支持向量机和逻辑回归使用参数C来控制返回的特征矩阵的稀疏性,参数C越小,返回的特征越少。
Lasso回归,用alpha参数来控制返回的特征矩阵,alpha的值越大,返回的特征越少。

3、包装法Wrapper
包装法是将模型的性能作为特征选择的标准,最简单最暴力的方法就是穷举所有的特征组合,然后看哪个特征组合跑出来的分值高,那就选哪组特征。所以包装法的特征选择和模型训练是同时进行的。
包装法的效果是所有特征选择方法中最利于提升模型表现的特征筛选方法。但它计算开销太大,不适合大型数据。

(1)RFE(recursive feature elimination)

(2)RFECV 

(3)Null Importance特征挑选法
我们经常会遇到这种情况,比如某个特征在现在的真实标签下,它的importance分值很高,但是当我们把标签shuffle后,它的importance还是很高,其实这种特征是应该剔除的,最典型的就是比如像userid这类特征。真正强健、稳定且重要的特征一定是在真标签下特征很重要,但一旦标签打乱,这些优质特征的重要性就会变差。相反地,如果某特征在原始标签下表现一般,但打乱标签后,居然重要性上升,明显就不靠谱,这类特征就得剔除掉。 Null Importance的计算过程大致是:先在原始数据集运行模型获取特征重要性;再shuffle多次标签,每次shuffle后获取假标签下的特征重要性(你可以选择importance_gain或者importance_split);最后计算真假标签下的特征重要性差异,并基于差异,筛选特征。

从上面的分析看我们的数据量太大,不适合嵌入法和包装法,只能考虑过滤法。这里我用皮尔森相关系数指标来筛选,就是用我们创建的新特征和train表里面的target列之间计算皮尔逊相关性。所以我们要用一个个新特征分别和target计算相关性,然后取值绝对值,然后取前50个最相关的特征。

下面的代码就做参考了,因为只有1000个样本。  

#导入可能需要的库
import os
import numpy as np
import pandas as pd
import gc
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import datetime
import time

#读数据
train = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\train_pre.csv', header=0)
test = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\test_pre.csv', header=0)
transaction_oneline_columns1 = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_columns1.csv', header=0)
transaction_oneline_col2_1 = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_1.csv', header=0)
transaction_oneline_col2_2 = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_2.csv', header=0)
transaction_oneline_col2_34 = pd.read_csv(r'D:\pytorch-data\elo\a_result_preprocessing\transaction_oneline_col2_3_4.csv', header=0)

#由于我们的交易数据只有1000个card_id,得先把这1000个样本从训练集和测试集中取出来
train_id = train['card_id'].tolist()
test_id  = test['card_id'].tolist()
new0_id = transaction_oneline_columns1['card_id'].tolist()
new1_id = transaction_oneline_col2_1['card_id'].tolist()
new2_id = transaction_oneline_col2_2['card_id'].tolist()
new3_id = transaction_oneline_col2_34['card_id'].tolist()

#看这1000个id是在训练集还是测试集,同时看看有没有异常
train_id_part = []   #640
test_id_part = []    #360
yichang = []    #0
for i in new0_id:
    if i in train_id:
        train_id_part.append(i)
    elif i in test_id:
        test_id_part.append(i)
    else:
        yichang.append(i)
#没有发现异常

#从训练集和测试集中把这1000个样本索引出来
train_part, test_part = [], []

for i in train_id_part:
    s = train[train['card_id']==i]
    train_part.append(s)
trainp = pd.concat(train_part, axis=0, ignore_index=True)

for j in test_id_part:
    s = test[test['card_id']==j]
    test_part.append(s)
testp = pd.concat(test_part, axis=0, ignore_index=True)    

 #从transaction_oneline_columns1中把和训练集一样id的样本、和测试集一样id的样本分别索引出来
new0train_part, new0test_part = [], []

for i in train_id_part:
    s = transaction_oneline_columns1[transaction_oneline_columns1['card_id']==i]
    new0train_part.append(s)
new0train = pd.concat(new0train_part, axis=0, ignore_index=True)

for j in test_id_part:
    s = transaction_oneline_columns1[transaction_oneline_columns1['card_id']==j]
    new0test_part.append(s)
new0test = pd.concat(new0test_part, axis=0, ignore_index=True)    

#从transaction_oneline_col2_1中把和训练集一样id的样本、和测试集一样id的样本分别索引出来
new1_train, new1_test = [], []
for i in train_id_part:
    s = transaction_oneline_col2_1[transaction_oneline_col2_1['card_id']==i]
    new1_train.append(s)
new1train = pd.concat(new1_train, axis=0, ignore_index=True)

for j in test_id_part:
    s = transaction_oneline_col2_1[transaction_oneline_col2_1['card_id']==j]
    new1_test.append(s)
new1test = pd.concat(new1_test, axis=0, ignore_index=True)

#从transaction_oneline_col2_2中把和训练集一样id的样本、和测试集一样id的样本分别索引出来
new2_train, new2_test = [], []
for i in train_id_part:
    s = transaction_oneline_col2_2[transaction_oneline_col2_2['card_id']==i]
    new2_train.append(s)
new2train = pd.concat(new2_train, axis=0, ignore_index=True)

for j in test_id_part:
    s = transaction_oneline_col2_2[transaction_oneline_col2_2['card_id']==j]
    new2_test.append(s)
new2test = pd.concat(new2_test, axis=0, ignore_index=True)

#提取transaction_oneline_col2_1中和target相关性排名前50个特征的数据
new1_target = trainp[['card_id', 'target']].merge(new1train, on='card_id', how='left')

corr = []
for f in new1_target.columns[2:]:
    corr.append(abs(new1_target[['target', f]].corr().values[0][1]))
corr_re = pd.Series(corr, index = new1_target.columns[2:]).sort_values(ascending=False)

col2_1_fs = ['card_id']+corr_re[:50].index.tolist()

new1train_fs = new1train[col2_1_fs]
new1test_fs = new1test[col2_1_fs]

#提取transaction_oneline_col2_2中和target相关性排名前50个特征的数据
new2_target = trainp[['card_id', 'target']].merge(new2train, on='card_id', how='left')

corr = []
for f in new2_target.columns[2:]:
    corr.append(abs(new2_target[['target', f]].corr().values[0][1]))
corr_re = pd.Series(corr, index = new2_target.columns[2:]).sort_values(ascending=False)

col2_2_fs = ['card_id']+corr_re[:50].index.tolist()

new2train_fs = new2train[col2_2_fs]
new2test_fs = new2test[col2_2_fs]

#从transaction_oneline_col2_3_4中把和训练集一样id的样本、和测试集一样id的样本分别索引出来
new3train_part, new3test_part = [], []

for i in train_id_part:
    s = transaction_oneline_col2_34[transaction_oneline_col2_34['card_id']==i]
    new3train_part.append(s)
new3train = pd.concat(new3train_part, axis=0, ignore_index=True)

for j in test_id_part:
    s = transaction_oneline_col2_34[transaction_oneline_col2_34['card_id']==j]
    new3test_part.append(s)
new3test = pd.concat(new3test_part, axis=0, ignore_index=True)    

#生成合并全部新特征的训练集和测试集
trainM = trainp.merge(new0train, on='card_id', how='left')
trainM = trainM.merge(new1train_fs, on='card_id', how='left')
trainM = trainM.merge(new2train_fs, on='card_id', how='left')
trainM = trainM.merge(new3train, on='card_id', how='left')

testM = testp.merge(new0test, on='card_id', how='left')
testM = testM.merge(new1test_fs, on='card_id', how='left')
testM = testM.merge(new2test_fs, on='card_id', how='left')
testM = testM.merge(new3test, on='card_id', how='left')

def data_change(df, col):    #转化日期特征的函数
    year = range(2011, 2019)
    year_map = pd.Series(range(len(year)), index = year)
    new_col_year = df[[col]].columns.values[0]+'_year'
    new_col_month = df[[col]].columns.values[0]+'_month'
    new_col_day = df[[col]].columns.values[0]+'_day'
    
    if len(df[[col]].iloc[0,0][:4])==4:
        df[new_col_year]= pd.Series([int(i[:4]) for i in df[col]])
        df[new_col_year] = df[new_col_year].map(year_map)
    if len(df[[col]].iloc[0,0][5:7]) == 2:
        df[new_col_month]= pd.Series([int(i[5:7]) for i in df[col]])
    if len(df[[col]].iloc[0,0][8:10]) == 2:
        df[new_col_day]= pd.Series([int(i[8:10]) for i in df[col]])
    df.drop([col], axis=1, inplace=True)
    return df

train_fin = data_change(trainM, 'first_active_month')
test_fin = data_change(testM, 'first_active_month')

 #去掉card_id列,生成可建模的训练集和测试集
train = train_fin.drop(['card_id'], axis=1, inplace=False)
test = test_fin.drop(['card_id'], axis=1, inplace=False)
train.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\train.csv', index=False)    #(640, 266)
test.to_csv(r'D:\pytorch-data\elo\a_result_preprocessing\test.csv', index=False)     #(360, 265)

猜你喜欢

转载自blog.csdn.net/friday1203/article/details/134999114