简介:这份资源聚焦光谷激光工厂的真实用电负荷,以1小时为采样粒度,面向从事能源管理、负荷预测与工业数据分析的工程师及学生。数据覆盖电压、电流、需量、电量、功率、温度及冻结表码等维度,可用于负荷趋势建模、能效对比、节能策略制定与设备异常预警等场景。压缩包共23个文件,约2.02MB,包含9个xlsx数据表、6个csv数据文件、7个m脚本和1个docx说明文档;表格承载原始与预处理数据,脚本涵盖SVM回归、ELM电力预测及数据清洗流程,文档则记录预测效果。已有127人学习下载。读者可据此复现从数据清洗、特征工程到负荷预测建模的完整链路,并借助现成脚本快速验证SVM与ELM等算法效果,为工厂能耗分析与调度优化提供可直接参考的实践素材。1. 光谷激光负荷数据:1h 粒度工厂用电曲线到底能拿来干什么如果你正在做工业负荷预测、需量管理或者储能容量配置,大概率绕不开一个问题:公开数据集里全是居民用电和气象数据,真正带工厂标签、带产线节奏的 1h 负荷曲线少得可怜。这份「光谷激光_实际工厂负荷数据_时间尺度为1h」就是冲着这个缺口来的——它记录的是某激光加工类工厂的真实用电负荷,采样间隔固定为 1 小时,属于典型的工业时序数据。适合三类人:做短期负荷预测算法验证的、做峰谷电价套利测算的、以及给微电网或储能系统做容量仿真的。它不能直接告诉你变压器该选多大,但能让你把模型跑在真实工业波形上,而不是拿居民曲线硬套。下面我按「数据长什么样 → 怎么读进来 → 怎么清洗 → 怎么建模 → 坑在哪」的顺序拆一遍。2. 先看懂 1h 工业负荷曲线的物理含义:别拿居民用电的直觉套工厂2.1 1h 采样到底丢了什么、留住了什么时间尺度为 1h,意味着一天只有 24 个点。这个粒度对居民负荷够用,因为居民用电的尖峰通常持续几十分钟到一两小时,1h 平均后还能看出早晚高峰。但工厂不一样:激光切割机、焊接机器人、空压机这类设备启停可能在几分钟内完成,1h 平均会把瞬时冲击抹平。所以你拿到这份数据后,第一件事是明确它能回答什么问题、不能回答什么问题。能回答的:日负荷率、峰谷差、班次规律、周内生产节奏、月度用电趋势、需量申报策略。不能回答的:设备级冲击电流、无功波动、谐波分析、秒级功率爬坡。常见做法是把它当作「电量结算级」数据来用,而不是「电能质量级」数据。我一般会先算三个统计量来判断这条曲线值不值得深挖:import pandas as pd # 假设数据为两列:timestamp(小时级)和 load_kw(有功功率,单位kW) df = pd.read_csv("factory_load_1h.csv", parse_dates=["timestamp"]) df = df.sort_values("timestamp").set_index("timestamp") # 日负荷率:日均负荷 / 日最大负荷,反映生产平稳程度 daily = df["load_kw"].resample("D") load_rate = (daily.mean() / daily.max()).round(3) # 峰谷差率:(最大 - 最小) / 最大,反映波动幅度 peak_valley = ((daily.max() - daily.min()) / daily.max()).round(3) # 周内规律:按星期几聚合,看周末是否停产 weekday_profile = df["load_kw"].groupby(df.index.dayofweek).mean().round(1) print(load_rate.head(10)) print(peak_valley.head(10)) print(weekday_profile)逻辑说明:resample("D")把小时数据聚合成天,mean()/max()得到日负荷率。参数上,load_kw列名要按你实际文件的列名改;如果原始数据是电量(kWh)而非功率(kW),1h 粒度下数值等价,不用换算。dayofweek返回 0 到 6,对应周一到周日,看周末均值是否明显掉下来,就能判断这家工厂是连续生产还是单班制。2.2 工业负荷的「班次指纹」怎么识别居民负荷的指纹是早晚双峰,工厂负荷的指纹是「跟班次走」。单班制工厂通常 8 点到 17 点高位、夜间接近待机功率;两班制会拖到 22 点;三班制则全天高位但仍有换班时的浅谷。识别班次指纹不需要复杂模型,画一张「小时 × 星期」的热力图就够了。import seaborn as sns import matplotlib.pyplot as plt # 构造 小时 × 星期 的透视表 df["hour"] = df.index.hour df["dow"] = df.index.dayofweek pivot = df.pivot_table(values="load_kw", index="dow", columns="hour", aggfunc="mean") sns.heatmap(pivot, cmap="YlOrRd", annot=False) plt.xlabel("Hour of Day") plt.ylabel("Day of Week (0=Mon)") plt.title("Hourly Load Heatmap") plt.show()逻辑说明:pivot_table对每个「星期几 + 小时」组合求均值,抹掉单日异常。参数上,aggfunc可以换成median,如果数据里有明显尖刺,中位数比均值更稳。热力图上如果出现「工作日整片深色、周末整片浅色」,说明是单班或双班制;如果周末仍有中等深度,可能是连续生产或存在保安、照明等基础负荷。提示:别急着把周末数据当异常删掉。很多工厂周末仍有空压机、冷冻机在跑,这部分基础负荷对储能容量配置很关键。3. 把数据读进管线的完整操作:从 CSV 到可建模的 DataFrame3.1 时间戳对齐与缺失值处理工业现场采集的数据,时间戳很少是干净的。常见问题有三种:时区不统一、夏令时跳变、采集器掉线导致整段缺失。1h 粒度下,缺失通常表现为某几个小时直接没有行,而不是空值。所以第一步不是fillna,而是reindex补全时间轴。# 生成完整的小时时间轴,从数据最小时间到最大时间 full_range = pd.date_range( start=df.index.min(), end=df.index.max(), freq="h", tz=df.index.tz # 保持原时区,避免时区错位 ) # 重新索引,缺失的小时会被标为 NaN df_full = df.reindex(full_range) df_full.index.name = "timestamp" # 统计缺失比例 missing_ratio = df_full["load_kw"].isna().mean() print(f"缺失比例: {missing_ratio:.2%}") # 短缺口(<=3h)用线性插值,长缺口标记出来单独判断 df_full["load_kw"] = df_full["load_kw"].interpolate( method="linear", limit=3, limit_direction="both" ) df_full["is_long_gap"] = df_full["load_kw"].isna()逻辑说明:reindex是补全时间轴的关键,它不会凭空造数据,而是把缺失位置显式标成 NaN,让你能统计缺口大小。interpolate的limit=3表示最多连续插 3 个小时,超过 3 小时的缺口不插值,因为工业负荷停 4 小时以上往往是停产或检修,线性插值会造出假的生产曲线。limit_direction="both"让首尾缺口也能被邻近值填充。参数上,freq="h"对应 1h 粒度,如果你的数据是半小时,改成"30min"。3.2 异常值判定:别用 3σ,用 IQR 加分位数工业负荷的分布不是正态的,用 3σ 会把正常的生产高峰当成异常。我一般用 IQR(四分位距)配合业务分位数来筛。Q1 = df_full["load_kw"].quantile(0.25) Q3 = df_full["load_kw"].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR # 标记异常,但不直接删除 df_full["is_outlier"] = ( (df_full["load_kw"] < lower_bound) | (df_full["load_kw"] > upper_bound) ) # 结合业务判断:超过 P99 且持续小于 2 小时的,才视为可疑尖刺 p99 = df_full["load_kw"].quantile(0.99) df_full["is_spike"] = ( (df_full["load_kw"] > p99) & (df_full["load_kw"].rolling(2, min_periods=1).count() < 2) ) print(df_full[df_full["is_outlier"]].head(20))逻辑说明:IQR 法对偏态分布更稳健,1.5 * IQR是经典阈值,工业场景可以放宽到2.0甚至3.0,取决于你想保留多少真实高峰。rolling(2).count()用来判断尖刺是否孤立,如果连续多个小时都超 P99,那可能是真实的生产高峰而非采集错误。参数上,quantile(0.99)可以按数据量调整,数据少于 1000 条时用0.95更合适。注意:异常值处理完一定要留一列标记,不要直接覆盖原始值。后面建模时可以把标记列作为特征,让模型自己学「哪些点不可信」。4. 基于 1h 负荷的建模与验证:从基线到特征工程4.1 三个基线模型先跑通,再谈复杂算法很多人一上来就上 LSTM、Transformer,结果连「昨天同时刻负荷」这个基线都打不过。1h 工业负荷的强周期性决定了,简单基线往往出奇地稳。我一般先跑三个:基线模型做法适用场景持久化模型用昨天同一小时的值作为预测生产节奏稳定的连续工厂周同期模型用上周同一天同一小时的值有明显周规律的工厂移动平均过去 24 小时均值波动平缓、无强班次特征# 持久化基线:t 时刻预测值 = t-24 时刻实际值 df_full["pred_persist"] = df_full["load_kw"].shift(24) # 周同期基线:t 时刻预测值 = t-168 时刻实际值(7*24) df_full["pred_weekly"] = df_full["load_kw"].shift(168) # 移动平均基线 df_full["pred_ma24"] = df_full["load_kw"].shift(1).rolling(24).mean() # 计算 MAE from sklearn.metrics import mean_absolute_error for col in ["pred_persist", "pred_weekly", "pred_ma24"]: mask = df_full[col].notna() & df_full["load_kw"].notna() mae = mean_absolute_error(df_full.loc[mask, "load_kw"], df_full.loc[mask, col]) print(f"{col} MAE: {mae:.2f} kW")逻辑说明:shift(24)和shift(168)分别对应「昨天同时刻」和「上周同时刻」,这是时序预测里最常用的两个滞后特征。shift(1).rolling(24).mean()先移一位再取 24 小时均值,避免用到当前时刻的真实值造成数据泄漏。参数上,MAE 的单位和负荷单位一致,方便直接判断误差是否可接受。如果周同期基线 MAE 明显低于持久化,说明这家工厂周规律强,后续特征工程要重点做「星期几」相关特征。4.2 特征工程:把「班次」和「电价时段」编码进去1h 粒度下,最有用的特征不是历史负荷的复杂变换,而是时间标签和业务标签。我一般会构造这几类:# 时间特征 df_full["hour"] = df_full.index.hour df_full["dow"] = df_full.index.dayofweek df_full["is_weekend"] = (df_full["dow"] >= 5).astype(int) # 班次特征:假设 8-16 为白班,16-24 为中班,0-8 为夜班 def shift_label(h): if 8 <= h < 16: return 0 # 白班 elif 16 <= h < 24: return 1 # 中班 else: return 2 # 夜班 df_full["shift"] = df_full["hour"].apply(shift_label) # 电价时段特征:假设峰段 10-12、14-19,谷段 23-7,其余平段 def price_period(h): if 10 <= h < 12 or 14 <= h < 19: return "peak" elif 23 <= h or h < 7: return "valley" else: return "flat" df_full["price_period"] = df_full["hour"].apply(price_period) # 滞后特征:过去 1、2、3、24 小时 for lag in [1, 2, 3, 24]: df_full[f"lag_{lag}"] = df_full["load_kw"].shift(lag) # 滚动统计:过去 24 小时均值和标准差 df_full["roll_mean_24"] = df_full["load_kw"].shift(1).rolling(24).mean() df_full["roll_std_24"] = df_full["load_kw"].shift(1).rolling(24).std()逻辑说明:shift_label和price_period是业务特征,它们把「工厂怎么排班」和「电费怎么算」这两件跟负荷强相关的事编码成离散变量。参数上,班次划分要按你手上工厂的实际排班改,别照搬。lag_1到lag_3捕捉短时惯性,lag_24捕捉日周期,roll_mean_24和roll_std_24分别刻画近期水平和波动。所有滚动特征都先shift(1),确保不泄漏当前时刻信息。提示:如果数据里包含节假日,一定要单独加一列is_holiday。工业负荷对节假日的响应比居民负荷更剧烈,漏掉这个特征会让模型在节后第一天误差飙升。5. 避坑与排查:1h 工厂负荷数据最容易翻车的五个地方5.1 时区没对齐,整条曲线平移几小时现象:画出来的负荷曲线高峰出现在凌晨 3 点,明显不符合生产常识。原因:采集器用 UTC 时间戳,而你以为它是本地时间,读进来后没做时区转换。解决:读数据时先pd.to_datetime(..., utc=True),再用.tz_convert("Asia/Shanghai")转到本地时区,最后统一用本地时间做特征。5.2 把「停产检修」当异常值删了现象:清洗后数据量少了 15%,模型在检修后第一天预测全错。原因:连续多天的低负荷被 IQR 判为异常,直接删除。解决:异常标记只用于建模时的样本权重调整,不要物理删除。检修段可以单独标记为is_maintenance,让模型学到「检修后负荷会爬坡」这个模式。5.3 用未来数据做滚动统计现象:离线评估 MAE 很低,上线后误差翻倍。原因:rolling(24).mean()没有shift(1),当前时刻的负荷被算进了特征。解决:所有滚动和滞后特征统一先shift(1),并在代码里加断言检查特征列与目标列的时序关系。5.4 忽略需量申报的 15 分钟窗口现象:按 1h 平均负荷算的需量申报值,实际结算时被罚。原因:需量通常按 15 分钟滑动窗口的最大值结算,1h 平均会低估尖峰。解决:如果手头只有 1h 数据,用「1h 最大值 × 1.1~1.2 系数」做保守估计,或者向现场要 15 分钟原始数据。5.5 训练集和测试集随机划分现象:模型在测试集上表现很好,但预测下个月完全不能用。原因:时序数据随机划分会让未来信息泄漏到训练集。解决:严格按时间切分,比如前 80% 时间做训练、后 20% 做测试,中间留一段 gap 避免边界泄漏。6. 进阶技巧:用 1h 数据反推储能容量与需量策略数据拿到手,最终要落到决策上。我一般会做两件事:一是用峰谷电价算储能套利空间,二是用负荷持续曲线定需量申报值。# 假设峰段电价 1.2 元/kWh,谷段 0.4 元/kWh,储能效率 0.9 peak_price = 1.2 valley_price = 0.4 efficiency = 0.9 # 按天计算峰段用电量和谷段用电量 daily = df_full.groupby(df_full.index.date) result = [] for day, group in daily: peak_kwh = group.loc[group["price_period"] == "peak", "load_kw"].sum() valley_kwh = group.loc[group["price_period"] == "valley", "load_kw"].sum() # 可转移电量取峰谷较小值,受储能功率和容量限制 shiftable = min(peak_kwh, valley_kwh * efficiency) saving = shiftable * (peak_price - valley_price / efficiency) result.append({"date": day, "peak_kwh": peak_kwh, "valley_kwh": valley_kwh, "saving_yuan": saving}) saving_df = pd.DataFrame(result) print(f"日均套利空间: {saving_df['saving_yuan'].mean():.1f} 元") print(f"年化估算: {saving_df['saving_yuan'].mean() * 330:.0f} 元")逻辑说明:peak_kwh和valley_kwh分别是峰段和谷段的电量,shiftable取两者较小值并考虑效率损耗,代表实际能搬移的电量。参数上,330是按年生产天数估的,你要按工厂实际生产天数改。efficiency=0.9是储能充放电综合效率,磷酸铁锂一般取 0.85 到 0.92。负荷持续曲线用来定需量申报值:把全年 1h 负荷从大到小排序,取第 95 百分位对应的值作为申报需量,既能覆盖大部分高峰,又不会因为个别尖刺把需量抬得过高。sorted_load = df_full["load_kw"].sort_values(ascending=False).reset_index(drop=True) declare_demand = sorted_load.quantile(0.95) print(f"建议需量申报值: {declare_demand:.1f} kW")逻辑说明:quantile(0.95)取排序后第 95 百分位,意味着允许 5% 的时间超出申报值。参数上,这个分位数可以按工厂对超需罚款的敏感度调整,罚款重就提到 0.98,罚款轻就降到 0.90。从那以后我每次拿到新的工厂负荷数据,都强制先跑一遍「时区检查 → 缺失统计 → 班次热力图 → 三个基线」这四步,确认数据没被自己读歪,再动模型。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源聚焦光谷激光工厂的真实用电负荷,以1小时为采样粒度,面向从事能源管理、负荷预测与工业数据分析的工程师及学生。数据覆盖电压、电流、需量、电量、功率、温度及冻结表码等维度,可用于负荷趋势建模、能效对比、节能策略制定与设备异常预警等场景。压缩包共23个文件,约2.02MB,包含9个xlsx数据表、6个csv数据文件、7个m脚本和1个docx说明文档;表格承载原始与预处理数据,脚本涵盖SVM回归、ELM电力预测及数据清洗流程,文档则记录预测效果。已有127人学习下载。读者可据此复现从数据清洗、特征工程到负荷预测建模的完整链路,并借助现成脚本快速验证SVM与ELM等算法效果,为工厂能耗分析与调度优化提供可直接参考的实践素材。1. 光谷激光负荷数据:1h 粒度工厂用电曲线到底能拿来干什么如果你正在做工业负荷预测、需量管理或者储能容量配置,大概率绕不开一个问题:公开数据集里全是居民用电和气象数据,真正带工厂标签、带产线节奏的 1h 负荷曲线少得可怜。这份「光谷激光_实际工厂负荷数据_时间尺度为1h」就是冲着这个缺口来的——它记录的是某激光加工类工厂的真实用电负荷,采样间隔固定为 1 小时,属于典型的工业时序数据。适合三类人:做短期负荷预测算法验证的、做峰谷电价套利测算的、以及给微电网或储能系统做容量仿真的。它不能直接告诉你变压器该选多大,但能让你把模型跑在真实工业波形上,而不是拿居民曲线硬套。下面我按「数据长什么样 → 怎么读进来 → 怎么清洗 → 怎么建模 → 坑在哪」的顺序拆一遍。2. 先看懂 1h 工业负荷曲线的物理含义:别拿居民用电的直觉套工厂2.1 1h 采样到底丢了什么、留住了什么时间尺度为 1h,意味着一天只有 24 个点。这个粒度对居民负荷够用,因为居民用电的尖峰通常持续几十分钟到一两小时,1h 平均后还能看出早晚高峰。但工厂不一样:激光切割机、焊接机器人、空压机这类设备启停可能在几分钟内完成,1h 平均会把瞬时冲击抹平。所以你拿到这份数据后,第一件事是明确它能回答什么问题、不能回答什么问题。能回答的:日负荷率、峰谷差、班次规律、周内生产节奏、月度用电趋势、需量申报策略。不能回答的:设备级冲击电流、无功波动、谐波分析、秒级功率爬坡。常见做法是把它当作「电量结算级」数据来用,而不是「电能质量级」数据。我一般会先算三个统计量来判断这条曲线值不值得深挖:import pandas as pd # 假设数据为两列:timestamp(小时级)和 load_kw(有功功率,单位kW) df = pd.read_csv("factory_load_1h.csv", parse_dates=["timestamp"]) df = df.sort_values("timestamp").set_index("timestamp") # 日负荷率:日均负荷 / 日最大负荷,反映生产平稳程度 daily = df["load_kw"].resample("D") load_rate = (daily.mean() / daily.max()).round(3) # 峰谷差率:(最大 - 最小) / 最大,反映波动幅度 peak_valley = ((daily.max() - daily.min()) / daily.max()).round(3) # 周内规律:按星期几聚合,看周末是否停产 weekday_profile = df["load_kw"].groupby(df.index.dayofweek).mean().round(1) print(load_rate.head(10)) print(peak_valley.head(10)) print(weekday_profile)逻辑说明:resample("D")把小时数据聚合成天,mean()/max()得到日负荷率。参数上,load_kw列名要按你实际文件的列名改;如果原始数据是电量(kWh)而非功率(kW),1h 粒度下数值等价,不用换算。dayofweek返回 0 到 6,对应周一到周日,看周末均值是否明显掉下来,就能判断这家工厂是连续生产还是单班制。2.2 工业负荷的「班次指纹」怎么识别居民负荷的指纹是早晚双峰,工厂负荷的指纹是「跟班次走」。单班制工厂通常 8 点到 17 点高位、夜间接近待机功率;两班制会拖到 22 点;三班制则全天高位但仍有换班时的浅谷。识别班次指纹不需要复杂模型,画一张「小时 × 星期」的热力图就够了。import seaborn as sns import matplotlib.pyplot as plt # 构造 小时 × 星期 的透视表 df["hour"] = df.index.hour df["dow"] = df.index.dayofweek pivot = df.pivot_table(values="load_kw", index="dow", columns="hour", aggfunc="mean") sns.heatmap(pivot, cmap="YlOrRd", annot=False) plt.xlabel("Hour of Day") plt.ylabel("Day of Week (0=Mon)") plt.title("Hourly Load Heatmap") plt.show()逻辑说明:pivot_table对每个「星期几 + 小时」组合求均值,抹掉单日异常。参数上,aggfunc可以换成median,如果数据里有明显尖刺,中位数比均值更稳。热力图上如果出现「工作日整片深色、周末整片浅色」,说明是单班或双班制;如果周末仍有中等深度,可能是连续生产或存在保安、照明等基础负荷。提示:别急着把周末数据当异常删掉。很多工厂周末仍有空压机、冷冻机在跑,这部分基础负荷对储能容量配置很关键。3. 把数据读进管线的完整操作:从 CSV 到可建模的 DataFrame3.1 时间戳对齐与缺失值处理工业现场采集的数据,时间戳很少是干净的。常见问题有三种:时区不统一、夏令时跳变、采集器掉线导致整段缺失。1h 粒度下,缺失通常表现为某几个小时直接没有行,而不是空值。所以第一步不是fillna,而是reindex补全时间轴。# 生成完整的小时时间轴,从数据最小时间到最大时间 full_range = pd.date_range( start=df.index.min(), end=df.index.max(), freq="h", tz=df.index.tz # 保持原时区,避免时区错位 ) # 重新索引,缺失的小时会被标为 NaN df_full = df.reindex(full_range) df_full.index.name = "timestamp" # 统计缺失比例 missing_ratio = df_full["load_kw"].isna().mean() print(f"缺失比例: {missing_ratio:.2%}") # 短缺口(<=3h)用线性插值,长缺口标记出来单独判断 df_full["load_kw"] = df_full["load_kw"].interpolate( method="linear", limit=3, limit_direction="both" ) df_full["is_long_gap"] = df_full["load_kw"].isna()逻辑说明:reindex是补全时间轴的关键,它不会凭空造数据,而是把缺失位置显式标成 NaN,让你能统计缺口大小。interpolate的limit=3表示最多连续插 3 个小时,超过 3 小时的缺口不插值,因为工业负荷停 4 小时以上往往是停产或检修,线性插值会造出假的生产曲线。limit_direction="both"让首尾缺口也能被邻近值填充。参数上,freq="h"对应 1h 粒度,如果你的数据是半小时,改成"30min"。3.2 异常值判定:别用 3σ,用 IQR 加分位数工业负荷的分布不是正态的,用 3σ 会把正常的生产高峰当成异常。我一般用 IQR(四分位距)配合业务分位数来筛。Q1 = df_full["load_kw"].quantile(0.25) Q3 = df_full["load_kw"].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR # 标记异常,但不直接删除 df_full["is_outlier"] = ( (df_full["load_kw"] < lower_bound) | (df_full["load_kw"] > upper_bound) ) # 结合业务判断:超过 P99 且持续小于 2 小时的,才视为可疑尖刺 p99 = df_full["load_kw"].quantile(0.99) df_full["is_spike"] = ( (df_full["load_kw"] > p99) & (df_full["load_kw"].rolling(2, min_periods=1).count() < 2) ) print(df_full[df_full["is_outlier"]].head(20))逻辑说明:IQR 法对偏态分布更稳健,1.5 * IQR是经典阈值,工业场景可以放宽到2.0甚至3.0,取决于你想保留多少真实高峰。rolling(2).count()用来判断尖刺是否孤立,如果连续多个小时都超 P99,那可能是真实的生产高峰而非采集错误。参数上,quantile(0.99)可以按数据量调整,数据少于 1000 条时用0.95更合适。注意:异常值处理完一定要留一列标记,不要直接覆盖原始值。后面建模时可以把标记列作为特征,让模型自己学「哪些点不可信」。4. 基于 1h 负荷的建模与验证:从基线到特征工程4.1 三个基线模型先跑通,再谈复杂算法很多人一上来就上 LSTM、Transformer,结果连「昨天同时刻负荷」这个基线都打不过。1h 工业负荷的强周期性决定了,简单基线往往出奇地稳。我一般先跑三个:基线模型做法适用场景持久化模型用昨天同一小时的值作为预测生产节奏稳定的连续工厂周同期模型用上周同一天同一小时的值有明显周规律的工厂移动平均过去 24 小时均值波动平缓、无强班次特征# 持久化基线:t 时刻预测值 = t-24 时刻实际值 df_full["pred_persist"] = df_full["load_kw"].shift(24) # 周同期基线:t 时刻预测值 = t-168 时刻实际值(7*24) df_full["pred_weekly"] = df_full["load_kw"].shift(168) # 移动平均基线 df_full["pred_ma24"] = df_full["load_kw"].shift(1).rolling(24).mean() # 计算 MAE from sklearn.metrics import mean_absolute_error for col in ["pred_persist", "pred_weekly", "pred_ma24"]: mask = df_full[col].notna() & df_full["load_kw"].notna() mae = mean_absolute_error(df_full.loc[mask, "load_kw"], df_full.loc[mask, col]) print(f"{col} MAE: {mae:.2f} kW")逻辑说明:shift(24)和shift(168)分别对应「昨天同时刻」和「上周同时刻」,这是时序预测里最常用的两个滞后特征。shift(1).rolling(24).mean()先移一位再取 24 小时均值,避免用到当前时刻的真实值造成数据泄漏。参数上,MAE 的单位和负荷单位一致,方便直接判断误差是否可接受。如果周同期基线 MAE 明显低于持久化,说明这家工厂周规律强,后续特征工程要重点做「星期几」相关特征。4.2 特征工程:把「班次」和「电价时段」编码进去1h 粒度下,最有用的特征不是历史负荷的复杂变换,而是时间标签和业务标签。我一般会构造这几类:# 时间特征 df_full["hour"] = df_full.index.hour df_full["dow"] = df_full.index.dayofweek df_full["is_weekend"] = (df_full["dow"] >= 5).astype(int) # 班次特征:假设 8-16 为白班,16-24 为中班,0-8 为夜班 def shift_label(h): if 8 <= h < 16: return 0 # 白班 elif 16 <= h < 24: return 1 # 中班 else: return 2 # 夜班 df_full["shift"] = df_full["hour"].apply(shift_label) # 电价时段特征:假设峰段 10-12、14-19,谷段 23-7,其余平段 def price_period(h): if 10 <= h < 12 or 14 <= h < 19: return "peak" elif 23 <= h or h < 7: return "valley" else: return "flat" df_full["price_period"] = df_full["hour"].apply(price_period) # 滞后特征:过去 1、2、3、24 小时 for lag in [1, 2, 3, 24]: df_full[f"lag_{lag}"] = df_full["load_kw"].shift(lag) # 滚动统计:过去 24 小时均值和标准差 df_full["roll_mean_24"] = df_full["load_kw"].shift(1).rolling(24).mean() df_full["roll_std_24"] = df_full["load_kw"].shift(1).rolling(24).std()逻辑说明:shift_label和price_period是业务特征,它们把「工厂怎么排班」和「电费怎么算」这两件跟负荷强相关的事编码成离散变量。参数上,班次划分要按你手上工厂的实际排班改,别照搬。lag_1到lag_3捕捉短时惯性,lag_24捕捉日周期,roll_mean_24和roll_std_24分别刻画近期水平和波动。所有滚动特征都先shift(1),确保不泄漏当前时刻信息。提示:如果数据里包含节假日,一定要单独加一列is_holiday。工业负荷对节假日的响应比居民负荷更剧烈,漏掉这个特征会让模型在节后第一天误差飙升。5. 避坑与排查:1h 工厂负荷数据最容易翻车的五个地方5.1 时区没对齐,整条曲线平移几小时现象:画出来的负荷曲线高峰出现在凌晨 3 点,明显不符合生产常识。原因:采集器用 UTC 时间戳,而你以为它是本地时间,读进来后没做时区转换。解决:读数据时先pd.to_datetime(..., utc=True),再用.tz_convert("Asia/Shanghai")转到本地时区,最后统一用本地时间做特征。5.2 把「停产检修」当异常值删了现象:清洗后数据量少了 15%,模型在检修后第一天预测全错。原因:连续多天的低负荷被 IQR 判为异常,直接删除。解决:异常标记只用于建模时的样本权重调整,不要物理删除。检修段可以单独标记为is_maintenance,让模型学到「检修后负荷会爬坡」这个模式。5.3 用未来数据做滚动统计现象:离线评估 MAE 很低,上线后误差翻倍。原因:rolling(24).mean()没有shift(1),当前时刻的负荷被算进了特征。解决:所有滚动和滞后特征统一先shift(1),并在代码里加断言检查特征列与目标列的时序关系。5.4 忽略需量申报的 15 分钟窗口现象:按 1h 平均负荷算的需量申报值,实际结算时被罚。原因:需量通常按 15 分钟滑动窗口的最大值结算,1h 平均会低估尖峰。解决:如果手头只有 1h 数据,用「1h 最大值 × 1.1~1.2 系数」做保守估计,或者向现场要 15 分钟原始数据。5.5 训练集和测试集随机划分现象:模型在测试集上表现很好,但预测下个月完全不能用。原因:时序数据随机划分会让未来信息泄漏到训练集。解决:严格按时间切分,比如前 80% 时间做训练、后 20% 做测试,中间留一段 gap 避免边界泄漏。6. 进阶技巧:用 1h 数据反推储能容量与需量策略数据拿到手,最终要落到决策上。我一般会做两件事:一是用峰谷电价算储能套利空间,二是用负荷持续曲线定需量申报值。# 假设峰段电价 1.2 元/kWh,谷段 0.4 元/kWh,储能效率 0.9 peak_price = 1.2 valley_price = 0.4 efficiency = 0.9 # 按天计算峰段用电量和谷段用电量 daily = df_full.groupby(df_full.index.date) result = [] for day, group in daily: peak_kwh = group.loc[group["price_period"] == "peak", "load_kw"].sum() valley_kwh = group.loc[group["price_period"] == "valley", "load_kw"].sum() # 可转移电量取峰谷较小值,受储能功率和容量限制 shiftable = min(peak_kwh, valley_kwh * efficiency) saving = shiftable * (peak_price - valley_price / efficiency) result.append({"date": day, "peak_kwh": peak_kwh, "valley_kwh": valley_kwh, "saving_yuan": saving}) saving_df = pd.DataFrame(result) print(f"日均套利空间: {saving_df['saving_yuan'].mean():.1f} 元") print(f"年化估算: {saving_df['saving_yuan'].mean() * 330:.0f} 元")逻辑说明:peak_kwh和valley_kwh分别是峰段和谷段的电量,shiftable取两者较小值并考虑效率损耗,代表实际能搬移的电量。参数上,330是按年生产天数估的,你要按工厂实际生产天数改。efficiency=0.9是储能充放电综合效率,磷酸铁锂一般取 0.85 到 0.92。负荷持续曲线用来定需量申报值:把全年 1h 负荷从大到小排序,取第 95 百分位对应的值作为申报需量,既能覆盖大部分高峰,又不会因为个别尖刺把需量抬得过高。sorted_load = df_full["load_kw"].sort_values(ascending=False).reset_index(drop=True) declare_demand = sorted_load.quantile(0.95) print(f"建议需量申报值: {declare_demand:.1f} kW")逻辑说明:quantile(0.95)取排序后第 95 百分位,意味着允许 5% 的时间超出申报值。参数上,这个分位数可以按工厂对超需罚款的敏感度调整,罚款重就提到 0.98,罚款轻就降到 0.90。从那以后我每次拿到新的工厂负荷数据,都强制先跑一遍「时区检查 → 缺失统计 → 班次热力图 → 三个基线」这四步,确认数据没被自己读歪,再动模型。希望帮到你。本文还有配套的精品资源,点击获取