EDA

探索性数据分析 EDA(Exploratory Data Analysis)是数据分析和挖掘的第一步,主要是对数据集进行了解,包括基本情况、特征情况、特征间关系等等,为进一步的分析和挖掘提供信息。

一个完整的 EDA 过程一般大致包括四步:

  • 问题定义:问题定义涉及的主要任务是定义分析的主要目标,定义主要的可交付成果,概述主要角色和职责,获取数据的当前状态,定义时间表以及执行成本/收益分析。
  • 数据准备:包括数据源定义、数据 schema 定义、数据特征了解、数据清理、数据转换、数据分割等。
  • 数据分析:这是处理描述性统计信息和数据分析的最关键步骤之一。 主要任务包括汇总数据,发现数据之间隐藏的关联和关系,开发预测模型,评估模型以及计算精度。
  • 结果展示:以图表、摘要、地图和图表的形式将数据集呈现给目标受众。

本文主要是针对一般机器学习项目的 EDA,侧重于对数据基本情况和特征关系的了解,为下一步的特征工程和模型做准备。

数据来自:零基础入门金融风控-贷款违约预测-天池大赛-阿里云天池

代码 Notebook 在这里:EDA,或用 nbviewer 查看。

总体了解

这一部分主要了解数据集整体的情况,主要包括以下内容:

  • 数据集规模
  • 有哪些特征
  • 不同特征数据类型
  • 不同特征样本数
  • 不同特征均值、标准差、中位等
  • 缺失值
  • 唯一值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据规模
data.shape
# 特征
data.columns
# 数据类型
data.dtypes
# 不同特征样本数
data.inf()
# 特征基本统计指标
data.describe()
# 缺失值
data.isna().any().sum()
data.isnull().sum()
# 唯一值
[col for col in data.columns if data[col].nunique() <= 1]

数据分布

这一部分主要了解不同特征的分布,主要包括:

  • 特征在所有 Label 上的分布
  • 特征在不同 Label 上的分布
  • 特征的数据类型

特征的数据类型一般包括以下几类:

  • 数值数据:定量数据
    • 离散数据
    • 连续数据
  • 分类数据:定性数据

度量数据有以下不同方法:

  • 标签
  • 序数
  • 区间
  • 比率

数值数据一般可以直接用于模型,但风控时往往要对其进行分桶,对其 WOE 编码后再使用,这里提到过,这其实是一种非线性的思路,能够降低模型复杂度,减少噪声影响,使得模型更加稳定。

分类数据也可以分桶,或直接编码,编码方式有很多种,比如常见的 One-Hot 编码。数值类型中的离散数据也可以用类似的方式编码。

正因为每个特征都有特殊情况,所以,我们最好画出所有特征的分布,然后逐个去分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 特征 unique 值、Sample 等
features = data.columns
for fea in features:
print("Feature:", fea, "\t",
"Number Unique:", len(data[fea].unique()), "\t",
"Sample:", data[fea].iloc[0])

# 特征里面不同值的数量
rows = len(features)
fig, axes = plt.subplots(nrows=rows, ncols=1, figsize=(12, 4*rows))
for i, fea in enumerate(features):
# sns.barplot(
# x=list(data[fea].value_counts())[:10],
# y=data[fea].value_counts().keys()[:10],
# ax = axes[i]
# )
data_train[fea].value_counts().nlargest(10).plot(
kind='barh', ax=axes[i])
axes[i].set_title(fea)

# 所有 Label 上的特征分布
figsize = (18, 40)
cols = 4
rows = len(features) // cols + 1

def trim_axs(axs, N):
axs = axs.flat
for ax in axs[N:]:
ax.remove()
return axs[:N]
def plot_hist(fea_data, ax):
if fea_data.dtype == "object":
ax.hist(fea_data, bins=15)
else:
try:
sns.distplot(fea_data, ax=ax, axlabel=False)
except Exception as e:
ax.hist(fea_data, bins=15)

axs = plt.figure(figsize=figsize, constrained_layout=False).subplots(rows, cols)
axs = trim_axs(axs, len(features))
for ax, fea in zip(axs, features):
fea_data = data[fea].dropna()
ax.set_title(fea)
plot_hist(fea_data, ax)

# 偏态 log 化
skews = data[numerical_serial_fea].skew()
def prevent_zero(x):
return x + 1e-5

skew_dict = skews.to_dict()
rows = len(skew_dict)
fig, axes = plt.subplots(nrows=, ncols=2, figsize=(12, 3*rows))

for i, (key, val) in enumerate(skew_dict.items()):
fea_data = data[key].dropna()
plot_hist(fea_data, ax=axes[i, 0])
axes[i, 0].set_title(key +"/" + str(val), loc='right', pad=1.0)

new_data = fea_data.dropna().apply(prevent_zero).apply(np.log).dropna()
plot_hist(new_data, ax=axes[i, 1])
new_val = new_data.skew()
axes[i, 1].set_title(key +"/" + str(new_val), loc='right', pad=1.0)

# 不同 Label 特征分布
fig, axes = plt.subplots(nrows=len(features), ncols=2, figsize=(12, 3*len(features)))

for i, fea in enumerate(features):
fea_data = data[data.label == 1][fea].dropna()
plot_hist(fea_data, ax=axes[i, 0])
axes[i, 0].set_title(fea + " Label=1", loc='right', pad=1.0)

fea_data = data[data.label == 0][fea].dropna()
plot_hist(fea_data, ax=axes[i, 1])
axes[i, 1].set_title(fea + " Label=0", loc='right', pad=1.0)

数据关系

这一部分主要了解特征的关系,主要包括:

  • 特征和因变量之间的关系
  • 特征之间的关系

在此处理特征之间关系前需要先对特征进行一些处理,包括:

  • 清理掉唯一值的特征
  • 非数值特征能转为数值特征的转为数值特征
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 特征和因变量
rows = len(features)
need_drop_feas = ["policyCode", "isDefault"]
fig, axes = plt.subplots(nrows=rows, ncols=1, figsize=(12, 4*rows))
g = sns.FacetGrid(data_train, hue="isDefault")
for i, fea in enumerate(features):
ax.set_title(fea)
if fea in category_fea + numerical_discrate_fea:
sns.countplot(fea, hue="isDefault", data=data_train, ax=axes[i])
else:
g = g.map(sns.distplot, fea, hist=False, rug=False, ax=axes[i])

# 转换
need_drop_features = []
df = data.drop(columns=need_drop_features)
df[need_transfer] = df[need_transfer].apply(transfer_func)
# or
df[need_map] = df[need_map].map(map_dict)
# 日期
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
data['date'] = data['date'].apply(lambda x: x - startdate).dt.days

# 相关关系
# 散点图
sns.pairplot(df)
correlation = df.corr(method='pearson')
## 热力图
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 15))
sns.heatmap(correlation,
ax=ax,
fmt='.1f',
xticklabels=correlation.columns,
yticklabels=correlation.columns)

## 另一种格式
fig, (ax) = plt.subplots(1, 1, figsize=(18, 18))
hm = sns.heatmap(correlation,
ax=ax,
cmap="bwr",
annot=True,
fmt='.1f',
linewidths=.05)

fig.subplots_adjust(top=0.94)
fig.suptitle('Attributes and their Correlation Heatmap',
fontsize=14,
fontweight='bold');

参考资料