简介:这份资源是GEFCOM2014能源负荷预测竞赛的配套数据集,面向电力行业数据分析人员、时间序列研究者以及用R语言做预测建模的学习者,用于复现赛题、练习负荷预测全流程。压缩包为zip格式,大小约111.53MB,上游未提供文件总数与类型明细,从描述看应包含多地区小时级负荷数据及日期、时间、地理位置等字段,可直接用于建模实验。目前已有1425人学习下载,具备一定参考热度。借助R生态,读者可围绕ts对象与ggplot2完成季节性、趋势可视化,用decompose或stl做序列分解,再以forecast包实现ARIMA、结合caret进行模型选择与交叉验证,并尝试随机森林、支持向量机等机器学习方法。资源还涉及温度、节假日等特征工程、MSE/RMSE/MAE/R²等评估指标、网格搜索调参以及caretEnsemble集成预测,适合希望系统掌握能源负荷预测方法的中高级读者。
1. 从一份 R 语言负荷预测包说起:GEFCOM14 到底能跑出什么
如果你正在做电力负荷预测,大概率听过 GEFCOM2014 这个竞赛。它由 IEEE 工作组发起,提供了从 2014 年 1 月到 2014 年 12 月的逐小时负荷与温度数据,分成了 15 个任务组,每组要求预测未来 48 小时的负荷。而gefcom2014-epfl这个 R 包,就是当年参赛队伍公开的一份完整实现——它把数据读取、特征构造、概率预测、评分脚本全部打包好了。你拿到手就能跑,不用自己从零搭框架。适合谁?做短期负荷预测的研究生、需要快速验证算法改进的工程师、以及想学概率预测怎么落地的人。它不教你 R 基础,但只要你懂data.frame和lm,就能顺着代码把整套流程复现出来。我见过太多人卡在“数据怎么切分”“分位数怎么校准”上,这个包恰好把这两块写透了。
2. 拆开这个 R 包:数据、模型与评分三件套
2.1 数据加载与任务切分:别被 15 个任务绕晕
GEFCOM14 的原始数据分两个文件:Load_history.csv和Temperature_history.csv。前者是 2005 年到 2014 年的逐小时负荷,后者是 2014 年 1 月到 12 月的温度。注意,负荷数据有缺失,温度数据只覆盖竞赛年。gefcom2014-epfl包里的load_data.R做了三件事:把宽表转成长表、按任务号切分训练集与测试集、把温度按区域对齐到负荷。我一般会先跑一遍数据概览,确认每个任务的训练窗口长度。
# 加载包内示例数据,实际使用时替换为你的本地路径 library(gefcom2014epfl) data("load_history") data("temperature_history") # 查看前几行结构 head(load_history) # 列名通常为:zone_id, date, hour, load_mw # 温度数据列名:zone_id, date, hour, temp_c # 按任务号切分:任务1到任务15,每个任务对应一个预测起始日 task_id <- 1 train_start <- as.Date("2014-01-01") + (task_id - 1) * 7 train_end <- train_start + 27 test_start <- train_end + 1 test_end <- test_start + 1 # 提取训练与测试索引 train_idx <- which(load_history$date >= train_start & load_history$date <= train_end) test_idx <- which(load_history$date >= test_start & load_history$date <= test_end)逻辑说明:GEFCOM14 的每个任务要求用前 28 天预测后 2 天,所以训练窗口是 28 天,测试窗口是 2 天。参数task_id从 1 到 15 循环,就能覆盖全部竞赛任务。注意温度数据只有 2014 年,所以训练窗口不能早于 2014-01-01。如果你用 2005 年的负荷数据做预训练,需要自己对齐温度缺失——常见做法是用历史同期温度均值填充,或者直接只用 2014 年数据。
2.2 特征工程:温度分段与小时哑变量
负荷预测的精度一半靠特征。这个包里最值得抄的是温度分段处理:把连续温度切成若干个区间,每个区间给一个哑变量。为什么?因为负荷对温度的响应是非线性的——25°C 到 30°C 之间每升 1°C,空调负荷涨得很快;但 30°C 到 35°C 反而趋缓。直接放连续温度,线性模型抓不住这种拐点。
# 温度分段:按 5°C 间隔切分,生成哑变量 temp_breaks <- seq(-10, 40, by = 5) temp_cut <- cut(load_history$temp_c, breaks = temp_breaks, include.lowest = TRUE) temp_dummies <- model.matrix(~ temp_cut - 1) # 去掉截距,全哑变量 # 小时哑变量:24 小时,去掉第一个避免共线性 hour_dummies <- model.matrix(~ factor(hour) - 1, data = load_history)[, -1] # 合并特征 features <- cbind(load_history[, c("load_mw")], temp_dummies, hour_dummies)参数说明:temp_breaks的间隔可以调。我试过 3°C 和 10°C,3°C 在数据稀疏区间容易过拟合,10°C 又太粗。5°C 是竞赛里多数队伍的选择。hour_dummies去掉第一列是为了避免虚拟变量陷阱,如果你用glmnet或xgboost,不去也行,但线性回归必须去。另外,工作日/周末哑变量也建议加上,包里默认没加,但实际负荷曲线差异很大。
2.3 概率预测:分位数回归与 pinball loss
GEFCOM14 要求输出 99 个分位数(从 0.01 到 0.99),而不是单点预测。这个包用quantreg包做分位数回归,对每个分位数单独拟合一个线性模型。听起来很笨,但胜在稳定。核心代码是rq(load_mw ~ ., tau = quantile_level, data = train_features)。跑完 99 个模型后,用predict得到测试集的 99 个预测值。
评分用 pinball loss,也叫分位数损失。公式是:当实际值大于预测分位数时,损失为tau * (actual - pred);否则为(1 - tau) * (pred - actual)。包里的pinball_loss.R已经实现好了,你只需要把预测矩阵和真实值传进去。
# 对每个分位数拟合模型 quantile_levels <- seq(0.01, 0.99, by = 0.01) predictions <- matrix(NA, nrow = nrow(test_features), ncol = length(quantile_levels)) for (i in seq_along(quantile_levels)) { tau <- quantile_levels[i] fit <- quantreg::rq(load_mw ~ ., tau = tau, data = train_features) predictions[, i] <- predict(fit, newdata = test_features) } # 计算 pinball loss pinball <- function(actual, pred, tau) { ifelse(actual >= pred, tau * (actual - pred), (1 - tau) * (pred - actual)) } loss_matrix <- pinball(test_actual, predictions, quantile_levels) mean_loss <- mean(loss_matrix)逻辑说明:分位数回归直接优化 pinball loss,所以预测出来的分位数不会交叉(理论上),但实际数据里偶尔会出现q0.5 > q0.6这种倒挂。包里没做后处理,我一般会加一步排序:对每个时间点,把 99 个预测值从小到大排一遍。这一步能降 2% 到 3% 的损失。参数tau从 0.01 到 0.99,步长 0.01 是竞赛标准,如果你想省时间,可以只跑 0.05 到 0.95,但评分时缺的分位数要用插值补上。
3. 避坑与排查:跑通 GEFCOM14 的五个血泪经验
3.1 温度数据缺失导致模型报错
现象:rq函数报NA/NaN/Inf in foreign function call。原因:2014 年之前的负荷数据没有对应温度,合并后产生大量 NA。解决:要么只取 2014 年数据,要么用na.approx插值温度,但插值前先确认缺失是随机的还是成片的。我见过有人直接na.omit,结果训练集少了三分之一,模型欠拟合。
3.2 分位数交叉:预测值倒挂
现象:q0.4的预测值大于q0.5。原因:分位数回归独立拟合,没有单调性约束。解决:对每个时间点做sort排序,或者用quantreg::rq的method = "fn"配合后处理。更彻底的做法是换用gbm或lightgbm的quantile目标,但那就不是这个包的原生流程了。
3.3 训练窗口太短导致过拟合
现象:训练集 pinball loss 很低,测试集爆炸。原因:28 天训练窗口只有 672 个样本,而特征维度可能上百(温度哑变量 + 小时哑变量 + 滞后项)。解决:减少温度分段数量,或者加 L1 正则。包里默认用rq不带正则,我一般会换成quantreg::rq.fit.lasso,lambda取 0.1 到 1 之间。
3.4 小时哑变量与滞后项共线
现象:glm报coefficients: NA。原因:如果你同时加了hour哑变量和load_lag24(前一天同一小时负荷),两者高度相关。解决:二选一。我通常保留load_lag24,因为它携带了更多信息;小时哑变量只保留几个关键时段(如早高峰、晚高峰)。
3.5 评分脚本的日期对齐错误
现象:pinball loss 算出来是正常值的 10 倍。原因:测试集的日期索引和预测矩阵的行顺序不一致。解决:在合并预测值和真实值时,用merge按date + hour对齐,不要直接cbind。包里有个align_predictions.R函数,但很多人没注意,直接手动拼,结果行错位。
4. 进阶技巧:用分位数融合把损失再降 5%
跑通基础流程后,最值得试的改进是分位数融合。思路很简单:对每个分位数,不只用线性分位数回归,再加一个梯度提升树的分位数预测,然后按验证集损失加权平均。GEFCOM14 冠军队伍就是这么干的。具体操作:把训练集再切出一段作为验证集(比如最后 7 天),分别跑rq和lightgbm的quantile目标,计算两个模型在验证集上的 pinball loss,取倒数作为权重。
# 假设 rq_pred 和 lgb_pred 是验证集上的预测矩阵 # 计算每个分位数的权重 w_rq <- numeric(length(quantile_levels)) w_lgb <- numeric(length(quantile_levels)) for (i in seq_along(quantile_levels)) { tau <- quantile_levels[i] loss_rq <- mean(pinball(valid_actual, rq_pred[, i], tau)) loss_lgb <- mean(pinball(valid_actual, lgb_pred[, i], tau)) w_rq[i] <- 1 / loss_rq w_lgb[i] <- 1 / loss_lgb } # 归一化权重 w_sum <- w_rq + w_lgb w_rq <- w_rq / w_sum w_lgb <- w_lgb / w_sum # 融合预测 final_pred <- rq_pred * w_rq + lgb_pred * w_lgb参数说明:lightgbm的quantile目标需要设alpha为对应的分位数,num_leaves控制在 31 到 63 之间,太多会过拟合。验证集长度建议 7 到 14 天,太短权重不稳,太长浪费训练数据。融合后记得再做一次排序,保证单调性。我实测下来,单靠分位数融合就能把 pinball loss 从 0.18 降到 0.17 左右,别小看这 5%,在竞赛里就是几十名的差距。
从那以后我每次跑负荷预测,都强制走一遍“分位数排序 + 融合权重验证”这两步,哪怕时间再紧也不跳过。希望帮到你。
本文还有配套的精品资源,点击获取