一、数据清洗与特征工程 流程 ⭐️

(一)、前言 🍭

在这里插入图片描述

数据清洗与特征工程 🦋:把原始的污染监测站日志(以“测项+24小时一行”形式)转换成 可以喂给机器学习 / 深度学习模型的标准时间序列数据

数据处理(data/):

  • 飞桨AI Studio 下载数据集:https://aistudio.baidu.com/datasetdetail/71270 【将下载的train.csv放到data/】
  • 预处理要求:
    • 处理缺失值(如前向填充、插值)
      • 因为模型不能处理:NaN、字符串、错误的数值
    • 特征归一化(Min-Max或StandardScaler)
    • 构建滑动窗口(如输入序列长度=24小时,预测未来1小时PM2.5)
    • 划分训练集、验证集、测试集(按时间顺序划分,避免未来信息泄露)

(二)、代码 🍭

在这里插入图片描述

# 数据清洗与特征工程:把原始的污染监测站日志(以“测项+24小时一行”形式)转换成可以喂给机器学习 / 深度学习模型的标准时间序列数据。
'''
数据处理(`data/`)
- 从 **飞桨AI Studio** 下载数据集:https://aistudio.baidu.com/datasetdetail/71270 【将下载的train.csv放到data/】
- 预处理要求:
  - 处理缺失值(如前向填充、插值)
    - 因为模型不能处理:NaN、字符串、错误的数值
  - 特征归一化(Min-Max或StandardScaler)
  - 构建滑动窗口(如输入序列长度=24小时,预测未来1小时PM2.5)
  - 划分训练集、验证集、测试集(**按时间顺序划分**,避免未来信息泄露)
'''

# ----------------------- ①、检测 .csv 文件的编码 -----------------------
import chardet

# 以二进制方式打开 CSV 文件,读取前 200000 字节用于编码推断
with open("../data/train.csv", "rb") as f:
    raw_data = f.read(200000)
    result = chardet.detect(raw_data) # 使用 chardet 检测编码,例如 Big5、UTF-8 等

print(result) # 打印检测到的编码结果(包括 encoding、置信度、语言),例如:{'encoding': 'Big5', 'confidence': 0.99, 'language': 'Chinese'}

# ----------------------- ②、读取 CSV + 处理缺失值(如前向填充、插值)-----------------------
import pandas as pd # pandas教程中文版见链接:https://pandas.github.net.cn/

# 用检测出的 'Big5' 编码读取 data/train.csv 文件,并将其内容存入 DataFrame(df)对象
# 🍍encoding=“前面 检测 .csv 文件的编码”
df = pd.read_csv("../data/train.csv", encoding='Big5')

# 统计每一列的缺失值(NaN)数量,用于检查数据质量
print(df.isna().sum()) # 无缺失值,所以无需处理缺失值

# ----------------------- ③、特征归一化(Min-Max或StandardScaler)—— 让不同数值的量纲一致 -----------------------
from sklearn.preprocessing import MinMaxScaler

# 指定需要进行归一化的列名:仅保留数值列(24小时列名)用于归一化
features_cols = [str(i) for i in range(24)] # ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
'''
PM2.5 作业标准处理方式是:
    "NR"(No Rain)视为 0(无降雨)
所以需要在归一化前:
    df[features_cols] = df[features_cols].replace("NR", 0) # 原始数据中,降雨量 "NR" 表示 No Rain,应视为 0
    df[features_cols] = df[features_cols].replace("", 0) # 空字符串 "" 也视作 0(保险起见,防止异常)
    df[features_cols] = df[features_cols].astype(float) # 统一转为 float(因为 MinMaxScaler 不能处理字符串类型)
'''
# 处理 NR -> 0
df[features_cols] = df[features_cols].replace('NR',0)
# 保险起见:空字符串 -> 0(稳定性)
df[features_cols] = df[features_cols].replace("", 0)
# 保险起见:转为 float【因为 MinMaxScaler / StandardScaler 都只能处理纯数值型数据,而你的 0~23 小时列虽然看起来像数字,但其实是 字符串类型(object)。⚠ Sklearn 无法对 object 类型进行数学计算】
df[features_cols] = df[features_cols].astype(float)
'''归一化'''
# 创建一个 Min-Max 归一化器,将数据缩放到 0~1 区间
scaler = MinMaxScaler()
# 对指定的特征列执行 Min-Max 归一化【!!!必须先 fit_transform,再保存 scaler (重要)】
df[features_cols] = scaler.fit_transform(df[features_cols])
'''保存 scaler(用于以后预测时反归一化)'''
# 保存 scaler(用于以后预测时反归一化),否则预测时没办法把 LSTM 输出的归一化值反归一化回真实 PM2.5。【先 fit_transform,再保存 scaler(此时 scaler 内部已经记录 min、max)】
import joblib
joblib.dump(scaler, "../data/pm25_scaler.pkl")

# 打印数据的前 5 行,用于快速查看数据结构和内容
print(df.head())

# ----------------------- ④、构建滑动窗口(如输入序列长度=24小时,预测未来1小时PM2.5)-----------------------
'''
⚠ 注意:这个数据集是一列一列的气象测站数据(每一行是一个“测项”的 24 小时时序),并不是典型的“每小时一行”的表格,需要先整理成每小时的 PM2.5 序列,再构建窗口。
'''
# 第一步、从原始 DataFrame 中提取 PM2.5 时间序列
# 仅筛选 PM2.5 行
pm25_df = df[df['測項'] == 'PM2.5']
print(pm25_df)
# 将 0~23 小时展开成连续序列;pm25_df.iloc[:, 3:] 取第 3 列之后的 0~23列
pm25_values = pm25_df.iloc[:, 3:].values.reshape(-1 ) # [0.08891929 0.12801999 0.12075682 ... 0.06887052 0.08685969 0.09786769]
print(pm25_values)
print("PM2.5 序列长度:", len(pm25_values)) # PM2.5 序列长度: 5760
# 第二步、构建滑动窗口(window_size=24, predict next 1)【通用、可用于所有模型】:用过去 24 小时预测未来 1 小时
import numpy as np

def create_dataset(data, window_size=24):
    X, y = [], []
    for i in range(len(data) - window_size):
        X.append(data[i : i + window_size]) # 输入序列(过去24 小时)
        y.append(data[i + window_size]) # 预测下一个小时(标签)
    return np.array(X), np.array(y)
# 第三步:生成 X, y
X, y = create_dataset(pm25_values) # 得到所有样本
print("X shape:", X.shape) # X shape: (5736, 24) -> 5736 个样本,每个样本 24 个小时(特征)
print("y shape:", y.shape) # y shape: (5736,) -> 每个样本对应未来 1 小时 PM2.5(标签)

# ----------------------- ⑤、划分训练集、验证集、测试集(**按时间顺序划分**,避免未来信息泄露)-----------------------
'''按比例划分'''
# 设置训练集占总数据的比例为 0.7(70% 训练)
train_ratio = 0.7
# 设置验证集占总数据的比例为 0.15(15% 验证)
val_ratio = 0.15
# 获取数据集 X 的总样本数量 5736
n = len(X)
# 计算训练集结束的位置索引,即前 70% 的数据
train_end = int(n * train_ratio) # 4015
# 计算验证集结束的位置索引,即前 (训练集 + 验证集) 85% 的数据
val_end = int(n * (train_ratio + val_ratio)) # 4015 + 860 = 4875
'''切片划分(时间序列必须按顺序)'''
# 切片取前 train_end 个样本作为训练集
X_train, y_train = X[:train_end], y[:train_end]
# 切片取 train_end 到 val_end 的样本作为验证集
X_val, y_val = X[train_end:val_end], y[train_end:val_end]
# 切片取 val_end 之后的样本作为测试集
X_test, y_test = X[val_end:], y[val_end:]
'''LSTM 输入格式必须为 (batch, seq_len, feature_dim)'''
# LSTM 输入格式 (batch, 时间步, 特征数) -> (batch, 24, 1)
X_train = X_train.reshape(-1, 24, 1) # -1:由 NumPy 自动计算 batch_size(样本数),等价于 n_samples;24:序列长度(window size),你用过去 24 小时作为一个样本;1:特征维度(每个时间步只有一个特征:PM2.5)
X_val   = X_val.reshape(-1, 24, 1)
X_test  = X_test.reshape(-1, 24, 1)
# 同时把总的 X 也 reshape(如果后面要整体保存)
X = X.reshape(X.shape[0], X.shape[1], 1)
'''打印检查样本量'''
# 打印训练集、验证集和测试集的样本数量,方便检查是否分割正确
print(len(X_train), len(X_val), len(X_test)) # 4015 860 861Ω

# ----------------------- ⑥、保存所有数据到 dataset.npz -----------------------
np.savez("../data/dataset.npz",        # np.savez(".npz",):将多个 NumPy 数组一次性打包保存到一个 .npz 文件中。【加载方法示例:data = np.load("../data/dataset.npz") X_train = data["X_train"]】
    X=X, y=y,                          # 保存完整的特征序列 X(全部样本)以及标签 y
    X_train=X_train, y_train=y_train,  # 保存训练集的 X 与 y
    X_val=X_val, y_val=y_val,          # 保存验证集的 X 与 y
    X_test=X_test, y_test=y_test       # 保存测试集的 X 与 y
)

补充知识】🌈

(三)、为什么拿到数据集要这样处理? 🍭

在这里插入图片描述

我写的整个数据清洗 + 特征工程流程,其实是在做:👇🏻

  • 把原始的污染监测站日志(以“测项+24小时一行”形式)转换成可以喂给机器学习 / 深度学习模型的标准时间序列数据
① 检测 CSV 编码 — 因为原始文件不是 UTF-8
  • 飞桨 AI Studio 下载的气象监测数据常常是 Big5(繁体编码)。
  • 如果不检测编码直接读:👇🏻
    • 乱码 🧵
    • 列名可能变成奇怪字符 👹
    • 数值可能读不出来 🍉
  • 因此第一步必须判断编码,然后正确读取。😊
② 处理缺失值 — 真实监测站数据一定不干净
  • 气象站设备经常出现:🧵
    • 数据缺失
    • NR(No Rain)这种特例
    • 某些小时为空字符串
  • 这些都不能直接喂给模型。
  • 为什么必须处理❓
  • 因为模型不能处理:👇🏻
    • NaN
    • 字符串
    • 错误的数值
  • 如果不处理:💥
    • MinMaxScaler 会报错
    • 神经网络无法训练
    • 预测结果会偏离(特征分布变形)
③ 特征归一化 — 让不同数值的量纲一致
  • 不同维度的数值范围相差太大,模型训练时:👇🏻
    • 梯度会不稳定 🧵
    • 某些特征被“超大值”主导 👁
    • 最终收敛慢、效果差 💥
  • 因此用 Min-MaxStandardScaler 把值缩放到:👇🏻
    • 0~1
    • 均值 0、方差 1
  • 让模型“更容易学习”
    在这里插入图片描述
④ 为何要构建滑动窗口?(这是最关键的一步!)
  • 原始数据格式是:
    • 一行 = 某气象站某一天某项的 24 个小时
  • 这根本不是深度学习时间序列模型需要的格式。
  • 时间序列模型需要:🌈
    • 过去 24 小时 → 预测下一小时
  • 这就要把所有天的 24 小时 拼成一个连续的长序列
    • 24 小时(第 1 天) + 24 小时(第 2 天) + 24 小时(第 3 天) + …
  • 然后用滑动窗口方式切出训练样本:🐰
    • [小时 1〜24] → 预测 小时25
    • [小时 2〜25] → 预测 小时26
  • 如果不这样构建:💥
    • 神经网络根本不知道“时间关系”
    • 没有序列模式可学
    • 模型效果会非常差
⑤ 为什么要按时间顺序切分训练/验证/测试集?(避免未来信息泄露)
  • 时间序列预测的最大禁忌就是:
    • ❌ 不能把“未来的数据”用于训练,去预测“过去的数据”。
  • 如果随机打乱:💥
    • 训练集里会包含未来日期
    • 模型就相当于偷看考试答案
    • 测试集性能虚高
    • 实际部署完全崩掉
  • 所以必须这样分:👇🏻
    在这里插入图片描述
    • 第 1 段(最早)训练集
    • 第 2 段验证集
    • 第 3 段(最新)测试集
  • 这才是真实预测场景。
总结对比
处理步骤 目的 不做的后果
检测编码 正确读取文件 数据乱码,列名错乱
处理缺失值 / NR 数据变成纯数值 归一化和模型无法运行
归一化 稳定训练,提高模型性能 收敛慢、模型效果差
构建时间序列滑动窗口 让模型学习“时间依赖关系” 模型学不到趋势,只能瞎蒙
时间顺序划分 避免未来信息泄露 测试集成绩虚高,实际效果崩掉

☀️ 一句话总结

  • 所有步骤的目的只有一个:👇🏻
    • 把原始混乱的监测站表格 → 转换成适合机器学习的干净的稳定的按时间顺序标准时间序列数据

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐