2. 回灌量与回灌压力时序曲线绘制与异常识别

双纵轴时序曲线的数据组织

回灌量与回灌压力量纲不同(m³/h 与 MPa),必须使用双纵轴。绘图前先把清洗后的数据整理成统一时间索引的两列:time、flow、pressure。左轴对应流量,右轴对应压力,两条曲线用不同颜色区分,并在图例中注明单位。这样做的目的是让“流量下降”和“压力升高”在同一时间坐标上可以直接对照,而不是靠记忆换算。

下面给出一个可直接运行的 Python 示例,使用 pandas 与 matplotlib 完成绘图与异常标注。示例数据为教学构造,用于演示方法,不代表真实试验结果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 构造教学示例数据:30 分钟步长,共 48 个点(24 小时)
rng = pd.date_range("2024-05-01 00:00", periods=48, freq="30min")
flow = np.full(48, 25.0)          # 设计回灌流量 25 m3/h
press = np.full(48, 0.32)         # 设计井口压力 0.32 MPa

# 注入两段异常:第 12~15 点流量骤降;第 30~33 点压力异常升高
flow[12:16] = [18.0, 12.5, 9.8, 14.0]
press[30:34] = [0.41, 0.47, 0.52, 0.45]

df = pd.DataFrame({"time": rng, "flow": flow, "pressure": press}).set_index("time")

# 滑动窗口统计(窗口 5 点,中心对齐)
df["flow_ma"] = df["flow"].rolling(5, center=True, min_periods=1).mean()
df["flow_std"] = df["flow"].rolling(5, center=True, min_periods=1).std().fillna(0)
df["press_ma"] = df["pressure"].rolling(5, center=True, min_periods=1).mean()

# 阈值规则
FLOW_DROP = 0.20      # 相对设计流量下降超过 20% 判为骤降
PRESS_HIGH = 0.40     # 井口压力超过 0.40 MPa 判为异常升高
df["flow_anom"] = df["flow"] < 25.0 * (1 - FLOW_DROP)
df["press_anom"] = df["pressure"] > PRESS_HIGH

fig, ax1 = plt.subplots(figsize=(11, 4.5))
ax1.plot(df.index, df["flow"], color="#1f77b4", lw=1.8, label="回灌流量 (m³/h)")
ax1.plot(df.index, df["flow_ma"], color="#1f77b4", ls="--", lw=1.0, alpha=0.7, label="流量滑动均值")
ax1.set_ylabel("回灌流量 (m³/h)", color="#1f77b4")
ax1.tick_params(axis="y", labelcolor="#1f77b4")
ax1.set_ylim(0, 30)

ax2 = ax1.twinx()
ax2.plot(df.index, df["pressure"], color="#d62728", lw=1.8, label="井口压力 (MPa)")
ax2.plot(df.index, df["press_ma"], color="#d62728", ls="--", lw=1.0, alpha=0.7, label="压力滑动均值")
ax2.set_ylabel("井口压力 (MPa)", color="#d62728")
ax2.tick_params(axis="y", labelcolor="#d62728")
ax2.set_ylim(0.2, 0.6)

# 异常时段底色标注
for t in df.index[df["flow_anom"]]:
    ax1.axvspan(t - pd.Timedelta("15min"), t + pd.Timedelta("15min"),
                color="#ffd8a8", alpha=0.5, lw=0)
for t in df.index[df["press_anom"]]:
    ax1.axvspan(t - pd.Timedelta("15min"), t + pd.Timedelta("15min"),
                color="#ffc9c9", alpha=0.5, lw=0)

# 操作事件标注
ax1.annotate("疑似过滤器堵塞\n流量骤降", xy=(df.index[13], 9.8),
             xytext=(df.index[6], 4), fontsize=9,
             arrowprops=dict(arrowstyle="->", color="#e8590c"))
ax2.annotate("疑似阀门误调\n压力升高", xy=(df.index[32], 0.52),
             xytext=(df.index[38], 0.55), fontsize=9,
             arrowprops=dict(arrowstyle="->", color="#c92a2a"))

lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left", fontsize=8)
ax1.set_xlabel("时间")
plt.title("回灌量与井口压力双纵轴时序曲线(教学示例)")
plt.tight_layout()
plt.show()

异常识别的判读逻辑

阈值规则负责“抓点”,滑动窗口统计负责“看趋势”,两者结合才能区分瞬时波动与持续异常。示例中流量骤降用相对设计流量的 20% 作为下限,压力异常用 0.40 MPa 作为上限;这两个阈值应根据实际设计参数和井口承压能力调整,不能照搬。滑动均值曲线若同步偏离,说明异常具有持续性;若原始曲线偏离而均值基本平稳,则更可能是单点噪声。

回灌量与井口压力双纵轴时序曲线(教学示例) 0 6 12 18 24 30 回灌流量 (m³/h) 0.20 0.28 0.36 0.44 0.52 0.60 井口压力 (MPa) 00:00 06:00 12:00 18:00 24:00 流量下限 20 m³/h 压力上限 0.40 MPa 疑似过滤器堵塞 → 流量骤降 疑似阀门误调 → 压力升高 回灌流量 流量滑动均值 井口压力 压力滑动均值 流量异常时段 压力异常时段

结果判读与操作事件对应

图中橙色底色段对应流量骤降,红色底色段对应压力异常升高。判读时按以下顺序核对:

  • 流量骤降段:原始流量跌破 20 m³/h 下限,滑动均值同步下移,说明不是单点噪声。常见对应事件为过滤器堵塞、回灌泵降频或管路阀门误关,需结合现场操作记录确认。
  • 压力升高段:井口压力越过 0.40 MPa 上限,滑动均值同步抬升。常见对应事件为下游阀门误调、管路局部堵塞或回灌层位吸水能力下降。
  • 流量与压力反向变化:若流量下降同时压力升高,优先怀疑管路阻力增大;若流量下降同时压力也下降,则更可能是泵或水源侧问题。
阈值不是固定值。建议先用设计流量和井口允许压力确定初值,再结合滑动窗口标准差动态调整:当窗口内标准差明显增大时,可适当放宽阈值,避免把正常波动误判为异常。
异常时段标注必须与操作记录时间对齐后再下结论。仅凭曲线形态不能断定具体原因,图中“疑似”标注只是排查起点,最终判断需结合现场阀门开度、泵频率和过滤器压差等辅助信息。