前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据的探索性(EDA)分析

数据的探索性(EDA)分析

作者头像
mathor
发布2020-03-25 17:19:48
1K0
发布2020-03-25 17:19:48
举报
文章被收录于专栏:mathormathor

该文章基于天池二手车交易价格预测比赛进行学习

大纲如下:

  • 数据初识(对数据做初步探索,查看字段类型,相关统计量等)
  • 数据感知(在初识的基础上进一步挖掘信息,主要包括数据的确实情况和异常情况)
  • 数据不惑(在前两个基础上进一步挖掘,包括查看预测值的分布和字段的类型判断)
  • 数据洞玄(对数值特征和类别特征分开挖掘,包括类别偏斜,类别分布可视化,数值相关等各种可视化技巧)
  • 数据知命(介绍pandas_profiling数据探索性分析的神器,通过这个神器可以对上面的信息进行整合)
  • 总结

首先导入一些包和数据集

代码语言:javascript
复制
import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno # 缺失值的可视化处理

"""导入数据集"""
train_data = pd.read_csv('./dataset/used_car_train_20200313.csv', sep=' ') # 指定分隔符为空格
test_data = pd.read_csv('./dataset/used_car_testA_20200313.csv', sep=' ')

1. 数据初识

这里主要是对读取的数据有一个大致的了解,包括简单了解数据的行列信息,数据的统计特征等

代码语言:javascript
复制
# 查看数据的形状(行数和列数)
print('train_data shape :', train_data.shape) # (150000, 31)
print('test_data shape :', test_data.shape) # (50000, 30)

# 数据简要概览
train_data.head().append(train_data.tail()) # 将开头5行和结尾5行拼接起来展示,head()和tail()默认值是5
test_data.head().append(test_data.tail())

从形状可以看出训练集共150000个样本,30个特征,1列价格;测试集共50000个样本,30个特征

代码语言:javascript
复制
# 数据信息的查看 .info()可以看到每列的type,以及NAN缺失值的信息
train_data.info()

通过info()可以发现几点信息,首先就是字段的类型,有一个object(后面需要单独处理)。其次有一些字段有空值,清洗的时候需要处理

通过info()了解数据每列的type,还有助于了解是否存在除了nan以外的特殊符号异常

代码语言:javascript
复制
# 通过 .columns 查看列名
train_data.columns

# 数据的统计信息概览
train_data.describe()

describe()中有每列的统计值,包括:个数count、平均值mean、方差std、最小值min、下四分位数25%、中位数50%、上四分位数75%、以及最大值max。查看这些信息可以瞬间掌握数据的大概范围以及异常值判断,例如999999,-1这些值其实是nan的另一种表达方式,有时候需要注意下

注意看左下角,提示有30列,但是刚才输出shape的时候明明提示有31列。注意,describe()是不包括object类型字段的统计信息的,毕竟不是数值类型。当然你也可以单独用describe()看看

代码语言:javascript
复制
train_data['notRepairedDamage'].describe()

# 结果:
count     150000
unique         3
top          0.0
freq      111361
Name: notRepairedDamage, dtype: object

可以看出,object类型的统计信息和数值类型的不太一样

2. 数据感知

数据感知是在数据初识的基础上,进一步挖掘数据的信息,主要包括数据的确实值和异常值等

代码语言:javascript
复制
# 查看每列存在nan的情况
train_data.isnull().sum()
# test_data.isnull().sum()

可以看出,modelbodyTypefuelTypegearbox有缺失值。还可以对nan进行可视化,看的更加明显

代码语言:javascript
复制
missing = train_data.isnull().sum()
missing = missing[missing > 0]
missing.sort_values(inplace=True)
missing.plot.bar()

可视化nan的个数主要目的在于,查看nan存在的个数是否真的很大,如果很小一般选择填充,如果使用lgb等树模型可以直接让树自己去优化,但如果nan存在的过多,可以考虑删掉

下面是可视化缺失值

代码语言:javascript
复制
# 可视化缺失值
msno.matrix(train_data.sample(250)) # sample(250)表示抽取250个样本
# msno.matrix(test_data.sample(250))

# msno.bar(train_data.sample(1000))
# msno.bar(test_data.sample(1000))

下图是代码运行后得到的结果,白线越多,代表缺失值越多(fuleType缺失的最多)

对数据持着怀疑的角度审视,尤其是object字段

代码语言:javascript
复制
# 看看object这个字段的取值情况
train_data['notRepairedDamage'].value_counts()

# 结果
0.0    111361
-       24324
1.0     14315
Name: notRepairedDamage, dtype: int64

这个字段里面居然有个-值,如果单看比赛给的字段描述:0代表有未修复的损害,1代表没有,如果我们不持着怀疑的态度,很难发现这里还有个-,这个也代表缺失,因为很多模型可以对nan直接处理,所以这里我们先将-替换为nan

代码语言:javascript
复制
train_data['noRepairedDamage'].replace('-', np.nan, inplace=True)
# test_data['noRepairedDamage'].replace('-', np.nan, inplace=True)

3. 数据不惑

通过初识和感知,我们不仅认识了数据,害发现了一些异常和缺失,下面我们进一步挖掘数据信息,主要包括查看预测值的分布以及 将字段分成数值型和类别型,后面分开查看和处理

3.1 了解预测值的分布
代码语言:javascript
复制
"""查看预测值的频数"""
train_data['price'].value_counts()

# 直方图可视化 自动划分10(默认值)个价格区间 统计每个区间的频数
plt.hist(train_data['price'], orientation='vertical', histtype='bar', color='red')
plt.show()

查看频数,发现价格大于20000的值极少,其实这里也可以把这些值当作特殊值(或异常值)直接删掉,不过直接删掉不太好,毕竟这是个回归问题

代码语言:javascript
复制
"""总体分布概况(无界约翰逊分布等)"""
import scipy.stats as st
y = train_data['price']

plt.figure(figsize=(20, 5))
plt.subplot(1, 3, 1)
plt.title('Johnson SU')
sns.distplot(y, kde=False, fit=st.johnsonsu)

plt.subplot(1, 3, 2)
plt.title('normal')
sns.distplot(y, kde=False, fit=st.norm)

plt.subplot(1, 3, 3)
plt.title('Log Normal')
sns.distplot(y, kde=False, fit=st.lognorm)

可以发现,价格不服从正态分布,所以在进行回归之前,必须将它进行转换。最佳拟合的是无界约翰逊分布

代码语言:javascript
复制
# log变换之后的分布会变得比较均匀,可以进行log变换进行预测,这也是预测问题常用的trick
plt.hist(np.log(train_data['price']), orientation='vertical', histtype='bar', color='red')
plt.show()

对预测标签做log转换,使其更加服从正态分布

代码语言:javascript
复制
"""查看偏度和峰度"""
sns.distplot(train_data['price'])
print('Skewness : %f' % train_data['price'].skew())
print('Kurtosis : %f' % train_data['price'].kurt())

# 结果
Skewness : 3.346487
Kurtosis : 18.995183
代码语言:javascript
复制
# train_data.skew(), train_data.kurt()
plt.figure(figsize=(20, 5))
plt.subplot(121)
sns.distplot(train_data.skew(), color='blue', axlabel='Skewness')
plot.subplot(122)
sns.distplot(train_data.kurt(), color='orange', axlabel='Kurtness')

峰度Kurt代表数据分布的尖锐程度,偏度简单来说就是数据的不对称程度,skew、kurt说明参考这篇博客

3.2 把字段分为数值字段和类别字段
代码语言:javascript
复制
"""先分离出label值"""
y_train = train_data['price']

# 数值特征
# numeric_features = train_data.select_dtypes(include=[np.number])
# numeric_features.columns

# 类别特征
# categorical_features = train_data.select_dtypes(include=[np.object])
# categorical_features.columns

上面是自动选取的方式,也可以人为设定

代码语言:javascript
复制
"""人为设定"""
numeric_features = ['power', 'kilometer'].extend(['v_'+str(i) for i in range(15)])

# 这里我感觉这个name和预测值没有关系,所以虽然是类别,可以先去掉看看, 日期的也去掉
categorical_features = ['model', 'brand', 'bodyType', 'fuelType', 'gearbox', 
                        'notRepairedDamage','regionCode', 'seller', 'offerType']

4. 数据洞玄

前面的工作我们已经分析了预测值的分布,从分布中我们看到,如果把预测值进行对数变化一下,效果可能更好。然后我们又把特征字段拆分为数值型和类别型。接下来我们主要对数值特征和类别特征进一步挖掘信息,包括类别偏斜,类别分布可视化,数值可视化等

4.1 类别特征的探索

类别特征主要是看一下每个类别字段的取值和分布,会用到箱型图、小提琴图、柱状图等各种可视化技巧

代码语言:javascript
复制
"""类别偏斜处理"""
for cat_fea in categorical_features:
    print(cate_fea + '特征分布如下:')
    print('{}特征有{}不同的值'.format(cate_fea, len(train_data[cat_fea].unique())))
    print(train_data[cat_fea].value_counts())
    print()

这里主要是重点查看一下类别特征有没有数量严重偏斜的情况(由于太多,不在这里显示),这样的情况一般对预测没有什么帮助

代码语言:javascript
复制
train_data['seller'].value_counts()

0    149999
1         1
Name: seller, dtype: int64
代码语言:javascript
复制
train_data['offerType'].value_counts()

0    150000
Name: offerType, dtype: int64

sellerofferType字段偏斜就比较严重,直接删除这些字段

代码语言:javascript
复制
del train_data['seller']
del train_data['offerType']
del test_data['seller']
del test_data['offerType']

categorical_features.remove('seller')
categorical_features.remove('offerType')

下面看一下每个字段,有多少类(unique)

代码语言:javascript
复制
"""类别的unique分布"""
for cat in categorical_features:
    print(len(train_data[cat].unique()))

# 结果
249
40
9
8
3
3
7905

# 因为regionCode的类别太稀疏了,所以先去掉,因为后面要可视化,不画稀疏的
categorical_features.remove('regionCode')

下面使用各种可视化方式,可视化类别特征

代码语言:javascript
复制
"""类别特征箱型图可视化"""
for c in categorical_features:
    train_data[c] = train_data[c].astype('category')
    if train_data[c].isnull().any():
        train_data[c] = train_data[c].cat.add_categories(['MISSING'])
        train_data[c] = train_data[c].fillna('MISSING')

def boxplot(x, y, **kwargs):
    sns.boxenplot(x=x, y=y)
    x = plt.xticks(rotation=90)

f = pd.melt(train_data, id_vars=['price'], value_vars=categorical_features)
g = sns.FacetGrid(f, col="variable",  col_wrap=3, sharex=False, sharey=False, size=5)
g = g.map(boxplot, "value", "price")
代码语言:javascript
复制
"""类别特征的小提琴图可视化, 小提琴图类似箱型图,比后者高级点,图好看些"""
catg_list = categorical_features
target = 'price'
for catg in catg_list :
    sns.violinplot(x=catg, y=target, data=train_data)
    plt.show()

"""类别特征的柱形图可视化"""
def bar_plot(x, y, **kwargs):
    sns.barplot(x=x, y=y)
    x=plt.xticks(rotation=90)

f = pd.melt(train_data, id_vars=['price'], value_vars=categorical_features)
g = sns.FacetGrid(f, col="variable",  col_wrap=3, sharex=False, sharey=False, size=5)
g = g.map(bar_plot, "value", "price")

看一下柱形图的结果(小提琴的不在这展示)

代码语言:javascript
复制
"""类别特征的每个类别频数可视化(count_plot)"""
def count_plot(x,  **kwargs):
    sns.countplot(x=x)
    x=plt.xticks(rotation=90)

f = pd.melt(train_data,  value_vars=categorical_features)
g = sns.FacetGrid(f, col="variable",  col_wrap=3, sharex=False, sharey=False, size=5)
g = g.map(count_plot, "value")

这里面用到了一个melt函数,这是个转换函数,参考这篇博客

4.2 数值特征的探索

数值特征的探索我们要分析相关性等,也会学习各种相关性可视化的技巧

代码语言:javascript
复制
numeric_train_data = train_data[numeric_features]

# 把price这一列加上,这个也是数值
numeric_train_data['price'] = Y_train

"""相关性分析"""
correlation = numeric_train_data.corr()
print(correlation['price'].sort_values(ascending=False), '\n')   # 与price相关的特征排序

.corr()可以看到每个特征与price的相关性,并且排了个序。下面进行相关性可视化,使用热力图比较合适

代码语言:javascript
复制
# 可视化
f, ax = plt.subplots(figsize=(10,10))
plt.title('Correlation of Numeric Features with Price', y=1, size=16)
sns.heatmap(correlation, square=True, vmax=0.8)
代码语言:javascript
复制
# 删除price
del numeric_train_data['price']

"""查看几个数值特征的偏度和峰度"""
for col in numeric_train_data.columns:
     print('{:15}'.format(col), 
          'Skewness: {:05.2f}'.format(numeric_train_data[col].skew()) , 
          '   ' ,
          'Kurtosis: {:06.2f}'.format(numeric_train_data[col].kurt())  
         )

"""每个数字特征得分布可视化"""
f = pd.melt(train_data, value_vars=numeric_features)
g = sns.FacetGrid(f, col="variable",  col_wrap=5, sharex=False, sharey=False)
g = g.map(sns.distplot, "value")

数值特征的分布可视化,从这里可以看到数值特征的分布情况,其中匿名特征的分布相对均匀

代码语言:javascript
复制
"""数字特征相互之间的关系可视化"""
sns.set()
columns = ['price', 'v_12', 'v_8' , 'v_0', 'power', 'v_5',  'v_2', 'v_6', 'v_1', 'v_14']
sns.pairplot(train_data[columns],size = 2 ,kind ='scatter',diag_kind='kde')
plt.show()

这里面会看到有些特征之间是相关的, 比如v_1和v_6

代码语言:javascript
复制
"""多变量之间的关系可视化"""
fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6), (ax7, ax8), (ax9, ax10)) = plt.subplots(nrows=5, ncols=2, figsize=(24, 20))
# ['v_12', 'v_8' , 'v_0', 'power', 'v_5',  'v_2', 'v_6', 'v_1', 'v_14']
v_12_scatter_plot = pd.concat([Y_train,train_data['v_12']],axis = 1)
sns.regplot(x='v_12',y = 'price', data = v_12_scatter_plot,scatter= True, fit_reg=True, ax=ax1)

v_8_scatter_plot = pd.concat([Y_train,train_data['v_8']],axis = 1)
sns.regplot(x='v_8',y = 'price',data = v_8_scatter_plot,scatter= True, fit_reg=True, ax=ax2)

v_0_scatter_plot = pd.concat([Y_train,train_data['v_0']],axis = 1)
sns.regplot(x='v_0',y = 'price',data = v_0_scatter_plot,scatter= True, fit_reg=True, ax=ax3)

power_scatter_plot = pd.concat([Y_train,train_data['power']],axis = 1)
sns.regplot(x='power',y = 'price',data = power_scatter_plot,scatter= True, fit_reg=True, ax=ax4)

v_5_scatter_plot = pd.concat([Y_train,train_data['v_5']],axis = 1)
sns.regplot(x='v_5',y = 'price',data = v_5_scatter_plot,scatter= True, fit_reg=True, ax=ax5)

v_2_scatter_plot = pd.concat([Y_train,train_data['v_2']],axis = 1)
sns.regplot(x='v_2',y = 'price',data = v_2_scatter_plot,scatter= True, fit_reg=True, ax=ax6)

v_6_scatter_plot = pd.concat([Y_train,train_data['v_6']],axis = 1)
sns.regplot(x='v_6',y = 'price',data = v_6_scatter_plot,scatter= True, fit_reg=True, ax=ax7)

v_1_scatter_plot = pd.concat([Y_train,train_data['v_1']],axis = 1)
sns.regplot(x='v_1',y = 'price',data = v_1_scatter_plot,scatter= True, fit_reg=True, ax=ax8)

v_14_scatter_plot = pd.concat([Y_train,train_data['v_14']],axis = 1)
sns.regplot(x='v_14',y = 'price',data = v_14_scatter_plot,scatter= True, fit_reg=True, ax=ax9)

v_13_scatter_plot = pd.concat([Y_train,train_data['v_13']],axis = 1)
sns.regplot(x='v_13',y = 'price',data = v_13_scatter_plot,scatter= True, fit_reg=True, ax=ax10)

关于可视化的更多学习,可以参考这篇博客

5. 数据知命

这里会综合上面的这些过程,用pandas_profiling这个包使用函数ProfileReport生成一份数据探索性报告, 在这里面会看到:

  • 总体的数据信息(首先是数据集信息:变量数(列)、观察数(行)、数据缺失率、内存;数据类型的分布情况)
  • 警告信息
    • 类型,唯一值,缺失值
    • 分位数统计量,如最小值,Q1,中位数,Q3,最大值,范围,四分位数范围
    • 描述性统计数据,如均值,模式,标准差,总和,中位数绝对偏差,变异系数,峰度,偏度
  • 单变量描述(对每一个变量进行描述)
  • 相关性分析(皮尔逊系数和斯皮尔曼系数)
  • 采样查看等
代码语言:javascript
复制
# 两行简单的代码即可搞定上面的这些信息
pfr = ppf.ProfileReport(train_data)
pfr.to_file("./EDA.html")

6. 总结

今天通过围绕着二手车价格预测的比赛,从五个维度整理了一下数据探索性分析的相关知识,下面根据思维导图进行回顾

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 数据初识
  • 2. 数据感知
  • 3. 数据不惑
    • 3.1 了解预测值的分布
      • 3.2 把字段分为数值字段和类别字段
      • 4. 数据洞玄
        • 4.1 类别特征的探索
          • 4.2 数值特征的探索
          • 5. 数据知命
          • 6. 总结
          相关产品与服务
          灰盒安全测试
          腾讯知识图谱(Tencent Knowledge Graph,TKG)是一个集成图数据库、图计算引擎和图可视化分析的一站式平台。支持抽取和融合异构数据,支持千亿级节点关系的存储和计算,支持规则匹配、机器学习、图嵌入等图数据挖掘算法,拥有丰富的图数据渲染和展现的可视化方案。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档