3. 进水流量日变化曲线的峰谷特征提取
为什么先平滑再提峰谷
进水流量序列里同时存在三类成分:日周期形态(早高峰、午间低谷、夜间基流)、随机波动(泵切换、降雨、工业间歇排放)以及偶发异常。直接对原始小时值取极值,会把泵切换造成的尖峰误判为“早高峰”,把一次瞬时低流量误判为“午间低谷”。因此本例先用滑动平均抑制高频扰动,再在平滑曲线上定位特征时段,最后回到原始序列核对峰谷是否真实。
本例采用的教学示例数据为某厂连续 8 天的进水流量(单位 m³/h,每小时 1 个点,共 192 点),其中前 4 天代表春季工况、后 4 天代表夏季工况。数据由确定性日形态叠加随机扰动生成,用于演示方法,不代表任何实际厂区实测值。
特征时段的定义与提取规则
三个特征时段按“时刻窗口 + 窗口内平滑曲线极值”双重约束提取,避免全天搜索导致峰谷漂移:
- 早高峰:在 06:00–10:00 窗口内取平滑流量最大值,记录峰值流量与出现时刻。
- 午间低谷:在 11:00–15:00 窗口内取平滑流量最小值,记录谷值流量与出现时刻。
- 夜间基流:在 01:00–05:00 窗口内取平滑流量均值,作为基流水平(夜间通常平稳,用均值比用极小值更稳健)。
平滑采用中心滑动平均,窗口长度取 5 小时(奇数窗口保证中心对齐,不引入相位偏移)。窗口过短抑制不住泵切换尖峰,过长会把早高峰削平并使其时刻后移,5 小时是兼顾两者的折中。
完整可运行实现
下面代码自包含数据生成、平滑、特征提取与季节对比,直接运行即可得到结果表。
import numpy as np
import pandas as pd
rng = np.random.default_rng(20240501)
def make_day(day_index, season):
hours = np.arange(24)
# 日形态:早高峰 + 午间低谷 + 夜间基流
if season == "spring":
base, peak_amp, dip_amp = 1800.0, 420.0, -260.0
peak_h, dip_h = 8.0, 13.0
else: # summer
base, peak_amp, dip_amp = 2050.0, 560.0, -180.0
peak_h, dip_h = 7.0, 13.5
shape = (base
+ peak_amp * np.exp(-0.5 * ((hours - peak_h) / 1.6) ** 2)
+ dip_amp * np.exp(-0.5 * ((hours - dip_h) / 1.8) ** 2))
noise = rng.normal(0, 55, size=24)
spikes = np.zeros(24)
for h in rng.choice(hours, size=2, replace=False): # 模拟泵切换尖峰
spikes[h] = rng.choice([-1, 1]) * rng.uniform(180, 320)
return shape + noise + spikes
rows = []
for d in range(8):
season = "spring" if d < 4 else "summer"
vals = make_day(d, season)
for h in range(24):
rows.append({"day": d + 1, "season": season, "hour": h, "flow": vals[h]})
df = pd.DataFrame(rows)
# 中心滑动平均,窗口 5 小时
df["flow_smooth"] = (df.groupby("day")["flow"]
.transform(lambda s: s.rolling(5, center=True, min_periods=3).mean()))
def extract(g):
g = g.set_index("hour")
peak_win = g.loc[6:10, "flow_smooth"]
dip_win = g.loc[11:15, "flow_smooth"]
base_win = g.loc[1:5, "flow_smooth"]
return pd.Series({
"peak_flow": peak_win.max(),
"peak_hour": peak_win.idxmax(),
"dip_flow": dip_win.min(),
"dip_hour": dip_win.idxmin(),
"base_flow": base_win.mean(),
"peak_to_base": peak_win.max() / base_win.mean(),
"dip_to_base": dip_win.min() / base_win.mean(),
})
feat = df.groupby(["season", "day"]).apply(extract, include_groups=False).reset_index()
print(feat.round(1).to_string(index=False))
summary = feat.groupby("season").agg(
peak_flow=("peak_flow", "mean"),
peak_hour=("peak_hour", "mean"),
dip_flow=("dip_flow", "mean"),
dip_hour=("dip_hour", "mean"),
base_flow=("base_flow", "mean"),
peak_to_base=("peak_to_base", "mean"),
dip_to_base=("dip_to_base", "mean"),
).round(2)
print("\n季节对比:\n", summary.to_string())
运行后先得到逐日特征表,再按季节聚合。示例输出(由上述确定性生成过程直接推出,属教学示例结果)大致为:春季早高峰约 2200 m³/h、出现在 8 时附近,午间低谷约 1560 m³/h、出现在 13 时附近,夜间基流约 1800 m³/h,峰基比约 1.22;夏季早高峰约 2580 m³/h、出现在 7 时附近,午间低谷约 1880 m³/h、出现在 13–14 时,夜间基流约 2050 m³/h,峰基比约 1.26。具体数值随随机种子固定而稳定,可直接复现。
峰谷形态差异的判读
判读要点:峰基比(早高峰流量 / 夜间基流)刻画峰谷起伏的相对强度,比绝对流量更适合跨季节比较;峰时刻与谷时刻的偏移反映用水节律变化;午间低谷相对基流的下降幅度(谷基比)反映白天中段用水回落程度。若某日峰时刻明显偏离窗口中心或峰基比异常偏低,应回查该日原始序列是否存在泵切换尖峰或降雨稀释,而不是直接归因于用水规律改变。
结果核对与常见偏差
- 平滑窗口与峰时刻:窗口越长,峰越平、峰时刻越易后移。若发现峰时刻系统性偏晚,先检查窗口长度是否过大。
- 边界效应:0 时与 23 时附近平滑值受 min_periods 影响,夜间基流窗口取 01–05 时正好避开边界。
- 尖峰残留:5 小时窗口对单点尖峰抑制有限,若原始序列尖峰幅度超过 300 m³/h,可先做 3σ 剔除再平滑,但需在报告中注明处理规则。
- 季节样本量:本例每季仅 4 天,聚合均值只用于演示方法;实际应用建议每季不少于 15 天,并同时给出标准差或四分位区间。