数据探索与可视化(EDA):加载C-MAPSS数据集

做剩余寿命预测,第一步不是调模型,而是先看看数据长什么样。我见过不少新手,拿到数据集就急着跑LSTM,结果折腾半天效果奇差——回头一看,数据本身就有问题。所以,咱们先沉下心来,把C-MAPSS数据集摸透。

加载C-MAPSS数据集

C-MAPSS数据集来自NASA,模拟了航空发动机从健康到失效的全过程。它包含四个子集:FD001、FD002、FD003、FD004。每个子集又分训练集和测试集。

我个人习惯用Pandas直接读取。文件是文本格式,没有表头,需要手动指定列名。

import pandas as pd
import numpy as np

# 列名定义
columns = ['engine_id', 'cycle', 'op_setting_1', 'op_setting_2', 'op_setting_3'] + \
          [f'sensor_{i}' for i in range(1, 22)]

# 加载训练集
train_df = pd.read_csv('train_FD001.txt', sep=' ', header=None, names=columns)

# 去掉末尾的空列(文件里有多余空格)
train_df = train_df.dropna(axis=1, how='all')

print(train_df.head())
print(train_df.shape)

嗯,这里要注意:文件里每行末尾有多余的空格,读进来会多出空列。用dropna(axis=1, how='all')直接干掉就行。我在项目中遇到过类似情况,当时没注意,结果后面算特征时全乱了。

数据清洗与缺失值处理

加载完数据,第一件事就是检查缺失值。C-MAPSS数据集本身比较干净,但工业现场的数据可没这么友好。

# 检查缺失值
missing = train_df.isnull().sum()
print(missing[missing > 0])

如果发现缺失值,怎么处理?我一般分情况讨论:

  • 传感器数据缺失:用前向填充(ffill)或线性插值。发动机运行是连续的,前一时刻的值很有参考价值。
  • 运行设置缺失:这个比较棘手。我曾经遇到过一个案例,某台发动机的运行设置全为空,后来发现是传感器故障。这种直接剔除该发动机的数据。
我的小技巧:对于传感器数据,先画个时序图看看缺失模式。如果是随机缺失,插值就行;如果是整段缺失,那可能是停机维护,需要单独标记。
# 前向填充示例
train_df = train_df.fillna(method='ffill')

# 或者线性插值
train_df = train_df.interpolate(method='linear', limit_direction='forward')

传感器信号可视化

数据洗干净了,接下来就是看信号。21个传感器,不是每个都有用。我习惯先挑几个典型的画出来看看。

import matplotlib.pyplot as plt

# 选一台发动机
engine_1 = train_df[train_df['engine_id'] == 1]

# 绘制几个关键传感器
sensors_to_plot = ['sensor_2', 'sensor_3', 'sensor_4', 'sensor_7', 'sensor_11', 'sensor_15']

plt.figure(figsize=(12, 8))
for i, sensor in enumerate(sensors_to_plot, 1):
    plt.subplot(2, 3, i)
    plt.plot(engine_1['cycle'], engine_1[sensor])
    plt.title(sensor)
    plt.xlabel('Cycle')
    plt.ylabel('Value')
plt.tight_layout()
plt.show()

你想想看,这些传感器信号有什么规律?有的传感器(比如sensor_2)随着运行周期增加,趋势很明显——要么上升,要么下降。这种就是好特征。有的传感器(比如sensor_3)基本是一条直线,没什么变化,那它对预测就没啥贡献。

关键发现:在FD001数据集中,sensor_2、sensor_3、sensor_4、sensor_7、sensor_11、sensor_12、sensor_15这7个传感器表现出明显的退化趋势。其他传感器要么噪声太大,要么变化不明显,后续建模时可以优先考虑这7个。

运行周期分布分析

接下来看看每台发动机能跑多久。这个分布直接决定了我们的预测任务难度。

# 计算每台发动机的最大周期(即寿命)
life_span = train_df.groupby('engine_id')['cycle'].max().reset_index()
life_span.columns = ['engine_id', 'max_cycle']

# 绘制直方图
plt.figure(figsize=(10, 6))
plt.hist(life_span['max_cycle'], bins=30, edgecolor='black', alpha=0.7)
plt.xlabel('Max Cycle (Life Span)')
plt.ylabel('Number of Engines')
plt.title('Distribution of Engine Life Spans in FD001')
plt.grid(True, alpha=0.3)
plt.show()

# 统计信息
print(life_span['max_cycle'].describe())

结果很有意思:FD001中发动机寿命最短的只有几十个周期,最长的能到300多个周期。分布大致呈右偏态,大部分发动机集中在150-250周期之间。

统计量
发动机数量 100
最短寿命 128 周期
最长寿命 362 周期
平均寿命 206 周期
标准差 46 周期
避坑指南:我曾经犯过一个错误——直接用原始周期数作为标签训练模型。结果模型对短寿命发动机预测极差。后来才意识到,不同发动机的退化速度不同,需要做归一化处理。具体做法是把每个发动机的周期映射到[0,1]区间,0表示健康,1表示失效。

知识体系总览

说了这么多,咱们用一张图把EDA的核心逻辑串起来:

C-MAPSS 数据探索与可视化流程 加载数据集 读取文本文件 数据清洗 缺失值处理 信号可视化 传感器趋势分析 分布分析 寿命周期分布 特征筛选 选择退化趋势传感器 标签构建 剩余寿命计算 输出:清洗后的数据集 + 可视化报告 + 特征重要性排序

这张图把EDA的流程串起来了。从加载数据开始,到清洗、可视化、分布分析,最后筛选出有效特征并构建标签。每一步都环环相扣。

说白了,EDA不是走形式。它是帮你建立对数据的直觉。我做了这么多年工业AI,发现一个规律:EDA做得越扎实,后面建模就越顺。那些上来就调模型的人,往往会在数据上栽跟头。

好了,数据探索这部分就到这儿。数据准备好了,下一步就是构建训练样本和标签了。

专注资料整理