资讯详情

电力AI大赛数据挖掘管道搭建:从数据清洗到特征工程全流程指南

📅 2026/10/22 4:11:27 | 华诺云谱 👁 阅读
电力AI大赛数据挖掘管道搭建:从数据清洗到特征工程全流程指南
简介基于大航杯“智造扬中”电力AI大赛的完整数据挖掘管道搭建示例是一份面向大数据、人工智能相关专业学生及竞赛选手的高分实战资料答辩评审分达95分。资源围绕电力负荷预测场景完整覆盖数据清洗、特征提取与组合、样本划分、模型训练及结果预测并细致演示缺失值、异常值处理及天气、节假日等外部因素影响。压缩包共三十二个文件以Jupyter Notebook脚本、Python工具脚本、CSV数据集、TXT说明和Markdown文档为主整体仅3.72MB结构清晰便于对照学习。Notebook脚本记录数据可视化、特征工程与调参思路Python脚本负责天气和节假日数据爬取及预处理CSV包含赛题数据说明文档给出项目背景与运行结果。除完整源码外还附有项目授权码所有代码均测试运行成功可直接用于毕设、课设或比赛方案复现也便于按需扩展目前已有45人学习下载是快速上手电力AI数据挖掘管道的轻量完整参考。1. 电力AI大赛的数据挖掘管道先跑通“出分闭环”再谈模型提到电力AI大赛很多人打开那份《数据挖掘管道搭建示例详细文档全部资料源码》压缩包后第一反应是“资料真多”然后被困在数据采集脚本、说明文档和一堆没跑通的依赖里。作为一线工程师我带过的参赛队伍里有一半都栽在同一个认知偏差上把竞赛当模型比拼却忽略了数据挖掘管道本身才是决定结果上限的骨架。数据挖掘管道不是几个脚本的拼盘而是从原始电力负荷、工况数据一路走到可提交结果的组织方式。本文要讲的就是这套管道的搭建逻辑数据层怎么清洗、特征层怎么构造、验证层怎么不翻车以及最后如何避免提交时被格式问题打回。适合首次参加电力类赛事、手里有一份资料包却不知从哪下手的初学者也适合已经跑通过一次、想整理成规范流程的熟手。2. 管道设计数据、特征、训练和推理的四个层次2.1 管道分层与数据流向一份能端到端出分的骨架拿到电力赛赛题我一般不会立刻打开建模文档而是先在白纸上画一条数据流原始数据从哪个目录进来经过哪几层处理后从哪个文件出去。竞赛管道和工程系统的差别在于速度但架构上越接近工程系统越不容易在后期返工。常见做法是把整个管道拆成四个层次数据加载层、特征工程层、模型训练层、推理提交层。下面这个骨架是我常用的入口组织方式它把四个层次收拢到一个函数里确保任何人拿到代码都能按同一顺序跑出结果。# pipeline_main.py from data_loader import load_raw, clean_data from feature_builder import build_features from model_trainer import train_model, predict def run_pipeline(raw_path: str, out_path: str): # 数据层读原始文件输出规整后的DataFrame raw_df load_raw(raw_path) # 数据层清洗缺失、去重、排时序 clean_df clean_data(raw_df) # 特征层生成滑窗、滞后、时间派生特征 feature_df build_features(clean_df) # 建模层时序交叉验证 训练基线模型 model, cv_score train_model(feature_df) # 推理层对预测集生成提交文件 submission predict(model, feature_df) submission.to_csv(out_path, indexFalse) print(fCV RMSE: {cv_score:.4f})这段代码的关键在于“分层”而非“实现”。load_raw只负责读文件和规定列类型clean_data只处理脏数据build_features不碰模型。这样分层有几个好处第一比赛后期换特征时不需要动模型代码第二线下调试时可以直接对某个层单独跑测试第三如果主办方重新发了一份数据只需要改load_raw里的路径和字段名后面全部复用。我在实际带队伍时经常看到有人把所有逻辑塞进一个pandas脚本线性插值、lag特征、LightGBM训练全在一起指标一差根本不知道是哪一步污染了数据。各层产物可以用一张表固化下来方便核对中间结果是否符合预期。管道层核心职责输出产物常见失误数据加载读原始csv统一列类型与时间格式规整DataFrame设备ID被读成数值导致前导零丢失数据清洗去重、排序、处理缺失干净时序表排序前就做插值行列错位特征工程生成时间特征与统计特征宽表特征集shift/rolling顺序错乱引入未来数据训练验证模型训练与分数评估模型文件与CV分数随机KFold导致时序泄漏推理提交对预测集生成评分文件submission.csv表头列顺序与官方要求不一致2.2 为什么电力时序数据不能照搬普通表格流程很多人在参加电力AI大赛之前先跑过房价预测、信贷评分这类表格赛于是把同一套流程搬过来pandas读取、LabelEncoder、随机切分、XGBoost调参。这种做法在表格赛里问题不大但电力赛的数据几乎都是带时间戳的序列数据比如某个设备每15分钟或每小时采集一次的负荷值再叠加温度、湿度、风速等外部工况。普通表格流程至少有三个地方会直接翻车。第一样本之间不是独立的。相邻时间点的负荷高度相关今天的负荷对明天同一时刻有极强参考意义。如果用随机打乱的方式划分训练集和验证集验证集里会出现训练集未来时刻的信息模型在验证集上的分数会虚高公开评测时又打回原形。第二电力数据存在明显的周期结构日周期、周周期、季节周期叠加特征构造如果不考虑滞后项和滑窗统计模型只能学到平均水平。第三同一设备的时间序列和另一台设备的序列不能简单混合不同设备的装机容量、运行工况差异很大按行混排会让模型学到“平均负荷”而非“设备行为”。我一般会在管道设计阶段就明确两个规则一切操作都按device_id timestamp分组排序验证集划分必须以时间顺序为前提不能混入未来样本。这两条规则写进管道骨架后后面所有步骤都围绕它们展开。否则等到特征构造完再来补排序排查成本会成倍上升。3. 数据清洗与特征工程把电力负荷变成模型能吃的样子3.1 脏时序的加载与清洗先处理排序、去重、缺失三座山电力数据最脏的地方不是格式而是时间序列的完整性。以我经历过的某次电力负荷预测赛为例原始文件里同一个设备在同一个时间戳可能有两行记录部分设备某一天的数据整体缺失还有个别采样点数值为0或为负。这些现象单独看都不复杂但凑在一起会让特征工程阶段产生大量错位。先看加载和排序这一步决定了后续所有依赖“顺序”的操作是否正确。# data_loader.py import pandas as pd def load_raw(raw_path: str) - pd.DataFrame: dtype_map { device_id: str, # 防止001被读成1 timestamp: str, load: float, # 负荷值统一为float temperature: float, wind_speed: float, } df pd.read_csv(raw_path, dtypedtype_map, parse_dates[timestamp]) # 时序数据的核心先按设备时间排序再操作 df df.sort_values([device_id, timestamp]).reset_index(dropTrue) # 同设备同时间戳只保留最后一条采样 df df.drop_duplicates(subset[device_id, timestamp], keeplast) return df这里有两个参数值得注意。dtype_map把device_id强制转成字符串避免pandas把“007”读成7parse_dates让timestamp列成为pandas的datetime类型这样后续dt.hour、dt.dayofweek才能正常工作。排序和去重必须紧跟在读取之后顺序不能反过来否则去重会保留错乱的顺序。我见过一个队伍在这里少写了一行sort_values结果后面所有shift特征全部错位模型分数一直不稳定最后排查了两天才找到根因。接下来是缺失值处理。电力数据的缺失分两种单点缺失和连续断档。单点缺失通常用前后线性插值即可连续断档如果超过一定长度线性插值会把断档区域填成一条直线完全丢掉日周期特征。常见做法是对断开长度分档处理短缺口插值长缺口参考前一天同时刻。def clean_data(df: pd.DataFrame, max_gap: int 6) - pd.DataFrame: df df.sort_values([device_id, timestamp]).copy() # 单点缺失与短缺口按设备线性插值 df[load] df.groupby(device_id)[load].transform( lambda s: s.interpolate(methodlinear, limit_directionboth) ) # 长缺口用前一天同一时刻回填 df[load] df.groupby(device_id)[load].transform( lambda s: s.fillna(s.shift(24)) ) # 兜底仍缺失的采样点用设备中位数 df[load] df.groupby(device_id)[load].transform( lambda s: s.fillna(s.median()) ) return df这个函数的思路是层层兜底每一步只负责填补上一轮遗留的空缺。limit_directionboth让序列开头和结尾的单点缺失也能被填补shift(24)假设数据是小时粒度如果题目给的是15分钟粒度就要改成shift(96)。最后的中位数兜底虽然朴素但至少保证模型训练时不会因为NaN报错。真正到比赛后期你可以把中位数替换成更精细的模型预测值但在搭建管道的第一版稳定优先于技巧。需要注意transform要求每个设备组内必须有顺序所以函数入口第一行又做了一次排序。这里的sort_values不是多余的因为clean_data可能被单独调用不能假设上一步已经排好序。3.2 特征构造滑窗、滞后与业务侧分箱清洗完毕后进入特征工程。我常用的特征类型有三类时间派生特征、滞后特征、滑窗统计特征。时间派生特征捕捉周期规律滞后特征让模型看到“昨天同一时刻发生了什么”滑窗统计则刻画近期趋势和波动水平。# feature_builder.py import pandas as pd def build_features(df: pd.DataFrame) - pd.DataFrame: df df.sort_values([device_id, timestamp]).copy() # 时间派生小时、星期、是否周末 df[hour] df[timestamp].dt.hour df[dayofweek] df[timestamp].dt.dayofweek df[is_weekend] (df[dayofweek] 5).astype(int) # 滞后特征昨天同一时刻的负荷 df[lag_1d] df.groupby(device_id)[load].shift(24) # 滑窗统计过去7天均值与标准差 df[rolling_mean_7d] df.groupby(device_id)[load].transform( lambda s: s.rolling(7 * 24, min_periods1).mean() ) df[rolling_std_7d] df.groupby(device_id)[load].transform( lambda s: s.rolling(7 * 24, min_periods1).std() ) # 业务交叉特征温度与负荷的交互项 df[temp_load] df[temperature] * df[load] return df这里最关键的是shift与transform的分工。shift是逐行偏移必须配合groupby使用否则会把上一台设备的最后一行当成下一台设备的第一行历史rolling窗口大小必须和数据粒度对齐小时粒度下7天就是168个点。min_periods1允许序列开头还没攒够窗口时先算部分均值避免前168行全部变成NaN。滞后和滑窗特征给模型提供了“记忆”但也引入了风险训练集和验证集的边界特征依赖历史数据如果验证集起点前没有对应历史就会出现空窗。这个问题在后面避坑章节会详细讲。特征构造完成后建议用df.shape和df.isna().sum()快速检查一次确认没有意外的大面积缺失。4. 建模与验证先让基准分可信再谈调优4.1 基线模型怎么选为什么我先上LightGBM电力负荷预测这类回归任务我几乎不会一上来就搭深度学习模型。原因很实际管道还没跑通时需要先用一个稳定、解释性强、训练快的模型把基线分数打出来。LightGBM是我在多数电力赛里的首选。它对表格型特征的支持好能处理缺失值训练速度快而且特征重要度可以直接输出方便后续筛选特征。# model_trainer.py import lightgbm as lgb from sklearn.metrics import mean_squared_error def train_model(feature_df): feature_cols [ hour, dayofweek, is_weekend, lag_1d, rolling_mean_7d, rolling_std_7d, temp_load, temperature, wind_speed, ] # 训练标签是当前时刻的负荷 target_col load train_df feature_df.dropna(axis0, subsetfeature_cols [target_col]) params { objective: regression, metric: rmse, learning_rate: 0.05, num_leaves: 63, min_child_samples: 50, feature_fraction: 0.8, bagging_fraction: 0.9, bagging_freq: 1, verbosity: -1, } # 先不做验证直接训练一个快速基线 dtrain lgb.Dataset(train_df[feature_cols], labeltrain_df[target_col]) model lgb.train(params, dtrain, num_boost_round500) return model这段代码里需要留意的不是参数而是dropna处理。lag_1d和rolling_mean_7d在序列开头是NaN直接交给模型会报错或静默产生偏差所以训练前必须删除。num_leaves63对应约2的6次方是LightGBM在中等数据量下的常见起点数据量大可以调高数据量小则要调低防止过拟合。learning_rate0.05配合num_boost_round500是偏保守的组合训练速度快后期调参时可以降到0.01并提高迭代次数。如果你用的是XGBoost或CatBoost管道结构完全一样只需要替换训练函数内部的模型API。先跑通分数再换模型才是管道搭建的正确节奏。很多人一开始就同时在三个模型间横跳结果连基线分数都说不清是哪版代码跑出来的。4.2 用时序验证避免“线下飘高线上翻车”基线模型能做预测后最迫切的任务不是调参而是建立一套可信的线下验证流程。前面已经提过电力时序数据不能随机切分这里给出具体的做法用TimeSeriesSplit按时间顺序做多折验证或者按设备分组做泛化验证两种各有适用场景。# 时序折验证保证验证集时间全部晚于训练集 from sklearn.model_selection import TimeSeriesSplit import numpy as np class TimeCV: def __init__(self, df, n_splits5, gap_hours24): self.df df.sort_values(timestamp).copy() self.n_splits n_splits self.gap_hours gap_hours def split(self): tscv TimeSeriesSplit(n_splitsself.n_splits) for train_idx, valid_idx in tscv.split(self.df): yield train_idx, valid_idxTimeSeriesSplit的思路很直接把样本按行顺序从头到尾切成n1段第一折用第1段训练、第2段验证第二折用前两段训练、第3段验证以此类推。它只按行切分所以使用前提是DataFrame已经按时间排序。但这里有第一个边界情况如果数据是多台设备并列排列行顺序不是纯时间顺序切出来的每一折都会包含多台设备同一时段的数据。此时预测目标如果是“未来同一时段所有设备”这种切法有效如果目标是“新设备是否正常”就需要按设备分组切分。第二个边界情况是gap。TimeSeriesSplit默认训练集和验证集相邻验证集开头会紧挨训练集结尾此时如果特征里带了rolling_mean_7d验证集开头一段数据的滑窗特征会“看到”训练集末尾的数据严格来说也算泄漏。常见做法是在两次切分之间留出24小时或更长的间隔。sklearn的TimeSeriesSplit支持gap参数直接传行数即可。验证流程建立后记录每一次特征改动的CV分数。我一般会在一个本地文件里维护一张实验记录表列名包括特征版本、CV分数、训练耗时、备注。这张表在比赛后期非常值钱它能告诉你每一次改动是真正变好还是只是随机波动。5. 管道搭建避坑五个最让人上头的坑5.1 随机KFold给了一个不存在的分数现象线下CV的RMSE非常漂亮但把同样代码跑在线上评测数据上分数暴跌一大截。原因用train_test_split或KFold(shuffleTrue)切分电力时序数据时验证集随机混入了训练集时间范围内的样本模型提前“看过”了答案。解决把验证切分改为TimeSeriesSplit并确保数据按device_id timestamp先排序。这件事没有玄学成分时序数据一旦打乱泄漏就无孔不入。我的一个建议是在管道里默认禁用一切shuffle参数无论是验证集切分还是LightGBM内部的bagging都要意识到shuffle发生的位置。LightGBM的bagging只作用于单棵树内部的样本采样不是切分验证集这两者不冲突。5.2 缺失值插值把停电“填直”了现象预测曲线在某些设备上一段接一段特别平直完全看不出负荷波动的日周期特征打开原始数据发现这些设备在对应时段有大段缺失记录。原因对连续几十个小时的缺失缺口做线性插值会被拉成一条直线模型学到“这段时间负荷就是平的”。解决clean_data里按缺口长度分流短缺口线性插值长缺口用前一天同时刻回填。回填后仍要检查一下设备中位数兜底的部分是否合理有时候历史中位数和当前时段负荷差异很大宁可用回填也不用一条直线。一个实用技巧是统计每个设备的最大连续缺失长度把它作为一列特征喂给模型。设备掉线时长本身可能和运行状态相关多加这一列后有些赛题的分数反而涨了。5.3 特征错位shift漏排序导致预测混入未来现象模型在训练时分数异常高逻辑上也说不通排查后发现lag_1d这一列的大部分值都来自“未来”。原因做groupby(device_id)[load].shift(24)之前没有按时间排序或者排序时只排了时间没排设备导致分组内部其实是乱的shift出来的值根本不是“过去的24个小时”而是任意相邻行。解决在特征构造函数开头强制sort_values([device_id, timestamp])并且在shift之后立刻做一次数据抽查手动打印某个设备某几行的原始load和lag_1d用肉眼确认滞后方向正确。这类错位不会导致程序报错最容易被忽略。我自己的习惯是每次构造完特征后对随机一个设备画三行数据贴到笔记里看一下load和lag_1d的时间关系确认无误再进模型。5.4 推理阶段特征空窗线上评估从中间开始现象线下CV正常提交线上后分数与线下严重不符回到本地模拟推理发现测试集初始几天的特征列是NaN。原因lag_1d和rolling_mean_7d需要依赖过去一段时间的真实负荷而官方给出的待预测时间段起点处根本没有历史数据可依赖。如果你的管道在预测时仍然沿用训练时的shift逻辑前几行特征必然为空。解决在推理脚本里维护一个“历史缓冲池”这个池子以dict形式按device_id存下最近N天的真实负荷预测起始时刻用它来构造滞后和滑窗特征每预测完一个时刻再把真实值或预测值追加进缓冲池。以下是一个简化的推理缓冲逻辑写代码时可以直接套用思路。def predict_with_history(model, feature_df, history_buffer): preds [] for row in feature_df.itertuples(): device_id row.device_id history history_buffer.get(device_id, []) # 用历史缓冲构造特征特征名称与训练时保持一致 row_feat build_feature_from_history( row, history ) pred model.predict([row_feat])[0] preds.append(pred) history.append(row.load if not np.isnan(row.load) else pred) history_buffer[device_id] history[- 7 * 24:] return predsbuild_feature_from_history里需要实现和build_features完全一致的特征逻辑这是整个管道中最容易产生重复代码的地方。我一般会把特征构造抽象成一个纯函数输入为“一行样本历史数组”输出为特征列表这样训练和推理都调用同一份代码避免两边逻辑漂移。5.5 单位与量纲幻觉模型被迫学“翻译”现象只有少数设备的预测误差特别大单独查看这些设备发现负荷数值分布和其他设备完全不同。原因这些设备可能上报的是兆瓦级别数据其他设备是千瓦级别或者负荷列是功率而温度列是摄氏度模型需要额外学习一个单位换算关系白白消耗表达能力。解决在load_raw里显式指定dtype_map后对每一列做describe()记录min、max、std发现数值跨度异常的列要么统一单位要么做log变换。这里不要用“全量数据标准化”来掩盖问题标准化会把单位差异抹掉但也会把设备间的真实差异抹掉。我曾在一次赛题里碰到某台设备的负荷整体比其他设备小一百倍排查后确认是采集点位置不同所致不是异常数据。这种情况下按设备做归一化比全局归一化更合理具体选择取决于你的预测目标。6. 提交前最后一步跑回放、对Schema、留基线管道跑通后我还有一个坚持到现在的习惯提交前做一次“回放验证”。回放验证不是重新训练而是把测试集开头的一部分当作未知数据用管道里的推理逻辑逐步预测、逐步把真实值喂回特征缓冲池模拟线上评估的完整轨迹。这个步骤能暴露出特征空窗、缓冲池更新错误、单位换算遗漏等所有本地CV看不出的问题。回放的精髓在于“逐步”。一次性预测一整段测试集和逐步预测的结果差异本质上就是管道在边界条件下的稳定性差异。很多线上翻车都是因为一次性推理时用了未来信息而回放可以让你亲眼看到每一步的输入和输出。def replay(model, raw_df, start_idx, n_steps100): history_buffer build_initial_history(raw_df.iloc[:start_idx]) replay_rows raw_df.iloc[start_idx: start_idx n_steps] preds [] for row in replay_rows.itertuples(): feat build_feature_from_history(row, history_buffer) pred model.predict([feat])[0] preds.append(pred) history_buffer[row.device_id] history_buffer.get( row.device_id, [] )[-24:] [row.load] return preds回放结果如果与一次性推理结果有几行差异说明管道里有滑窗特征在“偷看”未来这是最需要警惕的。回放分数与线下CV分数相差不大时提交才有底气。我每次调完特征都会把回放脚本重跑一遍把它当作提交前的安全气囊。提交格式方面我习惯写一个只校验输出文件的小脚本检查三件事表头列名是否与官方sample完全一致、行数是否等于预测集行数、device_id和timestamp组合是否唯一。这三项检查用几条pandas语句就能完成但能避免很多“好不容易训练好模型、提交却因为格式被拒”的尴尬。# check_submission.py import pandas as pd sub pd.read_csv(submission.csv) sample pd.read_csv(sample_submission.csv) assert list(sub.columns) list(sample.columns), 表头不一致 assert len(sub) len(sample), 行数不一致 assert sub[device_id].duplicated().sum() 0, ID重复 assert sub[load].isna().sum() 0, 存在缺失预测值 print(提交文件校验通过)回到这篇笔记的主题数据挖掘管道搭建的价值从来不是某个模型有多强而是让每一次实验、每一版特征、每一个分数都可追溯。把管道拆成数据、特征、训练、推理四层每层只做一件事然后配上回放验证和提交检查这就是我参加电力AI赛事的固定流程。希望这篇文章能帮你少走几趟弯路更早把精力放到真正值得深挖的特征和业务理解上去。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑