天池题目:工业蒸汽预测(一)- 数据探索

1. 题目介绍

火力发电的基本原理是:燃料在燃烧时加热水生成蒸汽,蒸汽压力推动汽轮机旋转,然后汽轮机带动发电机旋转,产生电能。在这一系列的能量转化中,影响发电效率的核心是锅炉的燃烧效率,即燃料燃烧加热水产生高温高压蒸汽。锅炉的燃烧效率的影响因素很多,包括锅炉的可调参数,如燃烧给量,一二次风,引风,返料风,给水水量;以及锅炉的工况,比如锅炉床温、床压,炉膛温度、压力,过热器的温度等。

数据为:经脱敏后的锅炉传感器采集的数据(采集频率是分钟级别),根据锅炉的工况,预测产生的蒸汽量。

 

题目地址:

https://tianchi.aliyun.com/competition/entrance/231693/introduction

 

2. 数据探索

2.1. 初步探索

先简单查看一下数据:

import pandas as pd
import s3fs
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats

plt.style.use('seaborn')
%matplotlib inline

train_raw = pd.read_csv(train_data_uri, sep='\t', encoding='utf-8')
test_raw = pd.read_csv(test_data_uri, sep='\t', encoding='utf-8')

train_raw.head()

 

 

train_raw.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2888 entries, 0 to 2887
Data columns (total 39 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   V0      2888 non-null   float64
 1   V1      2888 non-null   float64
 2   V2      2888 non-null   float64
 …
 37  V37     2888 non-null   float64
 38  target  2888 non-null   float64
dtypes: float64(39)
memory usage: 880.1 KB

从训练集 info 信息我们可以知道,在训练集中:

  1. 一共有2888 个样本, 38个字段(V0 - V37) ,1个 target
  2. 所有特征均为连续型特征
  3. Label(也就是target)为连续型,所以我们需要回归函数进行预测
  4. 所有特征均没有空置

 

测试集 info():

test_raw.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1925 entries, 0 to 1924
Data columns (total 38 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   V0      1925 non-null   float64
 1   V1      1925 non-null   float64
 2   V2      1925 non-null   float64
 …
 36  V36     1925 non-null   float64
 37  V37     1925 non-null   float64
dtypes: float64(38)
memory usage: 571.6 KB

 

从测试集info() 我们可以了解到,在测试集中:

  1. 一共有1925个样本,38个字段(V0 - V37)
  2. 所有特征均为连续型
  3. 没有缺失值

 

若是进一步对df 做 describe(),则会有 39 个字段的describe数据,从观察数据的角度来看,比较复杂,所以下一步我们对数据进行可视化。

 

2.2. 数据可视化

可视化的主要目标为:探索数据特征以及数据分布

 

2.2.3. 盒图

盒图是一种流行的分布的直观表示。盒图体现了五数概括:

  • 盒的端点一般在四分位数上,使得盒的长度是四分位数极差IQR
  • 中位数用盒内的线标记
  • 盒外的两条线(称作胡须)延伸到最小(Minimum)和最大(Maximum)观测值

当处理数量适中的观测值时,盒图中对离群点的表示为:仅当最高和最低观测值超过四分位数不到1.5 × IQR 时,胡须扩展到它们。否则胡须在出现在四分位数1.5 × IQR 之内的最极端的观测值处终止,剩下的情况个别地绘出。

先看看第一个特征的盒图:

可以看到有较多的离群点。

继续对所有特征画出盒图:

columns = train_raw.columns[:-1]

fig = plt.figure(figsize=(100, 80), dpi=75)
for i in range(len(columns)):
    plt.subplot(7, 6, i+1)
    sns.boxplot(train_raw[columns[i]], orient='v', width=0.5)
    plt.ylabel(columns[i], fontsize=36)
plt.show()

 

2.2.4. 使用模型预测识别离群点

下面是使用岭回归来预测离群点:

from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error

# train liner Ridge model
X_train = train_raw.iloc[:, 0:-1]
y_train = train_raw.iloc[:, -1]

model = Ridge()
model.fit(X_train, y_train)

y_pred = model.predict(X_train)
y_pred = pd.Series(y_pred, index=y_train.index)

# calculate residual
residual = y_pred - y_train
resid_mean = residual.mean()
resid_std = residual.std()

# calculate z score
sigma = 3
z = (residual - resid_mean) / resid_std

# get outliers
outliers = y_train[abs(z) > sigma].index

# plot outliers
plt.figure(figsize=(15, 5))

plt.subplot(131)
plt.plot(y_train, y_pred, '.')
plt.plot(y_train.loc[outliers], y_pred.loc[outliners], 'ro')
plt.xlabel('y_train')
plt.ylabel('y_pred')
plt.legend(['Accepted', 'Outlier'])

plt.subplot(132)
plt.plot(y_train, y_pred-y_train, '.')
plt.plot(y_train[outliers], y_train.loc[outliers] - y_pred.loc[outliers], 'ro')
plt.xlabel('y_train')
plt.ylabel('y_pred - y_train')
plt.legend(['Accepted', 'Outlier'])

ax3 = plt.subplot(133)
z.plot.hist(bins=50, ax=ax3)
z.loc[outliers].plot.hist(bins=50, ax=ax3, color='red')
plt.legend(['Accepted', 'Outlier'])
plt.xlabel('z')

 

2.2.5. 直方图与Q-Q图

Q-Q图的定义:The quantitle-quantile(q-q) plot is a graphical technique for determining if two data sets come from populations with a common distribution.

也就是指数据的分位数和正态分布的分位数对比参照的图,如果数据符合正态分布,则所有的点都会落在直线上。

它主要是直观的表示观测与预测值之间的差异。一般我们所取得数量性状数据都为正态分布数据。预测的线是一条从原点出发的45度角的虚线,事假观测值是实心点。

偏离线越大,则两个数据集来自具有不同分布的群体的结论的证据就越大。

首先,绘制特征V0的直方图查看其在训练集中的统计分布,并绘制Q-Q图查看V0的分布是否近似于正态分布:

plt.figure(figsize=(10, 5))

ax1 = plt.subplot(121)
sns.distplot(train_raw['V0'], fit=stats.norm)

ax2 = plt.subplot(122)
res = stats.probplot(train_raw['V0'], plot=plt)

 

可以看到训练集中V0 特征并非为正态分布。

接下来我们绘制所有特征的Q-Q图:

import warnings

warnings.filterwarnings("ignore")

# 38 * 2 = 76 = 4 * 19
plt.figure(figsize=(80, 190))

columns = train_raw.columns.tolist()[:-1]

# subplot position
ax_index = 1

for i in range(len(columns)):
    ax = plt.subplot(19, 4, ax_index)
    sns.distplot(train_raw[columns[i]], fit=stats.norm)
    ax_index += 1
    
    ax = plt.subplot(19, 4, ax_index)
    res = stats.probplot(train_raw[columns[i]], plot=plt)
    ax_index += 1
    
#plt.tight_layout()

 

部分结果如下:

可以看到其中有的特征符合正态分布,但大部分并不符合,数据并不跟随对角线分布。对此,后续可以使用数据变换对其进行处理。

 

2.2.6. KDE分布图

KDE(Kernel Density Estimation,核密度估计)可以理解为是对直方图的加窗平滑。它在概率论中用来估计未知的概率密度函数,属于非参数检验方法之一。通过核密度估计图可以比较直观的看出数据样本本身的分布特征。

通过绘制KDE图,可以查看并对比训练集和测试集中特征变量的分布情况,发现两个数据集中分布不一致的特征变量。

先对同一特征V0,分别在训练集与测试集中的分布情况,并观察分布是否一致:

plt.figure(figsize=(10, 8))
ax = sns.kdeplot(train_raw['V0'], color="Red", shade=True)
ax = sns.kdeplot(test_raw['V0'], color="Blue", shade=True)
ax.set_xlabel("V0")
ax.set_ylabel("Frequency")
ax.legend(['train', 'test'])

 

可以看到 V0 在两个数据集中的分布基本一致。

然后对所有特征画出训练集与测试集中的KDE分布:

# plot all features' kde plots
# 38 < 4*10
columns = train_raw.columns.tolist()[:-1]

plt.figure(figsize=(40, 100))
ax_index = 1

for i in range(len(columns)):
    ax = plt.subplot(10, 4, ax_index)
    ax = sns.kdeplot(train_raw[columns[i]], color="Red", shade=True)
    ax = sns.kdeplot(test_raw[columns[i]], color="Blue", shade=True)
    ax.set_xlabel(columns[i])
    ax.set_ylabel("Frequency")
    ax.legend(['train', 'test'])
ax_index += 1

 

可以看到大部分特征的分布在训练集与测试集中基本一致,但仍有几个特征的分布在两个数据集中不一致(主要为V5、V9、V11、V17、V22、V28),这样会导致模型的泛化能力变差,需要删除此类特征。

2.2.7. 线性回归关系图

线性回归关系图主要用于分析变量之间的线性回归关系。

首先看V0与target 之间的线性回归关系(sns.regplot 和 sns.distplot 可以同时用,同时查看特征分布以及特征和变量关系):

plt.figure(figsize=(10, 8))

ax = plt.subplot(121)
sns.regplot(x='V0', y='target', data=train_raw, ax=ax, 
            scatter_kws={'marker':'.', 's':4, 'alpha':0.3},
            line_kws={'color':'g'})
plt.xlabel('V0')
plt.ylabel('target')

ax = plt.subplot(122)
sns.distplot(train_raw['V0'].dropna())
plt.xlabel('V0')

plt.show()

 

从图像结果来看,V0 与 target 存在一定程度的线性关系。

然后查看所有特征变量与target 变量的线性回归关系:

fcols = 6
frows = len(columns)

plt.figure(figsize=(5*fcols, 4*frows), dpi=150)
ax_index = 1

for i in range(len(columns)):
    ax = plt.subplot(19, 4, ax_index)
    sns.regplot(x=columns[i], y='target', data=train_raw, ax=ax,
                scatter_kws={'marker':'.', 's':3, 'alpha':0.3},
                line_kws={'color':'g'})
    ax.set_xlabel(columns[i])
    ax.set_ylabel('target')
    ax_index += 1

    ax = plt.subplot(19, 4, ax_index)
    sns.distplot(train_raw[columns[i]].dropna())
    ax_index += 1

plt.show()

部分结果如下:

通过查看可视化的结果,我们可以明显看到一些特征与target 完全没有线性关系(例如V9、V17、V18、V22、V23、V24、V25、V28 等等……)

 

2.2.8. 特征变量的相关性

特征变量的相关性主要是通过计算协方差矩阵获取,首先我们删除训练集与测试集中分布不一致的特征变量(V5、V9、V11、V17、V21、V22、V23、V28):

为了方便查看,我们可以使用热力图显示结果:

plt.figure(figsize=(20, 16))
sns.heatmap(train_corr, square=True, annot=True)

 

从图中我们可以清晰地看到各个特征与 target 的相关系数程度。

有了相关系数后,继而我们可以通过相关系数来选择特征。

首先寻找K个与target变量最相关的特征:

# find top K most relevant features
K = 10
cols = train_corr.nlargest(10, 'target')['target'].index

plt.figure(figsize=(10, 8))
sns.heatmap(train_raw[cols].corr(), annot=True, square=True)

 

找到其中与target 变量相关性大于 0.5 的特征:

threshold = 0.5

corrmat = train_raw.corr()
most_relevants = corrmat[abs(corrmat['target']) > threshold].index

plt.figure(figsize=(10, 8))
sns.heatmap(train_raw[most_relevants].corr(), square=True, annot=True)

 

相关性选择主要用于判断线性相关,若是target 变量存在更复杂的函数形式的影响,则建议使用树模型的特征重要性去选择。

 

Box-Cox 变换

由于线性回归是基于正态分布的,因此在进行统计分析时,需要将数据转换使其符合正态分布。Box-Cox变换是统计建模中常用的一种数据转换方法,通过自动计算最优的 λ,使得变换后的样本(及总体)正态性最好。

在做Box-Cox变换之前,需要对数据做归一化处理。在归一化时,对数据进行合并操作可以使得训练数据与测试数据一致:

# normalization
from sklearn.preprocessing import MinMaxScaler
min_max_scaler = MinMaxScaler()

columns = data_all.columns

normalized_data_all = min_max_scaler.fit_transform(data_all)
normalized_data_all = pd.DataFrame(normalized_data_all, index=data_all.index, columns=columns)
normalized_data_all.describe()

 

对特征做了归一化后,继续做Box-Cox 变换,显示特征变量与target变量的线性关系:

# restore normalized training & test set 
train_size = train_raw.shape[0]

processed_train = normalized_data_all.iloc[:train_size]
processed_test = normalized_data_all.iloc[train_size:]

processed_train = pd.concat([processed_train, train_raw['target']], axis=1)

# box-cox transform and plot def scale_minmax(col): return (col - col.min()) / (col.max() - col.min()) columns = processed_train.columns[:4] plt.figure(figsize=(12, 100)) ax_index = 1 for i in range(len(columns)): # original distribution plot ax = plt.subplot(36, 3, ax_index) sns.distplot(processed_train[columns[i]], fit=stats.norm) ax.set_title('Original: ' + columns[i]) ax_index += 1 # prob plot ax = plt.subplot(36, 3, ax_index) stats.probplot(processed_train[columns[i]], plot=ax) ax.set_title('skew={:.4f}'.format(stats.skew(processed_train[columns[i]]))) ax_index += 1 # reg plot ax = plt.subplot(36, 3, ax_index) ax.plot(processed_train[columns[i]], processed_train['target'], '.', alpha=0.5) ax.set_title('corr={:.4f}'.format( np.corrcoef(processed_train[columns[i]], processed_train['target'])[0][1] )) ax_index += 1 # box-cox transformed ax = plt.subplot(36, 3, ax_index) trans_var, lambda_var = stats.boxcox(processed_train[columns[i]].dropna() + 1) trans_var = scale_minmax(trans_var) # plot transformed sns.distplot(trans_var, fit=stats.norm) ax.set_title('Transformed: ' + columns[i]) ax_index += 1 # prob plot ax = plt.subplot(36, 3, ax_index) stats.probplot(trans_var, plot=ax) ax.set_title('skew={:.4f}'.format(stats.skew(trans_var))) ax_index += 1 # reg plot ax = plt.subplot(36, 3, ax_index) ax.plot(trans_var, processed_train['target'], '.', alpha=0.5) ax.set_title('corr={:.4f}'.format( np.corrcoef(trans_var, processed_train['target'])[0][1])) ax_index += 1 plt.tight_layout()

 

从结果图可以看到,在执行了box-cox 变换后,变量分布更接近正态分布,且与target字段的线性关系更明显。

posted @ 2021-01-05 22:54  ZacksTang  阅读(1255)  评论(0编辑  收藏  举报