天天向上小队-天天,task2,EDA学习笔记
目录
数据处理总结
-
缺失值处理
- 该数据集缺失的都是类别特征里的,且部分类别特征与某些匿名变量线性相关性强
- 考虑填充新的值,比如-1
- 填充众数、平均数(需要取整),knn邻近(速度慢)
-
异常值处理
- 识别:
- 箱型图识别
- 3σ识别
- 处理:
- 边界值替换
- 映射到新维度μ,μ(正常值)=0,μ(异常值)= function(异常值)
- 不处理,与原数据一起归一化|标准化
- 分桶法(分箱法),单正常值要一起处理
- 识别:
-
特征选择:
- PCA
- 相关性分析,剔除相关性高的类别,仅保留其中一类或少数类
- 通过添加噪声体现特征重要性
- 使用一些基于树的模型训练,可得到参数重要性
-
特征构造:
- 构造统计量特征
- 计数
- 求和
- 比例
- 标准差
- 上述计量特征的组合
- 时间特征
- 绝对时间
- 时间差
- 特殊时间:春节、国庆节等等节假日,是否会对价格造成影响,因为商家可能进行促销等等
- 地理信息
- 分箱
- 分布编码?
- 高频统计,取高频
- 简单的标准化|归一化,暂时没有想到更好的方法了
- 非线性变换,包括 log/ 平方/ 根号等
- 多项式组合
- 根据已有二手车测评指标以及现有数据,构造有理论支撑的新特征
- 构造统计量特征
-
24个匿名变量:
- 特性:
- 24个匿名变量都没有缺失值
- 部分匿名变量与一些非匿名变量强线性相关,存在冗余
- 可以考虑删除强线相关的非匿名变量,比如notRepairedDamage,因为它有缺失值
- 对匿名变量二值化|分桶编码,据此填充相关类别特征的缺失值
- 部分匿名变量之间强线性相关,存在冗余
- 二手车交易价格预测在阿里天池上有两场:零基础入门数据挖掘(以下简称0基础)、以及河北高校邀请赛(以下简称邀请赛),两者的数据集所含非匿名特征一致,不同的是邀请赛的匿名变量更多。并且,在两个比赛的排行榜上,0基础B榜第一MAE>300 ,邀请赛(4月16日)A榜第一MAE=148,且top选手的MAE基本上都是邀请赛的更小,而且小得多,这说明了什么:
- 匿名变量的处理本次数据挖掘比赛是关键之一,这里的处理强调特征构造
- 特性:
数据探索性分析
读取数据
# train_path 本地训练集绝对路径
import pandas
import matplotlib.pyplot as plt
train_csv = pd.read_csv(train_path,sep= ' ')
查看训练集列属性:
index_se = pd.Series([i for i in range(len(train_csv['SaleID']))])
train_csv.columns
'''
输出:
Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType',
'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode',
'seller', 'offerType', 'creatDate', 'price', 'v_0', 'v_1', 'v_2', 'v_3',
'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12',
'v_13', 'v_14', 'v_15', 'v_16', 'v_17', 'v_18', 'v_19', 'v_20', 'v_21',
'v_22', 'v_23'],
dtype='object')
'''
数据分布可视化
ps:由于某些特征范围过于广泛,我仅可视化了这些特征的高频部分
name:汽车交易名称,已脱敏
name = pd.DataFrame(train_csv['name'].value_counts())
print('该类别种类数',len(set(train_csv['name'])))
name.sort_values(by='name',ascending=False,inplace=True)
name.head(40).plot.bar()
plt.show()
# 输出:该类别种类数:164312
model:车型编码,已脱敏
model = pd.DataFrame(train_csv['model'].value_counts())
print('该类别种类数',len(set(train_csv['model'])))
model.sort_values(by='model',ascending=False,inplace=True)
model.head(40).plot.bar()
plt.show()
# 输出:该类别种类数:251
brand:汽车品牌,已脱敏
brand = pd.DataFrame(train_csv['brand'].value_counts())
print('该类别种类数',len(set(train_csv['brand'])))
brand.sort_values(by='brand',ascending=False,inplace=True)
brand.head(20).plot.bar()
plt.show()
# 输出: 该类别种类数:40
bodyType:车身类型
豪华轿车:0,微型车:1,厢型车:2,大巴车:3,敞篷车:4,双门汽车:5,商务车:6,搅拌车:7
bodyType = pd.DataFrame(train_csv['bodyType'].value_counts())
bodyType.sort_values(by='bodyType',ascending=False,inplace=True)
bodyType.head(20).plot.bar()
plt.show()
fuelType:燃油类型
汽油:0,柴油:1,液化石油气:2,天然气:3,混合动力:4,其他:5,电动:6
fuelType = pd.DataFrame(train_csv['fuelType'].value_counts())
fuelType.sort_values(by='fuelType',ascending=False,inplace=True)
fuelType.head(20).plot.bar()
plt.show()
print(train_csv['fuelType'].value_counts())
'''
# 输出:
0.0 150664
5.0 72494
4.0 3577
3.0 385
2.0 183
1.0 147
6.0 60
Name: fuelType, dtype: int64
'''
gearbox:变速箱
手动:0,自动:1
gearbox = pd.DataFrame(train_csv['gearbox'].value_counts())
gearbox.sort_values(by='gearbox',ascending=False,inplace=True)
gearbox.head(20).plot.bar()
plt.show()
power:发动机功率
有很多车子的power=0,意思是车子报废了?还是没有这个数据,拿0填充的?这是我的疑问之一。
power = pd.DataFrame(train_csv['power'].value_counts())
print('该类别范围',train_csv['power'].min(),'~',train_csv['power'].max())
power.sort_values(by='power',ascending=False,inplace=True)
power.head(20).plot.bar()
plt.show()
# 输出:该类别范围: 0 ~ 20000
kilometers:汽车已行驶公里数
kilometer = pd.DataFrame(train_csv['kilometer'].value_counts())
print('该类别范围',train_csv['kilometer'].min(),train_csv['kilometer'].max())
kilometer.sort_values(by='kilometer',ascending=False,inplace=True)
kilometer.head(20).plot.bar()
plt.show()
# 输出: 该类别范围0.5 ~ 15.0
kilometers,肉眼可见的长尾分布
notRepairedDamage:汽车有尚未修复的损坏
汽车有尚未修复的损坏:是:0,否:1
notRepairedDamage = pd.DataFrame(train_csv['notRepairedDamage'].value_counts())
notRepairedDamage.sort_values(by='notRepairedDamage',ascending=False,inplace=True)
notRepairedDamage.head(20).plot.bar()
plt.show()
regDate:注册日期
注意:这里的regDate和createDate这两个日期属性中,都包含一类错误时间格式数据,比如 19700003,实际上是1970年3月的一天,缺失的是具体的天数。这可能与数据来源的平台处理有关。我将错误格式的日期放在一起,发现它们某两位的取值范围是1~12,据此认为这是月份。
regDate = train_csv['regDate']
for i in range(len(regDate)):
item = str(regDate.loc[i])
if item[4:6] == '00':
item = item[:4] + item[-2:] + '15'
regDate.loc[i] = int(item)
regDate = pd.to_datetime(regDate,format='%Y%m%d')
print('该离散特征种类数',len(set(regDate)))
print('该离散特征范围',regDate.min(),regDate.max())
regDate = pd.DataFrame(regDate.value_counts())
regDate.sort_values(by='regDate',ascending=False,inplace=True)
regDate.head(30).plot.bar()
plt.show()
'''
输出:
该离散特征种类数 7537
该离散特征范围 1910-01-03 00:00:00 2019-12-12 00:00:00
'''
regDate的分布就有点讨人厌了,范围1910年到2019年,先做个箱型图看看
regDate.plot(kind='box')
plt.show()
这是我很讨厌的特征分布,如果按箱型图识别异常值(这里指的是相对于特征分布中心偏离很大的值),那么非常多的数据会被认为是异常值。然而将过多数据按异常值处理了,有可能“损坏”了数据本身蕴含的信息。目前我尚未发现|找到什么好的处理方法,只能做一般的标准化|归一化了。
creatDate:汽车上线时间,即开始售卖时间
对于二手车而言,createDate - rDate 就是这辆车的已使用时间
creatDate = train_csv['creatDate']
for i in range(len(creatDate)):
item = str(creatDate.loc[i])
if item[4:6] == '00':
item = item[:4] + item[-2:] + '15'
creatDate.loc[i] = int(item)
creatDate = pd.to_datetime(creatDate,format='%Y%m%d')
print('该离散特征种类数',len(set(creatDate)))
print('该离散特征范围',creatDate.min(),creatDate.max())
creatDate = pd.DataFrame(creatDate.value_counts())
creatDate.sort_values(by='creatDate',ascending=False,inplace=True)
creatDate.head(30).plot.bar()
plt.show()
'''
输出:
该离散特征种类数 107
该离散特征范围 2014-03-10 00:00:00 2016-04-07 00:00:00
'''
ps:这里是截图,plt.saveflg()存的图片没有正常显示x轴上的时间
可以看到creatDate的范围相对小得多。
很少的日期在2014年,大部分数据在2016年,还有一部分在2015年,虽然箱型图看起来挺别扭,但是这个分布比regDate好得多,一般的的标准化|归一化还是可以接受的
另外,汽车已使用时间usedtime = creatDate - regDate,不了解二手车交易的小伙伴可以自行百度
regionCode:地区编码,已脱敏
regionCode = pd.DataFrame(train_csv['regionCode'].value_counts())
print('该类别种类数',len(set(train_csv['regionCode'])))
regionCode.sort_values(by='regionCode',ascending=False,inplace=True)
regionCode.head(30).plot.bar()
plt.show()
# 输出: 该类别种类数:8081
price:汽车交易价格
price = pd.DataFrame(train_csv['price'].value_counts())
print('该连续变量取值可能种类数',len(set(train_csv['price'])))
print('该离散特征范围',train_csv['price'].min(),train_csv['price'].max())
price.sort_values(by='price',ascending=False,inplace=True)
price.head(20).plot.bar()
plt.show()
'''
输出:
该连续变量取值可能种类数 4585
该离散特征范围 0 100000
'''
v_0 ~ v_23 :匿名特征
处理好匿名变量,包括匿名变量本身的数据处理,以及从匿名变量发掘出新特征,可能会提高模型效果。这是我的主观看法,做过主成分分析后,还会发现,一些匿名变量与几个非匿名变量之间具有很强的相关性,比如notRepairedDamage 与一个匿名变量相关系数大于0.9,而notRepairedDamage这个属性中有不少缺失值,据此,我们可以直接删除notRepairedDamage这一个属性,因为冗余了,或者对那个匿名变量二值化,代替notRepairedDamage。
v_col = ['v_'+ str(i) for i in range(24)]
v_ = pd.DataFrame(train_csv.get(v_col))
v_des = pd.DataFrame(v_.describe())
v_t = v_des.T
for col in v_col:
v_t.loc[col][1:].plot.bar()
plt.show()
- v_0
- v_1
- v_2
-
v_3
-
v_4
-
v_5
-
v_6
- v_7
- v_8
-
v_9
-
v_10
-
v_11
-
v_12
-
v_13
-
v_14
-
v_15
-
v_16
-
v_17
-
v_18
-
v_19
-
v_20
-
v_21
-
v_22
-
v_23
特征与回归目标price的散点图
data = train_csv
plot_kind = ['line','bar','barh','hist','box','kde','density','area','pie','scatter','hexbin']
columns = ['SaleID','name','regDate','model','brand','bodyType','fuelType','gearbox','power','kilometer','notRepairedDamage','regionCode',
'seller','offerType','creatDate','price'] + ['v_' + str(i) for i in range(0,24)]
data.columns
for col in columns:
plt.scatter(data[col],data['price'],c=data['price'],s = 10 , cmap='rainbow')
plt.xlabel(col)
plt.xlabel(col)
plt.ylabel('price')
plt.show()
ps:seller,offerType这种分布严重不均衡的特征在Datawhale的baseline里已经提到了,这也可以在散点图里看出
图片上传的顺序受上传速度等的影响,与代码跑出的顺序不一样
我的感觉是图片很好看,但是不知道绘制出来有什么用?
我的理解是:特征构造在无先验理论的支撑下,构造出新特征(尤其是有用的特征)是需要灵感的,也是有难度的,可视化能帮助我们产生奇思妙想。
处理异常值
箱型图识别与处理:
使用pands.DataFrame.plot(kind=‘box’),可绘制箱型图。
注意要一个特征一个特征的绘制,比如↓
for i,col in enumerate(train_csv.columns):
train_csv[col].plot(kind='box')
plt.show()
特征过多,仅展示部分,情况是很多特征有很多异常值,心累。看看我们训练集price的分布(只是看看,不会用箱型图处理回归目标的,有时会用box-cox):
感觉就想是箱型图不是很适合识别本数据集
博主写到这里想偷懒了
很简单就可以实现,博主因为参加了邀请赛,现在不适合直接上自己封装的方法,读者可以自行实现。
3σ准则高中数学就说过,
3σ准则
具体方法博主可能等到比赛结束后会更新,但是这无关紧要,重要的是处理数据的原理、方法。
离散变量-类别取高频 ,数值分桶|分箱
-
类别取高频
-
地理位置:聚类,算出几个中心点,计算曼哈顿距离、欧氏距离可能有神奇的效果
-
分桶方法:
- 等频分桶;
- 等距分桶;
- Best-KS 分桶(类似利用基尼指数进行二分类)
- 卡方分桶
-
为什么要进行分桶(个人理解):
- 降维
- 具体场景要求,比如火车票的售价与儿童身高之间的关系
- 为什么要进行数据分箱
-
分箱如何实现,pandas库里就有:
如何使用pandas进行数据分箱
相关性分析:
- 为什么要进行相关性分析:
- 减小特征之间的冗余,模型的参数的更新可能是去处理冗余特征之间的权重去了,没有实质上的进步。
-
data.corr()
,即可计算各个特征之间的相关系数 - 分析完,在相互相关的特征中选取一个或者多个,从而降维、处理冗余特征
主成分分析:
from sklearn.decomposition import IncrementalPCA
from sklearn.decomposition import SparsePCA
from sklearn.decomposition import PCA
sklearn 优雅的进行数据处理,建立模型、调参
类别变量处理
博主花了很多很多很多时间处理类别变量,实践了我所了解的以下全部编码方式,但是就交叉检验效果与A榜成绩而言,效果好方式的都差不多,没有什么实质上的提升。这也是我觉得匿名变量是本次比赛的关键的想法来源,也让我感觉**特征构造是关键,特征构造是关键,特征构造是关键。**因为我一直局限在原有特征上,怎么处理原数据、怎么调模型的参数、怎么尝试模型,效果好的时候MAE也差不多,效果差MAE能升天
-
one-hot,简单、粗暴、有效
- 特征取值少,one-hot编码
- 特征取值太多,统计高频,低频取值归为一类,或者分箱,然后one-hot编码
-
其他类别编码方式:
- 升维编码方式: BackwardDifferenceEncoder,BinaryEncoder,HashingEncoder,HelmertEncoder,SumEncoder,
- 维度不变:
CatBoostEncoder(),CountEncoder(),GLMMEncoder(),JamesSteinEncoder(),LeaveOneOutEncoder(),MEstimateEncoder(),OrdinalEncoder()TargetEncoder()
题外话,分享一个博主比赛时的趣事:
有一天,我像往常一样将处理数据好的数据喂给模型,模型经过几轮训练后,交叉检验的MAE只有68,我当时大喜,心想,A榜第一100多,我现在就68了。于是我赶紧给模型喂入处理好的testA数据,提交到天池。在我多次刷新之后,看到MAE=3300多。我大惊,仔细检查后发现,我不小心将price放进了训练集。哈哈哈…
END of task2