引入
在上期的实践尝试阶段,我们依据鱼佬给出的baseline,使用了lightgbm完成了基本的模型训练,并跑通了代码。在进阶实践部分,将在原有Baseline基础上做进阶代码,进阶版本代码。具体地,我们可以尝试提取更多特征改善最终结果,这也是数据挖掘比赛中的主要优化方向,很多情况下决定着最终的成绩。
1.进阶建议
在新的一期教学中,主要给出了以下4种方案来提高成绩。主要是构建交叉特征、历史平移特征、差分特征、和窗口统计特征;每种特征具体说明如下:
(1)交叉特征:主要提取流量、上部温度设定、下部温度设定之间的关系;
(2)历史平移特征:通过历史平移获取上个阶段的信息;
(3)差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;
(4)窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。
2.代码实现和解读
python
# 交叉特征
for i in range(1,18):
train[f'流量{i}/上部温度设定{i}'] = train[f'流量{i}'] / train[f'上部温度设定{i}']
test[f'流量{i}/上部温度设定{i}'] = test[f'流量{i}'] / test[f'上部温度设定{i}']
train[f'流量{i}/下部温度设定{i}'] = train[f'流量{i}'] / train[f'下部温度设定{i}']
test[f'流量{i}/下部温度设定{i}'] = test[f'流量{i}'] / test[f'下部温度设定{i}']
train[f'上部温度设定{i}/下部温度设定{i}'] = train[f'上部温度设定{i}'] / train[f'下部温度设定{i}']
test[f'上部温度设定{i}/下部温度设定{i}'] = test[f'上部温度设定{i}'] / test[f'下部温度设定{i}']
这部分代码为数据集(训练集和测试集)添加交叉特征。交叉特征通常是原有特征的组合,以此来创造新的特征,提高模型的表现。该代码创建了以下特征:
流量i/上部温度设定i
: 表示流量i
除以上部温度设定i
的值。流量i/下部温度设定i
: 表示流量i
除以下部温度设定i
的值。上部温度设定i/下部温度设定i
: 表示上部温度设定i
除以下部温度设定i
的值。
python
# 历史平移
for i in range(1,18):
train[f'last1_流量{i}'] = train[f'流量{i}'].shift(1)
train[f'last1_上部温度设定{i}'] = train[f'上部温度设定{i}'].shift(1)
train[f'last1_下部温度设定{i}'] = train[f'下部温度设定{i}'].shift(1)
test[f'last1_流量{i}'] = test[f'流量{i}'].shift(1)
test[f'last1_上部温度设定{i}'] = test[f'上部温度设定{i}'].shift(1)
test[f'last1_下部温度设定{i}'] = test[f'下部温度设定{i}'].shift(1)
这部分代码通过.shift(1)
实现特征的历史平移。这在时间序列数据中很常见,用于获得之前时间点的值。
例如,last1_流量i
表示流量i
在上一个时间点的值。
python
# 差分特征
for i in range(1,18):
train[f'last1_diff_流量{i}'] = train[f'流量{i}'].diff(1)
train[f'last1_diff_上部温度设定{i}'] = train[f'上部温度设定{i}'].diff(1)
train[f'last1_diff_下部温度设定{i}'] = train[f'下部温度设定{i}'].diff(1)
test[f'last1_diff_流量{i}'] = test[f'流量{i}'].diff(1)
test[f'last1_diff_上部温度设定{i}'] = test[f'上部温度设定{i}'].diff(1)
test[f'last1_diff_下部温度设定{i}'] = test[f'下部温度设定{i}'].diff(1)
差分是时间序列分析中常见的操作,用于获得序列的一阶差异。.diff(1)
实现了这个操作。
例如,last1_diff_流量i
表示流量i
与上一个时间点流量i
的差值。
python
# 窗口统计
for i in range(1,18):
train[f'win3_mean_流量{i}'] = (train[f'流量{i}'].shift(1) + train[f'流量{i}'].shift(2) + train[f'流量{i}'].shift(3)) / 3
train[f'win3_mean_上部温度设定{i}'] = (train[f'上部温度设定{i}'].shift(1) + train[f'上部温度设定{i}'].shift(2) + train[f'上部温度设定{i}'].shift(3)) / 3
train[f'win3_mean_下部温度设定{i}'] = (train[f'下部温度设定{i}'].shift(1) + train[f'下部温度设定{i}'].shift(2) + train[f'下部温度设定{i}'].shift(3)) / 3
test[f'win3_mean_流量{i}'] = (test[f'流量{i}'].shift(1) + test[f'流量{i}'].shift(2) + test[f'流量{i}'].shift(3)) / 3
test[f'win3_mean_上部温度设定{i}'] = (test[f'上部温度设定{i}'].shift(1) + test[f'上部温度设定{i}'].shift(2) + test[f'上部温度设定{i}'].shift(3)) / 3
test[f'win3_mean_下部温度设定{i}'] = (test[f'下部温度设定{i}'].shift(1) + test[f'下部温度设定{i}'].shift(2) + test[f'下部温度设定{i}'].shift(3)) / 3
这部分代码通过.shift()
函数来计算3个时间点的滑动平均值,这是时间序列分析中的另一个常见操作。
例如,win3_mean_流量i
表示流量i
的3个时间点(包括当前时间点之前的两个时间点和自身)的平均值。
3. 个人思考
事实上,baseline+4项进阶对我来说已经是大神般的操作了。我本就抱着学习的心态去拜读这些代码,又怎能对其进行进一步的修改和提升呢?
所以我决定将重心放到前期的数据清洗上
是的,你会发现大佬的模型和参数都已经很不错了,但是他直接拿源数据进行了训练。我们在数据预处理上还有优化空间。
4. 数据清洗
数据清洗是确保数据质量的关键步骤。在开始清洗之前,我们首先需要对数据进行探索性数据分析 (EDA) 以识别可能的问题。这通常包括以下几个步骤:
- 缺失值检查: 查看数据中是否存在缺失值,以及这些缺失值的分布。
- 异常值检查: 使用统计方法(例如 IQR、Z-Score)或数据可视化来识别可能的异常值。
- 数据分布检查: 检查每个特征的数据分布,以识别是否存在偏斜或其他不寻常的分布。
- 重复值检查: 确保数据中没有重复的行。
- 一致性检查: 确保数据中的相关字段是一致的。
4.1. 缺失值统计
python
#检查数据缺失值
missing_values = train_df.isnull().sum()
missing_values = missing_values[missing_values > 0] # Filtering out features with no missing values
missing_values_percentage = (missing_values / len(train_df)) * 100 # Calculate the percentage of missing values
missing_info = pd.DataFrame({
"Missing Values": missing_values,
"Percentage": missing_values_percentage
}).sort_values(by="Percentage", ascending=False)
missing_info
在官方提供的 train.csv
数据中没有缺失值。那我们不需要进行缺失值的填充或删除操作了。看来白忙活了一趟。
4.2. 异常值处理
我们可以使用箱线图或其他统计方法来检查每个特征的异常值。
由于数据集中的特征数量可能较多,我们可以选择一些关键特征,或者先查看流量和温度数据的描述性统计来识别可能的异常值。
我们可以先对数据进行描述性统计,以便更好地了解哪些特征可能有异常值。
一种常用的方法是查看每个特征的最小值、最大值、均值、中位数以及标准差。当最小值或最大值与均值和中位数差异很大时,我们可以怀疑存在异常值。
那么接下来我们首先为数据集中的特征生成描述性统计。
python
#存储描述性数据
descriptive_stats = train_df.describe().transpose()
#展示可能出现异常值的数据
potential_outliers = descriptive_stats[
((descriptive_stats['max'] - descriptive_stats['75%']) > 1.5 * (descriptive_stats['75%'] - descriptive_stats['25%'])) |
((descriptive_stats['25%'] - descriptive_stats['min']) > 1.5 * (descriptive_stats['75%'] - descriptive_stats['25%']))
]
potential_outliers
结果大致如下:
result
count mean std min 25% 50% \
流量1 26656.0 30.789741 8.749196 0.0 25.069000 32.563499
流量2 26656.0 31.157351 9.553690 0.0 24.684000 34.664001
流量3 26656.0 31.659505 9.724089 0.0 24.226999 32.250999
流量4 26656.0 27.460194 7.151646 0.0 22.273750 27.118000
流量5 26656.0 28.550216 7.496923 0.0 24.791000 28.461000
... ... ... ... ... ... ...
下部温度13 26656.0 851.670581 94.510100 348.0 829.000000 837.000000
下部温度14 26656.0 850.215186 101.237117 297.0 829.000000 837.000000
下部温度15 26656.0 848.684011 108.438835 246.0 829.000000 837.000000
下部温度16 26656.0 844.247074 114.867509 196.0 829.000000 837.000000
下部温度17 26656.0 763.231318 110.615706 145.0 750.000000 758.000000
75% max
流量1 35.558001 79.261002
流量2 38.547001 79.261002
流量3 39.324001 70.323998
流量4 31.002001 65.071999
流量5 32.597000 70.435997
... ... ...
下部温度13 931.000000 945.000000
下部温度14 931.000000 946.000000
下部温度15 931.000000 947.000000
下部温度16 931.000000 947.000000
下部温度17 850.000000 854.000000
[63 rows x 8 columns]
这样看来,以下特征可能会出现异常值:
- 进气流量:如
流量1
、流量2
等 - 加热棒上下部的设定温度和测量温度:如
下部温度13
、下部温度14
等
用箱线图可以很好地看出具体的问题:
python
import matplotlib.pyplot as plt
import seaborn as sns
# 选择目标
selected_features = ['流量1', '流量2', '流量3', '下部温度13', '下部温度14', '下部温度15']
# Plotting boxplots for the selected features
plt.figure(figsize=(15, 10))
for i, feature in enumerate(selected_features, 1):
plt.subplot(2, 3, i)
sns.boxplot(y=train_df[feature])
plt.title(f'Boxplot for {feature}')
plt.ylabel(feature)
plt.tight_layout()
plt.show()
看来是需要进行异常值处理的
python
import numpy as np
# 定义数据清洗函数
def handle_outliers(data, column):
# 计算特征值(上下四分位值和IQR)
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
# 计算上下限值
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 替换数据
data[column] = np.where(data[column] < lower_bound, lower_bound, data[column])
data[column] = np.where(data[column] > upper_bound, upper_bound, data[column])
return data
# 调用函数处理数据
for feature in selected_features:
train_df = handle_outliers(train_df, feature)
# 展示清洗后数据
plt.figure(figsize=(15, 10))
for i, (feature, eng_feature) in enumerate(zip(selected_features, selected_features_english), 1):
plt.subplot(2, 3, i)
sns.boxplot(y=train_df[feature])
plt.title(f'Boxplot for {eng_feature} (After Outlier Handling)')
plt.ylabel(eng_feature)
plt.tight_layout()
plt.show()
由此,异常值已经全部被平滑掉了。采用上下限替换的方式来"平滑"异常值对于树型模型(例如随机森林、决策树或梯度提升机)的影响较小,因为这些模型天然对异常值具有较好的鲁棒性。但对于其他模型(例如线性回归或神经网络),异常值可能会产生更大的影响。
总结
清洗是非常重要的一环,这样做之后的很多操作才有意义(比如差分等)。不过今天我的提交次数已经用完了(狗头)。到时候再来分享成绩吧~