下面分享一下自己通常会用到的一些数据预处理的方法和步骤,用Kaggle平台上的elo用户忠诚度预测的数据集作为应用案例。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
merchant = pd.read_csv(r'\原始数据\elo-merchant-category-recommendation\merchants.csv', header=0)
拿到一份数据,首先要确定样本大小、特征个数、字段类型这些基本信息。用.info()可以初步得到数据集的这些信息
merchant.info()
RangeIndex: 334696 entries, 0 to 334695
Data columns (total 22 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 merchant_id 334696 non-null object
1 merchant_group_id 334696 non-null int64
2 merchant_category_id 334696 non-null int64
3 subsector_id 334696 non-null int64
4 numerical_1 334696 non-null float64
5 numerical_2 334696 non-null float64
6 category_1 334696 non-null object
7 most_recent_sales_range 334696 non-null object
8 most_recent_purchases_range 334696 non-null object
9 avg_sales_lag3 334683 non-null float64
10 avg_purchases_lag3 334696 non-null float64
11 active_months_lag3 334696 non-null int64
12 avg_sales_lag6 334683 non-null float64
13 avg_purchases_lag6 334696 non-null float64
14 active_months_lag6 334696 non-null int64
15 avg_sales_lag12 334683 non-null float64
16 avg_purchases_lag12 334696 non-null float64
17 active_months_lag12 334696 non-null int64
18 category_4 334696 non-null object
19 city_id 334696 non-null int64
20 state_id 334696 non-null int64
21 category_2 322809 non-null float64
dtypes: float64(9), int64(8), object(5)
memory usage: 56.2+ MB
用.info()搞清楚哪些是分类变量,哪些是数值型变量,分类进行处理。可以先把变量名分类放入category_cols、numeric_cols两个列表中,等待后续操作。
由于object类型变量中有一些离散型变量,之后要把它们的取值映射到数值上(比如category_1有3个取值,分别是A 、B、C,无法直接对它们进行建模计算,要把它们分别转化成0、1、2),也可以把这样的object型变量名存放起来。
category_cols = ['merchant_id', 'merchant_group_id', 'merchant_category_id',
'subsector_id', 'category_1',
'most_recent_sales_range', 'most_recent_purchases_range',
'category_4', 'city_id', 'state_id', 'category_2']
numeric_cols = ['numerical_1', 'numerical_2',
'avg_sales_lag3', 'avg_purchases_lag3', 'active_months_lag3',
'avg_sales_lag6', 'avg_purchases_lag6', 'active_months_lag6',
'avg_sales_lag12', 'avg_purchases_lag12', 'active_months_lag12']
object_cols = ['category_1','most_recent_sales_range','most_recent_purchases_range','category_4' ]
一般多用于数据中的样本ID重复性检验,有些数据里面的样本id是不允许重复的,比如订单ID在电商系统中是不会重复使用的,这样的数据集中就不该有重复的样本id,没有重复的订单ID,才算是一份比较漂亮的数据。电商数据中,重复的订单ID可能说明以下两个事实:(1)样本中有大量重复数据(2)数据中的每条样本并不是订单,而是订单中的一个商品,因此标签中的异常实际上可能是“这笔交易的异常”,而不是整个订单的异常(如果是这种情况,数据预处理就会比较困难了)
而有些情况下,样本id重复是被允许的,比如在本文使用的这个例子当中,商户信息数据集中,商户id有重复,因为一些商户进行了业务更新;在一些信用卡交易记录数据中,每张信用卡可以交易多次,所以数据集中出现多个样本具有同样的id,也是一件正常的事情,还是要结合具体业务背景来看样本重复性是否被允许。但是样本重复性检验的确是不可缺失的一个步骤。
这一个步骤常用到.unique()和.nunique()
.unique()返回某一列中所有不同的值。
.nunique()返回某一列中不同取值的个数。
merchant.nunique()
merchant_id 334633
merchant_group_id 109391
merchant_category_id 324
subsector_id 41
numerical_1 954
numerical_2 947
category_1 2
most_recent_sales_range 5
most_recent_purchases_range 5
avg_sales_lag3 3372
avg_purchases_lag3 100003
active_months_lag3 3
avg_sales_lag6 4507
avg_purchases_lag6 135202
active_months_lag6 6
avg_sales_lag12 5009
avg_purchases_lag12 172917
active_months_lag12 12
category_4 2
city_id 271
state_id 25
category_2 5
dtype: int64
merchant.nunique()==merchant.shape[0]
merchant_id False
merchant_group_id False
merchant_category_id False
subsector_id False
numerical_1 False
numerical_2 False
category_1 False
most_recent_sales_range False
most_recent_purchases_range False
avg_sales_lag3 False
avg_purchases_lag3 False
active_months_lag3 False
avg_sales_lag6 False
avg_purchases_lag6 False
active_months_lag6 False
avg_sales_lag12 False
avg_purchases_lag12 False
active_months_lag12 False
category_4 False
city_id False
state_id False
category_2 False
dtype: bool
merchant.shape[0]是样本量,上面这一个命令是检查每个变量中的取值个数是否等于样本数量,若不等于(返回False),那就是有重复值出现.
若等于(返回True),就说明该变量里面没有重复值。
merchant数据集中所有的变量都是含有重复值的,并没有哪个变量里面的取值完全不一样
查看每一个变量中所含有缺失值的样本个数:data.isnull().sum()
merchant.isnull().sum()
merchant_id 0
merchant_group_id 0
merchant_category_id 0
subsector_id 0
numerical_1 0
numerical_2 0
category_1 0
most_recent_sales_range 0
most_recent_purchases_range 0
avg_sales_lag3 13
avg_purchases_lag3 0
active_months_lag3 0
avg_sales_lag6 13
avg_purchases_lag6 0
active_months_lag6 0
avg_sales_lag12 13
avg_purchases_lag12 0
active_months_lag12 0
category_4 0
city_id 0
state_id 0
category_2 11887
dtype: int64
处理缺失值几个可以思考的方向:
(1) 缺失是否与异常相关?(在样本不平衡场景下可以使用,比如:异常订单识别、患者筛查等等)
看一下有缺失值的样本和异常样本之间的交集,交集越大说明样本缺失与异常越紧密,这时就一定不能删除缺失样本。 还要看两个比例:交集部分/缺失值样本、交集部分/缺失值样本。
交集部分/缺失值样本:若该比例较大,说明缺失样本大多数是异常样本。
交集部分/异常值样本:异常值样本是珍稀物种,如果该比例较大,直接删除缺失值样本,就加重了样本不平衡问题让异常值更加难以捕捉。如果该比例小,直接删去缺失值样本,影响也不会太大。
(2)众数、均值填补
(3)算法填补
(4)直接删掉:.dropna(),删除之后一定要恢复索引:data.index=range(data.shape[0])
当然也可以把四种方法都尝试一下,看看在四份数据集上面建起来的模型谁的表现更好一些。
填补之前可以大致看一下数据分布,缺失值较少的情况下,就直接用均值、众数、中位数来填补;如果连续型变量的缺失值较多,可以考虑用算法填补(比如随机森林);如果是离散型变量缺失值很多的情况,可以先用一个数字来标记这些缺失值。
avg_sales_lag3=merchant['avg_sales_lag3'].value_counts().sort_index()
avg_sales_lag3
-82.13 1
-0.72 1
-0.40 1
0.33 42
0.34 99
..
360107.00 1
385833.00 1
608433.00 1
624741.00 1
851844.64 1
Name: avg_sales_lag3, Length: 3372, dtype: int64
统计不同取值的样本个数,并从大到小排序发现取值范围非常大,但是极度偏态,较大的取值之间非常离散。再来用可视化的方法看一下数据分布状况。用到的画图函数是自定义函数,代码在这篇博客里面,可以直接调用数据分布探索函数。
for i in ['avg_sales_lag3','avg_sales_lag6','avg_sales_lag12']:
data_distribution_explore(merchant,i,va_type='numeric')
data_distribution_explore(merchant,'category_2',va_type='category')
一共四个变量含有缺失值,离散变量’category_2’有许多缺失值(11887个),连续型数值变量‘avg_sales_lag3’、‘avg_sales_lag6’、‘avg_sales_lag12’都只有13个缺失值,数量较少。
因此,对于’category_2’,用-1来填补这些缺失值;对于‘avg_sales_lag3’、‘avg_sales_lag6’、‘avg_sales_lag12’,直接用中位数来填补,或者删掉也无妨。
#填补缺失值(分类型变量)
merchant['category_2'] = merchant['category_2'].fillna(-1)
numeric_fill=['avg_sales_lag3','avg_sales_lag6','avg_sales_lag12']
for i in numeric_fill:
merchant[i] = merchant[i].fillna(merchant[i].median())
numeric_fill=['avg_sales_lag3','avg_sales_lag6','avg_sales_lag12']
for i in numeric_fill:
merchant[i] = merchant[i].fillna(merchant[i].median())
data=merchant.copy()
data.dropna(inplace=True)
data.index=range(data.shape[0])
merchant.isnull().sum()
merchant_id 0
merchant_group_id 0
merchant_category_id 0
subsector_id 0
numerical_1 0
numerical_2 0
category_1 0
most_recent_sales_range 0
most_recent_purchases_range 0
avg_sales_lag3 0
avg_purchases_lag3 0
active_months_lag3 0
avg_sales_lag6 0
avg_purchases_lag6 0
active_months_lag6 0
avg_sales_lag12 0
avg_purchases_lag12 0
active_months_lag12 0
category_4 0
city_id 0
state_id 0
category_2 0
dtype: int64
缺失值填补完毕
查看哪些变量里面含有inf值,直接.describe(),看均值和最大值
merchant.describe()
可以看到还是有一些变量(avg_purchases_lag3、avg_purchases_lag6、avg_purchases_lag12)的最大值、均值出现了inf,说明这些变量里面存在inf值,无法直接建模。可以使用天花板盖帽法的方式对其进行修改,即将inf改为最大的显式数值。代码实现流程如下:
inf_cols = ['avg_purchases_lag3', 'avg_purchases_lag6', 'avg_purchases_lag12']
merchant[inf_cols] = merchant[inf_cols].replace(np.inf, merchant[inf_cols].replace(np.inf, -99).max().max())
merchant.describe()
merchant_group_id | merchant_category_id | subsector_id | numerical_1 | numerical_2 | avg_sales_lag3 | avg_purchases_lag3 | active_months_lag3 | avg_sales_lag6 | avg_purchases_lag6 | active_months_lag6 | avg_sales_lag12 | avg_purchases_lag12 | active_months_lag12 | city_id | state_id | category_2 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 | 3.346960e+05 | 334696.000000 | 334696.000000 | 3.346960e+05 | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 | 334696.000000 |
mean | 31028.736143 | 423.131663 | 25.116404 | 0.011476 | 0.008103 | 13.832494 | 2.145143 | 2.994108 | 2.164999e+01 | 2.441947 | 5.947397 | 2.522677e+01 | 2.633572 | 11.599335 | 102.917926 | 11.860942 | 2.259958 |
std | 31623.043426 | 252.898046 | 9.807371 | 1.098154 | 1.070497 | 2395.443478 | 213.955844 | 0.095247 | 3.947031e+03 | 209.439373 | 0.394936 | 5.251740e+03 | 205.206198 | 1.520138 | 107.090673 | 6.176889 | 1.657263 |
min | 1.000000 | -1.000000 | -1.000000 | -0.057471 | -0.057471 | -82.130000 | 0.333495 | 1.000000 | -8.213000e+01 | 0.167045 | 1.000000 | -8.213000e+01 | 0.098330 | 1.000000 | -1.000000 | -1.000000 | -1.000000 |
25% | 3612.000000 | 222.000000 | 19.000000 | -0.057471 | -0.057471 | 0.880000 | 0.923650 | 3.000000 | 8.500000e-01 | 0.902247 | 6.000000 | 8.500000e-01 | 0.898333 | 12.000000 | -1.000000 | 9.000000 | 1.000000 |
50% | 19900.000000 | 373.000000 | 27.000000 | -0.057471 | -0.057471 | 1.000000 | 1.016667 | 3.000000 | 1.010000e+00 | 1.026961 | 6.000000 | 1.020000e+00 | 1.043361 | 12.000000 | 69.000000 | 9.000000 | 1.000000 |
75% | 51707.250000 | 683.000000 | 33.000000 | -0.047556 | -0.047556 | 1.160000 | 1.146522 | 3.000000 | 1.230000e+00 | 1.215575 | 6.000000 | 1.290000e+00 | 1.266480 | 12.000000 | 182.000000 | 16.000000 | 4.000000 |
max | 112586.000000 | 891.000000 | 41.000000 | 183.735111 | 182.079322 | 851844.640000 | 61851.333333 | 3.000000 | 1.513959e+06 | 61851.333333 | 6.000000 | 2.567408e+06 | 61851.333333 | 12.000000 | 347.000000 | 24.000000 | 5.000000 |
数据中已经没有无穷值了
接下来对离散变量进行字典编码,即将object对象类型按照sort顺序进行数值化(整数)编码。例如原始category_1取值为Y/N,通过sort排序后N在Y之前,因此在重新编码时N取值会重编码为0、Y取值会重编码为1。以此类推。
需要注意的是,从严格角度来说,变量类型应该是有三类:连续性变量、名义型变量以及有序变量。
名义变量:没有数值大小意义的分类变量,例如用1表示女、0表示男,0、1只是作为性别的指代,而没有1>0的含义。
有序变量:离散型变量,但却有数值大小含义,如上述most_recent_purchases_range字段,销售等级中A>B>C>D>E,该离散变量的5个取值水平是有严格大小意义的,该变量就被称为有序变量。
下面自定义一个编码函数,可以将object型变量转化成离散型变量
# 字典编码函数
def change_object_cols(se):
value = se.unique().tolist()
value.sort()#从小到大排序
return se.map(pd.Series(range(len(value)), index=value)).values
for col in object_cols:
merchant[col] = change_object_cols(merchant[col])