2025-11-15 学习记录--Python-数据清洗与特征工程 流程
·
一、数据清洗与特征工程 流程 ⭐️
(一)、前言 🍭

数据清洗与特征工程 🦋:把原始的污染监测站日志(以“测项+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
)
【补充知识】🌈
- 1、
pandas教程:
(三)、为什么拿到数据集要这样处理? 🍭

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

更多推荐

所有评论(0)