前言
零基础入门数据挖掘-Task1 赛题理解
时间:2020.03.24
数据分析
数据分析,分析什么?需要先对数据有一个整体的把握。对于我这个新手来说,暂时把握不了。当然,套路还是要走一走的,例如:
# 导入数据
path = r'D:/testPython/zero_is_start/'
train_df = pd.read_csv(path+'used_car_train_20200313.csv', sep=' ')
test_df = pd.read_csv(path+'used_car_testA_20200313.csv', sep=' ')
# 查看训练集数据
train_df,shape #数据维度
train_df.info() #数据类型,类型是否一致,有无缺失值
train_df.describe() #方差,均值,四分位数
train_df.head(10) #前10个样本
个人认为,其中最重要的是train_df.info()
,通过它大体上能够了解哪些数据存在缺失,哪些数据看起来没有缺失实际上缺失,例如,二手车是否损坏notRepairedDamage
这一分类特征,按照道理上来讲,数据类型应该是数值类型,但是却是object
,心里暗道:这个有问题!果然,存在隐藏的缺失值,表示形式为"-"
,我们把它替换成None
:
train_df['notRepairedDamage'].value_counts() #数据类型计数
df['notRepairedDamage'] = df['notRepairedDamage'].astype('str').apply(lambda x: x if x != '-' else None).astype('float16')
其次,train_df.describe()
能够帮助我们看看是否存在极端值或异常值,通过对数据标签price
进行分析,发现价格分布上存在异常,出现偏尾分布,可以通过下面命令查看:
train_df['price'].describe() #max, min
import matplotlib.pyplot as plt
plt.hist(train_df['price']) #偏尾分布严重
同样地,通过对其它连续变量的查看,发现power
,相较于赛题给出的定义域[0, 600]
,大大超过600
,而且功率power
最小值竟然可以为0
?,这是不是也应该算作缺失值。处理方法为:
# Method 1
train_df['power'] = train_df['power'].map(lambda x: 600 if x>600 else x)
train_df['power'] = train_df['power'].replace(0, np.nan, inplace=True)
# Method 2 - 分箱 - 连续变离散
bin = [i*10 for i in range(31)]
train_df['power_bin'] = pd.cut(train_df['power'], bin, labels=False)
大致浏览了一下数据,发现不如意的地方真不少,而且是真的有点乱。。。接下来,我们系统地分析下数据!
特征分类
在分析之前,需要先对数据类型进行划分,本题可以分为三种类型:连续性数字特征、离散型分类特征和时间序列特征,其中时间序列也可以当作连续型数值,这里先不把它们合并:
# 数字特征
numeric_features = train_df.select_dtypes(include=[np.number])
numeric_features.columns
# 分类特征
categorical_features = train_df.select_dtypes(include=[np.object])
categorical_features.columns
# 数字特征、分类特征和时间序列
date_cols = ['regDate', 'creatDate']
cate_cols = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage', 'regionCode', 'seller', 'offerType']
num_cols = ['power', 'kilometer'] + ['v_{}'.format(i) for i in range(15)]
接下来,我们根据具体的特征类型逐一处理分析。
分类特征
通过对分类变量的类别进行统计,可以发现一些分类变量是否类别严重偏倚或类别单一,这样的情况不足以用于数据预测,我们简单看一下类别数量:
# 查看分类特征类别数量
for i in cate_cols:
print("{}特征有{}不同的值".format(i, train_df[i].nunique()))
print(train_df[i].value_counts().sort_values(ascending=False))
# 删除一些异常特征
del train_df["seller"]
del train_df["offerType"]
del train_df["name"] # 类别太多
我们发现,seller
、offerTye
类别出现严重偏倚,几乎没有分类价值。name
类别太多,几乎两个样本一类,我让它们都 get out 了。值得一提的是,regionCode
这个特征,它有7905个不同的值。我参考的文章中把它处理成了city
:
# 把地区编码变成city
train_df['city'] = train_df['regionCode'].apply(lambda x : str(x)[:-3])
# 查看转化后的类别数
train_df['city'].nunique() #类别种类数
train_df['city'].unique() #各具体类别
train_df['city'].value_counts() #各类别样本计数
我查看了一下得出的结果, 一共有9类,其中一类为空类,样本数量最多:
9 #nunique
['1' '4' '2' '' '6' '3' '5' '7' '8'] #unique
------------------------------------------------------
36680 #value_counts
1 31886
2 26481
3 20545
4 14969
5 10047
6 6304
7 2986
8 102
对于上述结果,由于regionCode
位数有2、3或4,所以上述截取,貌似出了些问题。而且,这个编码听说是德国编码,是脱敏过的? 我暂时不知道怎么处理。
还有一些分类特征,是需要仔细考量的,model
(车型编码)需不需要进一步调整,按数量划分成更少的种类,或是其他方法? brand
(品牌)有40个不同的值,是不是也要处理一下? bodyType
(车身类型)整体还算稳定,fuelType
(燃油类型),这个分布比较偏尾,而且这个分类有7个不同的值:汽油、柴油、混合动力和电动汽车还好接受点,天然气? 液化石油气? 买个二手车,要不要这样? 这些类型竟然卖的还挺好,这是什么市场逻辑,买二手车图新鲜?这种分类,这种销售量,看得我逻辑冒烟。。。我对二手车也不懂,仅代表个人心理活动。嗯,不知道有没有《二手车交易买卖方的价格心理战》,我一定要买一本看看。不过,不同燃油类型的汽车,price
(售价)如何呢?
再分析一个特征notRepairedDamage
,之前介绍过这个特征中有缺失值,我们看一下缺失值数量:
train_df['notRepairedDamage'].value_counts()
# 结果如下
0.0 111361
- 24324
1.0 14315
Name: notRepairedDamage, dtype: int64
这个缺的真不少,我们看一下它和价格的关系:
好像没有损坏的车相对损坏的车价格整体高点? 这么多缺失值到底该咋办呢? 对于缺失值,像 XGBoost
和 LightGBM
都能够自动识别处理,分类特征就先介绍到这里。
连续特征
我们先介绍一下时间相关的特征,'regDate', 'creatDate'
,听ML67直播的时候讲到,对于这种时间序列特征要小心些,例如,当你进行交叉验证时,你总不能拿2017的交易特征训练数据,然后预测2016年的二手车交易价格? 这是要注意的地方,深入理解先后顺序。不过这里倒是不用太担心,因为creatDate
基本都是2016年。还有还有,如果用2016年去预测2017年,不同时间下,特征重要性是否发生改变? 例如,燃油类型重要性是否改变? 考虑到高科技的迅速变革,特征要根据“市情”调整。
我们可以把特征处理成年月日,分析发现,creatDate
基本上都是2016年的,也就没多少价值了,可以考虑删掉(当然,也可以不删掉)。
# 提取年份月份,创建新特征 used_years & used_months
train_df['regYear'] = train_df['regDate'].map(lambda x:int(str(x)[:4]))
train_df['createYear'] = train_df['creatDate'].map(lambda x:int(str(x)[:4]))
train_df['regMonth'] = train_df['regDate'].map(lambda x:int(str(x)[4:6]))
train_df['createMonth'] = train_df['creatDate'].map(lambda x:int(str(x)[4:6]))
train_df['used_months'] = (train_df['createYear'] - train_df['regYear'])*12 + \
(train_df['createMonth'] - train_df['regMonth'])
train_df['used_years'] = round(train_df['used_months'] / 12, 0)
# 看看creatYear有哪些
train_df['creatYear'].value_counts()
# 结果
2016 149982
2015 18
Name: creatYear, dtype: int64
同时,仔细想想,车的使用年限是不是应该和车价有一定的负相关? 所以,我们把两个日期重新整合成一个新特征used_time
,这里月份出现00
月,异常处理要注意,结果如下:
# regDate - isnull - 15072
train_df['used_time'] = (pd.to_datetime(train_df['creatDate'], format='%Y%m%d', errors='coerce') - \
pd.to_datetime(train_df['regDate'], format='%Y%m%d', errors='coerce')).dt.days
# 9087个不相同的'used_time',先不用它,重新处理异常值
再发散一下思维,会不会哪个月份商家促销,影响交易价格? 会不会周六周末天气好,大家闲得无聊买辆车,一起去郊游? 我们可以分析下,看看星期与月份对价格的影响:
"""
创建一个函数来处理异常月份
"""
from tqdm import tqdm
def date_proc(x):
m = int(x[4:6])
if m == 0:
m = 1
return x[:4] + '-' + str(m) + '-' + x[6:]
for f in tqdm(date_cols):
train_df[f] = pd.to_datetime(train_df[f].astype('str').apply(date_proc))
train_df[f + '_year'] = train_df[f].dt.year
train_df[f + '_month'] = train_df[f].dt.month
train_df[f + '_day'] = train_df[f].dt.day
train_df[f + '_dayofweek'] = train_df[f].dt.dayofweek
plt.figure()
plt.figure(figsize=(16, 6))
i = 1
for f in date_cols:
for col in ['year', 'month', 'day', 'dayofweek']:
plt.subplot(2, 4, i)
i += 1
v = train_df[f + '_' + col].value_counts()
fig = sns.barplot(x=v.index, y=v.values)
for item in fig.get_xticklabels():
item.set_rotation(90)
plt.title(f + '_' + col)
plt.tight_layout()
plt.show()
星期几对价格有影响? 周日!三四月份也有影响,这个我查了一下,汽车行业有销售旺季和淡季之分,旺季在12月份和春季,淡季在夏秋季。和之前所说一样,
creatDate
可以不考虑,月份好像也有影响。之前创建了个新特征used_years
,让我们看一下使用时间和价格的关系:
sns.boxenplot(train_df['used_years'],train_df['price'])
plt.show()
# 类别数58
train_df['used_years'].nunique()
随着使用时间增加,价格整体呈降低趋势,这是符合逻辑的。时间特征大致分析到这里。
接下来看看数值连续特征,包括匿名特征:
num_cols = ['power', 'kilometer'] + ['v_{}'.format(i) for i in range(15)]
power
有异常值,这个文章开头处理过了,kilometer
整体分布情况可以看一下:
plt.hist(train_df['kilometer'])
kilometer
暂时也不需要处理,另外可以可视化看下其和价格的关系:
sns.barplot(train_df['kilometer'],train_df['price'])
plt.show()
为啥0.5那个地方,价格反而低呢? 急着用钱,卖便宜? 或者,这个车就是比较便宜的车型? 这个地方或许可以想想。
匿名特征该怎么处理呢? 我这里先考虑一下相关性,把相关性高的给踢掉:
"""
分析匿名特征间的相关性,并去除相关性高的
"""
v_n = ['v_{}'.format(i) for i in range(15)]
v_n = train_df[v_n]
v_n.info()
corr = v_n.corr()
print(corr)
feature_group = list(itertools.combinations(corr.columns, 2))
# 删除相关性高的变量
def filter_corr(corr, cutoff=0.8):
cols = []
for i, j in feature_group:
if corr.loc[i, j] > cutoff:
i_avg = corr[i][corr[i] != 1].mean()
j_avg = corr[j][corr[j] != 1].mean()
if i_avg >= j_avg:
cols.append(i)
else:
cols.append(j)
return set(cols)
# 相关系数高的组合去掉平均相关系数高的那个
drop_cols = filter_corr(corr, cutoff=0.9)
print(drop_cols)
# 结果
{'v_2', 'v_4', 'v_13', 'v_6'}
我们也可以通过降维或其他特征选择方法,例如打包法、嵌入法进行特征筛选,我也没弄,就不介绍了。对于连续特征,相差过大的话,可以考虑特征缩放,例如:log
、max-min
、bin
、Box-Cox
等,让其误差符合正态分布,更方便模型预测。
这样的话,一些冗余的特征就可以删掉了:
del df["seller"]
del df["offerType"]
del df["name"]
del df["regDate"]
del df["creatDate"]
del df["regYear"]
del df["createYear"]
del df["regMonth"]
del df["createMonth"]
del df["used_months"]
del df["regionCode"]
总结
目前大致就做了这些,用LightGBM
预测得分620,排不上名次,可能需要进一步挖掘特征或者做一下模型融合。
接下来考虑,做一下交互特征和模型融合,例如:用汽油的、柴油的之类的轿车、卡车对价格的影响? XGBoost
和LightGBM
结合一下? 最后,放几张不是我画的大图,具体可以查看我的参考文献:
参考
Datawhale 零基础入门数据挖掘-Task2 数据分析
TASK1赛题理解 & TASK2探索性分析(EDA
简单eda+baseline
baseline_model#线性模型#
关于 Datawhale
Datawhale
是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale
以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale
用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。
— End —