#机器学习--第1章:EDA数据探索性分析

#机器学习--第1章:EDA数据探索性分析

一、序言

  本系列博客面向初学者,只讲浅显易懂易操作的知识。包含:数据分析、特征工程、模型训练等通用流程。将会一步一步引领大家完成一次完整的机器学习案例。点击下载 本系列博客所用数据集,提取码: r6m6。本系列使用工具:Pycharm+Python3.7.6
本系列博客主要面向初学者,以知识性扩充为主,如有笔误,请联系反馈
本系列博客代码并非都是必需,某些代码其实对于本问题而言并不合理,但是为了教学而补充

  数据相关字段解释:

项目 Value
SaleID 交易ID,唯一编码
name 汽车交易名称,已脱敏
regDate 汽车注册日期,例如20160101,2016年01月01日
model 车型编码,已脱敏
brand 汽车品牌,已脱敏
bodyType 车身类型:豪华轿车:0,微型车:1,厢型车:2,大巴车:3,敞篷车:4,双门汽车:5,商务车:6,搅拌车:7
fuel 燃油类型:汽油:0,柴油:1,液化石油气:2,天然气:3,混合动力:4,其他:5,电动:6
gearbox 变速箱:手动:0,自动:1
power 发动机功率:范围 [ 0, 600 ]
kilometer 汽车已行驶公里,单位万km
notRepairedDamage 汽车有尚未修复的损坏:是:0,否:1
regionCode 地区编码,已脱敏
seller 销售方:个体:0,非个体:1
offerType 报价类型:提供:0,请求:1
createDate 汽车上线时间,即开始售卖时间
price 二手车交易价格(预测目标)
v系列特征 匿名特征,包含v0-14在内15个匿名特征

二、EDA的意义

  EDA是指对已有的数据(特别是调查或观察得来的原始数据)在尽量少的先验假定下进行探索,通过作图、制表、方程拟合、计算特征量等手段探索数据的结构和规律的一种数据分析方法。
  在拿到数据后,首先要进行的是数据探索性分析(Exploratory Data Analysis),它可以有效的帮助我们熟悉数据集、了解数据集。初步分析变量间的相互关系以及变量与预测值之间的关系,并且对数据进行初步处理,如:数据的异常和缺失处理等,以便使数据集的结构和特征让接下来的预测问题更加可靠。通常的步骤如下:

  1.对于数据的初步分析(直接查看数据,或.sum(), .mean(),.descirbe()等统计函数)可以从:样本数量,训练集数量,是否有时间特征,是否是时许问题,特征所表示的含义(非匿名特征),特征类型(string,int,float,time),特征的缺失情况(注意缺失的在数据中的表现形式,有些是空的有些是”NAN”符号等),特征的均值方差情况。

  2.分析记录某些特征值缺失占比30%以上样本的缺失处理,有助于后续的模型验证和调节,分析特征应该是填充(填充方式是什么,均值填充,0填充,众数填充等),还是舍去,还是先做样本分类用不同的特征模型去预测。

  3.对于异常值做专门的分析,分析特征异常的Label是否为异常值(或者偏离均值较远或者是特殊符号),异常值是否应该剔除,还是用正常值填充,是记录异常,还是机器本身异常等。

  4.对于Label做专门的分析,分析标签的分布情况等。

  5.进一步分析;可以通过对特征作图,特征和Label联合做图(统计图,离散图),直观了解特征的分布情况,通过这一步也可以发现数据之中的一些异常值等,通过箱型图分析一些特征值的偏离情况,通过特征和特征联合作图、特征和Label联合作图,分析其中的一些关联性。

异常值检测:
  异常值的检测通常是需要结合数据分布一起考量的,这里只是简单说下理论。

常见方法有:
  a.均方差、正态分布的 ±3σ 标准差,大约99.7%会在三个标准差范围内。
  b.箱形图和散点图,通过可视化的方法来直观的看出离群点。
  c.算法方法,聚类,孤立森林等。

注意点:
  1.以上说的都只适用于连续型特征。
  2.离散型特征更趋向于人的主观判断,比如某些类别过少等。
  3.离散型特征,主要处理目标变量的不平衡问题。
  4.异常值并不存在完美严格的标准可以适用于一切情况,最重要的还是具体问题具体分析。

三、EDA的流程


1、载入数据并简略观察数据

import numpy as np
import pandas as pd
import seaborn as sns
import missingno as msno
import scipy.stats as st
import matplotlib.pyplot as plt 

# 1.1
train_data = pd.read_csv("二手车交易价格预测/used_car_train_20200313.csv", ' ')
# 因为 SaleID 是明显无用特征,直接筛去
del train_data['SaleID']

在Pycharm的科学计算模式下,直接点击查看train_data。


2、总览数据概况
   - 在 describe 中有每一列的统计量、均值、标准差、最小值、中位数25% 50% 75%以及最大值。可以帮助我们快速掌握数据的大概范围和数据的异常判断。
   - 通过 info 来了解每列的 type 有助于了解是否存在除 Nan 以外的符号异常
   - 通过 isnull().sum() 查看每列缺失情况,nan 可以使用如众数、均值等进行填充,但若某一列存在过多 nan 就可以考虑将此列删除。

# 2.1
# 通过 info 查看数据类型
print(train_data.info())


# 2.2
# 在这里我们可以看到除 notRepairedDamage 外其它字段的类型均为数字类型
# 因此我们可以查看一下 notRepairedDamage 的值的分布情况
print(train_data['notRepairedDamage'].value_counts())


# 2.3
# 在这里我们看到有一个值为 “-” ,而其它值为数字,说明这个值是无意义的,也就是 Nan
# 因此我们在这里将其替换为 -1
train_data['notRepairedDamage'].replace("-", -1., inplace=True)
# 因为这个数据代表车辆是否有过修里破损,因此为了后续方便,将其数据类型转化为 int32
train_data['notRepairedDamage'] = train_data['notRepairedDamage'].astype(np.float32).astype(np.int32)
# 再次查看,已经都是数字了
print(train_data.info())


# 2.4
# 通过 describe 和 matplotlib 可视化查看数据的相关统计量
train_data_describe = train_data.describe()
columns = train_data_describe.columns
index = train_data_describe.index[1:]
colors = ['blue', 'red', 'green', 'black', 'pink', 'purple', 'gray', 'yellow']
plt.figure(figsize=(15, 10))
for i in range(len(columns) - 16):
    ax = plt.subplot(4, 4, i + 1)
    ax.set_title(columns[i])
    for j in range(len(index)):
        plt.bar(index[j], train_data_describe.loc[index[j], columns[i]], color=colors[j])
plt.show()
del columns, index, colors, i, j, ax
# 可以从中看到 seller 、 offerType 和 power 的数据不太正常
# 进一步查看分析 train_data_describe 得知:
# power、seller 的 mean 过于接近 min,offerType 的 mean=min=max


# 2.5
# 查看上述异常列的值的分布情况
for column in ['seller', 'offerType', 'power']:
    print(column + '\n', train_data[column].value_counts(), '\n')
del column
# 分析得知,power 仅仅只是 max 太大了
# 而 seller 和 offerType 特征严重倾斜,对预测没有什么帮助,故可选择删除
del train_data['seller'], train_data['offerType']


# 2.6
# 查看一下 nan 的分布情况
train_data['notRepairedDamage'].replace(-1, np.nan, inplace=True)
print(train_data.isnull().sum())
train_data['notRepairedDamage'].replace(np.nan, -1, inplace=True)
# 可视化查看 nan 的分布情况
msno.matrix(train_data)
plt.show()
# 如果某个特征的 nan 过多,则可选择性将其删除
# 这里我们看到虽然 notRepairedDamage 这个特征的 nan 高达 24324
# 但是直观上考虑到这个变量对于二手车价格的影响应该很大,所以这里就不删除了


3、了解预测值的分布

# 3.1
# 尝试多种方法,拟合价格分布
# 可参考 https://cloud.tencent.com/developer/ask/112712 选取合适的拟合方法
y_train = train_data['price']
fits = [st.johnsonsu, st.exponweib]
plt.figure(figsize=(6, 3 * len(fits)))
for i in range(len(fits)):
    ax = plt.subplot(len(fits), 1, i+1)
    ax.set_title(str(fits[i]))
    sns.distplot(y_train, kde=False, fit=fits[i])
plt.show()
del fits, ax, i
# 由图可知无界约翰逊分布和 exponweib 分布拟合效果很好


# 3.2
# 查看价格的偏度(skewness)和峰度(kurtosis)
# 有关偏度和峰度的介绍见链接 https://blog.csdn.net/qq_36523839/article/details/88671873
print("skewness is ", y_train.skew())
print("kurtosis is ", y_train.kurt())


# 3.3
# 从上面的拟合效果中可以看出价格大致服从 exp^-1 的指数分布
# 但是由于指数分布越往后分布越稀疏,因此尝试对数据尝试进行 log 变换。
# log 变换后再次绘制频数分布图,发现,明显缩小了价格的分布区间,
# 进行某种函数变换后再进行预测,这也是预测问题常用的trick
y_train = np.log(y_train)
plt.hist(y_train)
plt.show()


# 4.1
# 查看每个特征的 unique 数量
columns = train_data.columns
for i in columns:
    print(i, len(train_data[i].unique()))
del i, columns


# 4.2
# 根据特征含义,人工筛选连续型特征和离散特征
numeric_features = ['creatDate', 'regDate', 'power', 'kilometer', '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']
categorical_features = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage',
                        'regionCode']

# 4.3
# 对连续性特征和离散型特征分别进行相关性分析,并且绘制相关性矩阵
numeric_feature_corr = train_data[['price'] + numeric_features].corr()
categorical_feature_corr = train_data[['price'] + categorical_features].corr()
plt.figure(figsize=(7, 7))
plt.title("Correlation of numeric feature")
sns.heatmap(numeric_feature_corr, square=True)
plt.show()
print("Correlation of numeric feature with price:\n", numeric_feature_corr['price'], '\n')
plt.figure(figsize=(7, 7))
plt.title("Correlation of categorical feature")
sns.heatmap(categorical_feature_corr, square=True)
plt.show()
print("Correlation of numeric feature with price:\n", categorical_feature_corr['price'], '\n')
del categorical_feature_corr, numeric_feature_corr


# 4.6
# 让连续型特征的频率分布可视化
f = pd.melt(train_data, value_vars=numeric_features)
sns.FacetGrid(f, col="variable", col_wrap=4, sharex=False, sharey=False).map(sns.distplot, "value")
plt.show()
del f


# 4.7
# 观察分布非常不均匀的数据,进一步绘制其与 price 之间的散点图,观察相关关系
columns = ['power', 'v_1', 'v_2', 'v_5', 'v_6', 'v_7', 'v_11', 'price']
sns.set()
sns.PairGrid(train_data[columns], x_vars=columns[:len(columns) - 1], y_vars=['price']).map(sns.scatterplot)
plt.show()
del columns


# 4.8
# 绘制离散特征箱型图
# 关于箱型图和小提琴图请参考:https://www.cnblogs.com/zhhfan/p/11344310.html
# 让 X 轴上的标签为垂直方向
def boxplot(x, y, **kwargs):
    sns.boxplot(x=x, y=y)
    plt.xticks(rotation='vertical', **kwargs)


# 由 4.1 得知,name 的 unique 为 99662,regionCode 的 unique 为  7905,类别过于稀疏,因此这里不予考虑
categorical_features = ['model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage']
# 将数据类型转换为 category,并且填充 Nan 为 Missing
train_data['notRepairedDamage'].replace(-1, np.nan, inplace=True)
for i in categorical_features:
    train_data[i] = train_data[i].astype('category')
    if train_data[i].isnull().any():
        train_data[i].cat.add_categories(['missing'], inplace=True)
        train_data[i].fillna('missing')
# 数据融合并绘制箱型图,由于 model 和 brand 类别有点多,所以单独绘制它俩
f = pd.melt(train_data, id_vars='price', value_vars=categorical_features[2:])
plt.figure(figsize=(10, 10))
sns.FacetGrid(f, col='variable', col_wrap=2, sharex=False, sharey=False).map(boxplot, 'value', 'price')
plt.show()

# 单独绘制 model 的箱型图
plt.figure(figsize=(60, 4.8))
plt.xticks(rotation='vertical')
sns.boxplot(x=train_data[categorical_features[0]], y=train_data['price'])
plt.show()
# 单独绘制 brand 的箱型图
plt.figure(figsize=(9, 4.8))
sns.boxplot(x=train_data[categorical_features[1]], y=train_data['price'])
plt.show()
del i


# 4.9
# 绘制离散特征的小提琴图
# 数据融合并小提琴图,由于 model 和 brand 类别有点多,所以单独绘制它俩
sns.FacetGrid(f, col='variable', col_wrap=2, sharex=False, sharey=False).map(sns.violinplot, 'value', 'price')
# noinspection DuplicatedCode
plt.show()

# 单独绘制 model 的小提琴图
plt.figure(figsize=(60, 4.8))
plt.xticks(rotation='vertical')
sns.violinplot(x=train_data[categorical_features[0]], y=train_data['price'])
plt.show()
# 单独绘制 brand 的小提琴图
plt.figure(figsize=(9, 4.8))
sns.violinplot(x=train_data[categorical_features[1]], y=train_data['price'])
plt.show()
del f


# 4.10
# 绘制柱形图
f = pd.melt(train_data, id_vars=['price'], value_vars=categorical_features)
sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False).map(sns.barplot, "value", "price")
plt.show()


# 4.11
# 绘制频数图
f = pd.melt(train_data,  value_vars=categorical_features)
sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False, size=5).map(sns.countplot, "value")
plt.show()

发布了14 篇原创文章 · 获赞 50 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_43519779/article/details/105067691