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。具体数值随随机种子固定而稳定,可直接复现。

峰谷形态差异的判读

平滑曲线上的三特征时段与季节形态对比 流量 时刻 0612 1824 夜间基流 01–05 早高峰 06–10 午间低谷 11–15 春峰 8时 夏峰 7时 春谷 13时 夏谷 13–14时 春季平滑流量 夏季平滑流量 提取的特征点 形态差异判读 夏季峰更高、峰基比更大 夏季峰时刻提前约 1 小时 夏季午间低谷抬升、变浅 夜间基流整体上移

判读要点:峰基比(早高峰流量 / 夜间基流)刻画峰谷起伏的相对强度,比绝对流量更适合跨季节比较;峰时刻与谷时刻的偏移反映用水节律变化;午间低谷相对基流的下降幅度(谷基比)反映白天中段用水回落程度。若某日峰时刻明显偏离窗口中心或峰基比异常偏低,应回查该日原始序列是否存在泵切换尖峰或降雨稀释,而不是直接归因于用水规律改变。

结果核对与常见偏差

  • 平滑窗口与峰时刻:窗口越长,峰越平、峰时刻越易后移。若发现峰时刻系统性偏晚,先检查窗口长度是否过大。
  • 边界效应:0 时与 23 时附近平滑值受 min_periods 影响,夜间基流窗口取 01–05 时正好避开边界。
  • 尖峰残留:5 小时窗口对单点尖峰抑制有限,若原始序列尖峰幅度超过 300 m³/h,可先做 3σ 剔除再平滑,但需在报告中注明处理规则。
  • 季节样本量:本例每季仅 4 天,聚合均值只用于演示方法;实际应用建议每季不少于 15 天,并同时给出标准差或四分位区间。
把提取结果按“日期—峰流量—峰时刻—谷流量—谷时刻—基流—峰基比”存成一张特征表,后续做工艺调整或与水质指标关联时可直接复用,无需重复平滑。