零基础入门数据挖掘-Task2 数据分析

Datawhale 零基础入门数据挖掘-Task2 数据分析

前言

零基础入门数据挖掘-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"] # 类别太多

我们发现,sellerofferTye类别出现严重偏倚,几乎没有分类价值。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

这个缺的真不少,我们看一下它和价格的关系:
是否损坏与价格关系
好像没有损坏的车相对损坏的车价格整体高点? 这么多缺失值到底该咋办呢? 对于缺失值,像 XGBoostLightGBM 都能够自动识别处理,分类特征就先介绍到这里。

连续特征

我们先介绍一下时间相关的特征,'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

creatYear与价格关系图
同时,仔细想想,车的使用年限是不是应该和车价有一定的负相关? 所以,我们把两个日期重新整合成一个新特征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',先不用它,重新处理异常值           

月份异常值“00”
再发散一下思维,会不会哪个月份商家促销,影响交易价格? 会不会周六周末天气好,大家闲得无聊买辆车,一起去郊游? 我们可以分析下,看看星期与月份对价格的影响:

"""
创建一个函数来处理异常月份
"""
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分布kilometer暂时也不需要处理,另外可以可视化看下其和价格的关系:

sns.barplot(train_df['kilometer'],train_df['price'])
plt.show()

kilometer和价格的关系
为啥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'}

我们也可以通过降维或其他特征选择方法,例如打包法、嵌入法进行特征筛选,我也没弄,就不介绍了。对于连续特征,相差过大的话,可以考虑特征缩放,例如:logmax-minbinBox-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,排不上名次,可能需要进一步挖掘特征或者做一下模型融合。

接下来考虑,做一下交互特征和模型融合,例如:用汽油的、柴油的之类的轿车、卡车对价格的影响? XGBoostLightGBM结合一下? 最后,放几张不是我画的大图,具体可以查看我的参考文献:
连续值特征之间的关系可视化连续值特征和价格之间的关系可视化

参考

Datawhale 零基础入门数据挖掘-Task2 数据分析
TASK1赛题理解 & TASK2探索性分析(EDA
简单eda+baseline
baseline_model#线性模型#


关于 Datawhale

Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。

— End —

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值