充电负荷预测实战:时序建模、特征工程与有序充电调度 简介为研究电动汽车对配电网影响及充电站选址定容提供基础这份资源以Matlab程序完整复现了《电动汽车充电负荷预测方法及应用研究_曹卫涛》中的核心建模思路面向电气工程、交通能源领域的研究人员与高年级学生可帮助理解规模化电动汽车充电负荷随一天时间变化的分布规律。压缩包共7个文件包含6个.m脚本和1个caj文献原文脚本分别对应公交车、出租车、公务车及私家车等不同类型车辆的充电负荷计算模型caj文件便于对照原文学方法包体大小仅1.85MB轻量易用。目前已有3252人学习浏览说明其受到同领域研究者认可。通过运行程序读者可掌握分时电价、充电模式等因素对负荷分布的影响机制并可将模型迁移至其他区域进行充电负荷预测与分析为后续配电网规划研究提供数据支撑。同时程序中注释与变量命名清晰便于二次开发与参数调整。1. 充电负荷预测先搞清预测什么、给谁用一座充电站做扩容时被追问最多的不是装多少根桩而是负荷曲线峰值是多少、出现在几点、一年能出现几次。充电负荷预测就是把历史充电记录变成未来一段时间的功率曲线这个数字直接决定配电容量预留、有序充电策略和场站投资回报率的计算口径。这个方向同时牵扯时间序列建模、特征工程和调度策略三层问题适合做充电运营平台、园区微电网、电网侧调度系统的工程师参考。下文按定尺度、做特征、建模型、落调度四步展开代码基于充电订单表和 Python 技术栈可以直接改着跑。2. 充电负荷的周期性与影响因素先定时间尺度再选特征充电负荷预测常见的误区是一上来就选模型。实际上预测的时间尺度决定了允许使用哪些数据、采用哪种模型也决定了误差容忍度。时间尺度定错后续所有特征和模型选择都会跟着错最后调的其实是错误方向上的参数。2.1 预测时间尺度决定模型选型超短期、短期与中长期按业务切分充电负荷预测通常分成三档。超短期预测指未来 15 分钟到 1 小时用于有序充电的实时控制比如判断下一时刻站点会不会超过变压器容量进而决定是否限制新启动桩的功率。这档对延迟要求高模型要轻常见做法是滑动平均加误差校正或者很小的树模型不需要上深层网络。短期预测指未来 1 到 7 天以 15 分钟或 1 小时间隔输出负荷曲线用于充电场站的日调度计划、需求响应申报和运维排班。这是大多数充电运营平台投入资源最多的场景。中长期预测指月度、季度尺度的电量趋势主要用于扩容立项和配电网改造规划特征上更依赖车辆保有量和区域经济数据跟高频时序模型的关联反而弱。三档之间的核心差别在于超短期预测主要靠滞后值和当前功率爬坡趋势短期预测必须引入星期周期、节假日和温度等外生变量中长期预测依赖慢变量数据粒度可以粗到天。选型上超短期用持久性模型或 ARIMA 足够短期用 LightGBM 或 LSTM 都有成熟实践中长期直接做回归聚合更实际。2.2 充电负荷的四个关键影响因素充电负荷不是纯随机序列它有强周期性和明确的外生驱动。从实际数据里能稳定观察到的变量有以下几类。影响因素影响路径特征化方式环境温度低温触发电池加热能耗上升高温空调负荷叠加当前温度、日平均温度、前 24h 温差分时电价谷电时段订单集中负荷曲线由单峰变双峰电价时段标记、距电价切换点分钟数出行行为通勤早晚高峰、节假日城际出行星期、是否节假日、是否工作日天气状况雨雪天减少外出站点负荷整体下移降水等级、风速温度的影响在北方地区格外显著。同一站点1 月与 7 月的单次充电电量可以相差 20% 以上这部分不是车辆电池容量变化而是电池加热和空调负荷叠加到了充电功率上。处理时不要只把温度当连续特征把是否低于 0°C是否高于 30°C拆成阈值特征模型更容易在分裂时捕捉到拐点。分时电价的作用体现在用户行为迁移上。执行峰谷电价的地区夜间低谷时段的充电订单占比明显抬高。特征化时除了标记当前时段还可以构造距离下一个电价切换点的分钟数让模型学到电价切换前的抢充行为这一项在实测中能带来 3% 到 5% 的误差下降。2.3 数据采样对齐与缺失时段处理充电负荷数据来自充电桩功率采样或订单聚合常见的坑在于时间戳不齐。订单表里记录的 start_time 和 end_time 是充电会话起止时间电量是整段会话的累计值如果直接按结束时间聚合会把跨时段负荷全部算到最后一个时间槽上。正确做法是把每条会话按 15 分钟切片、按重叠时长比例拆分电量再按站点和时段求和。这一步对恒功率充电有轻微误差但对站点级聚合曲线影响很小。缺失时段通常来自桩离线或通信中断表现为连续零值或空值。连续缺失小于 2 小时的用前后向填充加平滑超过 2 小时的用同星期同时刻的历史中位数填充并在特征里加一列 missing_flag让模型意识到这段数据是补出来的。3. 用 LightGBM 做短期充电负荷预测的特征工程与参数整定确定时间尺度和特征来源后先落地的模型我推荐 LightGBM。它在充电负荷这类强周期性、中等数据量的回归任务上训练快、不需要归一化、对缺失值有原生处理而且特征重要性可以直接用来反推业务理解是否正确。3.1 为什么树模型在负荷预测里仍然值得先用短期充电负荷预测本质是从历史模式里找相似性工作日早高峰、节假日出行潮、温度骤降后的负荷抬升。这些模式用树模型的分裂规则就能表达不需要像深度模型那样靠大量数据才能拟合。一个充电站攒 3 到 6 个月的 15 分钟粒度数据大约 1 万到 4 万个样本LightGBM 在这个量级上已经能稳定跑到 5% 到 10% 的 MAPE。这不是说深度模型没用而是工程上要按收益排序。树模型训练一次几十秒特征可以反复迭代LSTM 要处理序列样本、调学习率、控制过拟合时间成本高一个量级。先把树模型的基线做出来用特征重要性检查业务假设再决定要不要上序列模型。3.2 从充电订单表构造训练集下面这段代码处理最典型的原始表结构每条记录是一次充电会话。先把会话按 15 分钟切分聚合成站点功率曲线再构造预测目标。import pandas as pd # 原始充电订单station_id, start_time, end_time, power_kwh df pd.read_csv(charging_sessions.csv, parse_dates[start_time, end_time]) # 按 15 分钟槽位拆分跨时段会话 slots [] for _, row in df.iterrows(): slot_range pd.date_range(row[start_time].floor(15min), row[end_time].floor(15min), freq15min) total_sec (row[end_time] - row[start_time]).total_seconds() for slot in slot_range: overlap min(slot pd.Timedelta(minutes15), row[end_time]) \ - max(slot, row[start_time]) minutes overlap.total_seconds() / 60 if minutes 0: # 按重叠时长比例拆分电量折算到对应槽位 slots.append({station_id: row[station_id], slot: slot, power_kwh: row[power_kwh] * minutes * 60 / total_sec}) agg pd.DataFrame(slots).groupby([station_id, slot], as_indexFalse)[power_kwh].sum() agg[load_kw] agg[power_kwh] * 4 # 15min 电量(kWh) 折算平均功率(kW)代码的核心是用时间重叠比例把一次会话的电量分配到多个槽位避免把整段电量错误计入结束时刻。最后一行的乘 4是因为 15 分钟窗口内的电量换算成功率要乘 4这个换算关系后续计算误差率时也要保持一致。生产环境数据量大时不要用 iterrows 逐行处理改成 SQL 或 Polars 的 interval join逻辑不变。构造完功率序列后特征工程按上一章的影响因素展开。滞后特征和周期特征是树模型的骨架。def build_features(agg, temperature): f agg.set_index(slot).sort_index().copy() f[hour] f.index.hour f[weekday] f.index.weekday # 0周一5/6 为周末 f[is_weekend] (f[weekday] 5).astype(int) # 滞后特征15min 粒度96 格 1 天 f[lag_96] f[load_kw].shift(96) # 昨天同时刻 f[lag_192] f[load_kw].shift(192) # 前天同时刻 f[lag_672] f[load_kw].shift(672) # 7 天前同时刻 f[roll_mean_96] f[load_kw].shift(96).rolling(96).mean() f[roll_std_96] f[load_kw].shift(96).rolling(96).std() # 外生变量对齐temperature 为站点气象序列 f f.join(temperature.reindex(f.index), howleft) f[temp_low] (f[temp] 0).astype(int) f[temp_high] (f[temp] 30).astype(int) return f.dropna()滞后 96 格让模型直接看到昨天同一时刻的负荷这是充电负荷预测里信息量最大的特征滞后 672 格捕捉星期周期。滚动均值提供近期水平滚动标准差提示今天是不是异常波动日。温度阈值特征的作用前面说过是为了让树模型更容易分裂出严寒和酷暑的边界这两个特征在北方冬季站点的特征重要性排名里通常能进前五。3.3 LightGBM 参数设定与调优特征就绪后按时间切分训练集和验证集。时间序列严禁随机切分验证集必须严格在训练集之后否则模型会看到未来评估结果虚高。import lightgbm as lgb # 按时间切分前 80% 训练后 20% 验证 split_idx int(len(features) * 0.8) train_x, train_y features[:split_idx], target[:split_idx] val_x, val_y features[split_idx:], target[split_idx:] params { objective: regression, metric: mae, learning_rate: 0.05, num_leaves: 63, max_depth: 7, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, min_data_in_leaf: 32, verbose: -1, } train_set lgb.Dataset(train_x, train_y) val_set lgb.Dataset(val_x, val_y, referencetrain_set) model lgb.train(params, train_set, num_boost_round500, valid_sets[val_set], callbacks[lgb.early_stopping(50)])注意时间序列切分一定要用时间先后顺序随机切分会让验证集包含训练集未来的样本MAE 虚低 10% 以上这是负荷预测项目里最常犯的错误。参数选择上learning_rate 0.05 配合 early stopping 是稳妥组合。调大 num_leaves 能拟合更细的时段模式但超过 100 后验证集 MAE 通常不再下降只会增加过拟合风险。min_data_in_leaf 32 在 15 分钟粒度数据上能有效抑制低谷时段的噪声拟合。这里选用 MAE 而不是 RMSE是因为充电负荷预测的业务损失更接近绝对值损失——峰值评估关心的是偏差多少千瓦而不是大误差的平方放大。3.4 预测结果后处理模型输出的原始预测值不会考虑物理约束。第一预测可能出现负值直接截断为 0第二单点预测抖动很大用 3 个槽位的滑动平均平滑输出曲线更符合调度系统预期第三如果站点有变压器限值把预测值裁剪到限值以下避免调度系统读到超限值后触发误告警。这三步在代码里是几行的事但对下游调度系统的稳定性影响很大尤其是有序充电策略对预测曲线跳变非常敏感。4. 用 LSTM 抓时序依赖PyTorch 最小实现与模型取舍树模型强在特征组合但有一个结构性短板它对序列顺序不敏感。滞后特征 lag_96 只是把昨天的值搬进来模型并不知道过去 8 小时的连续变化趋势意味着什么。当站点负荷受排队效应影响明显——比如充电桩满负荷时新到的车要排队当前功率实际上是过去几小时需求的累积结果——序列模型才有优势。4.1 序列模型解决的是滞后依赖问题排队效应在充电负荷里很常见。一个站 8 根桩全忙时后续到站的车辆在等待此时负荷曲线保持在高位平台而不是立即反映新增需求等桩空出来负荷继续维持。这种过去需求延迟释放的特征用固定滞后特征很难表达需要模型学习时间窗口内的变化模式。LSTM 的门控结构天然适合这个任务它能把过去 4 小时持续高位和过去 4 小时缓慢爬升编码成不同的隐状态。另一个适合序列模型的场景是负荷曲线形状相似但相位偏移。比如某站点周边商场活动导致晚高峰推迟半小时树模型看到晚高峰特征时已经晚了LSTM 可以通过观察窗口内的上升斜率提前响应。判断站点有没有这类模式可以画一下真实负荷曲线的自相关图看看滞后 96 格之外的周期性是否仍然明显。4.2 用 PyTorch 搭建 LSTM 负荷预测模型下面是最小可跑通的 LSTM 预测结构。输入是过去 96 个时间步1 天的特征序列输出是下一个时间步的负荷。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class LoadLSTM(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_dim, 1) def forward(self, x): out, _ self.lstm(x) # (batch, seq_len, hidden_dim) return self.fc(out[:, -1, :]) # 取最后一个时间步的隐状态 def make_sequences(data, seq_len96, load_idx0): # data: (n_samples, n_features)预测目标为下一时刻负荷 xs, ys [], [] for i in range(len(data) - seq_len): xs.append(data[i:iseq_len]) ys.append(data[iseq_len, load_idx]) # load_idx 是负荷列索引 return (torch.tensor(xs, dtypetorch.float32), torch.tensor(ys, dtypetorch.float32))关键参数是 seq_len 取 96覆盖一天周期让每个样本都能看到完整的日循环。hidden_dim 64 和 num_layers 2 是充电负荷这个数据量级下不容易过拟合的配置如果站点超过 50 个且数据量超过 10 万条可以试 128 和 3 层。dropout 只对非最后一层生效这是 PyTorch 里 LSTM 的一个易踩点num_layers1 时 dropout 参数会被直接忽略。训练时的特征缩放不能忽略。LSTM 内部使用 tanh 和 sigmoid 激活输入特征的量纲差异过大会让梯度不稳定。对功率、温度这类连续特征做 z-score 归一化对小时、星期这类周期特征用正弦余弦编码而不是直接喂 0 到 23 的整数。整数编码会让模型学到23 和 0 差距大这种错误先验正弦余弦编码则保留了 23 点和 0 点相邻的周期关系。4.3 训练注意点与两种模型的选择边界训练 LSTM 时验证集同样要按时间切分。额外要注意的是序列样本之间存在重叠前一个样本的后 95 个时间步和后一个样本的前 95 个时间步是同一段数据这会放大验证集的乐观偏差。常见做法是在构造序列时设置 step 参数每隔 16 个时间步取一个样本降低重叠度。对比维度LightGBMLSTM样本量要求1 万条以上即可稳定通常需要 5 万条以上训练耗时秒级到分钟级分钟级到小时级时序依赖靠滞后特征近似结构上原生建模特征重要性直接输出需置换法或注意力权重上线运维模型文件小无框架依赖需要 PyTorch 推理环境两种模型不是互斥关系。实际项目里我一般把 LightGBM 作为基线逐周监控误差当出现排队效应明显的集中式场站、且数据量足够时再叠加 LSTM。还有一种常见做法是取两个模型预测的加权平均权重用最近 30 天的验证误差动态计算工程上比追求单一模型更划算也能平滑单个模型在某类天气下的系统性偏差。5. 把预测曲线用起来有序充电调度与落地验证模型输出不是终点。预测曲线只有接到调度策略和容量评估流程里才算真正产生价值。这一章讲两个最直接的应用以及上线前必须做的验证动作。5.1 用预测曲线核算配电容量扩容场景的核心判断不是单日峰值而是峰值超限的频率。过去 30 天里预测峰值超过变压器限值 3 次以上才判定为结构性容量不足偶尔一次超限可能是极端天气或偶发事故引起的不应该触发扩容立项。over_limit_days (forecast_30d.max(axis1) transformer_kva).sum() if over_limit_days 3: # 申报容量取预测峰值的 85 分位避免为一年一次的极值买单 suggest_kva int(forecast_30d.max(axis1).quantile(0.85))用 85 分位而不是最大值是为了不为小概率极值支付高额容量费这个口径比峰值乘以同时率更容易跟财务对账也方便做投资回报测算。5.2 有序充电调度的最小实现有序充电的核心是错峰在预测即将超限的时段压低非紧急车辆的充电功率把需求平移到低谷时段。最小实现是按 15 分钟粒度滚动计算每根桩的功率上限不涉及多目标优化先保证系统不超限。def dispatch(schedule_15m, station_limit_kw, pile_capacity_kw, normal_pile_count, urgent_slots): # schedule_15m: 未来 96 个槽位的预测负荷曲线 pile_limits [] for t, forecast in enumerate(schedule_15m): remain station_limit_kw - forecast # 电网侧允许新增的功率 if t in urgent_slots: # 紧急车辆所在时段不限制 limit_per_pile pile_capacity_kw else: limit_per_pile max(0, remain) / max(1, normal_pile_count) pile_limits.append(min(pile_capacity_kw, limit_per_pile)) return pile_limitsforecast 来自预测模型remain 是剩余可分配功率除以非紧急桩数得到每根桩的功率上限桩端按这个值限制充电电流。预测误差会直接传导为 remain 偏差因此 15 分钟粒度误差率要控制在 15% 以内否则调度会频繁抖动反而影响用户体验。5.3 上线前的三样验证上线前做三样验证。第一回测用过去 30 天数据做滚动预测每天重训一次记录误差并按小时维度拆开看高峰时段误差大就优先补滞后特征。第二误差分布检查预测误差应接近正态分布出现系统性正偏或负偏时先查数据对齐逻辑而不是模型参数很多模型不行的结论最后都查出来是时区或夏令时错位。第三调度仿真把预测曲线和真实曲线分别接入调度函数对比两次调度的限功率次数差值占总时段比例应低于 5%超过这个数说明预测误差给调度带来的额外动作太多需要先降误差再谈策略优化。每周跑一次回测并按小时拆解误差比频繁换模型更能稳住调度效果。本文还有配套的精品资源点击获取