1. 压窜诊断的数据准备与曲线对齐
为什么“对齐”是压窜诊断的第一道门槛
邻井压裂干扰诊断的本质,是在同一时间轴上比较两口井的压力响应。现场拿到的数据往往来自不同采集系统:生产井压力来自SCADA/RTU,采样间隔可能是1 min或5 min;邻井压裂施工数据来自压裂车组记录仪,采样间隔常为1 s,且时间戳可能是本地时间、UTC或设备内部时钟。若直接叠加,压窜起跳点会被人为错位几分钟到几十分钟,导致“谁先响应、响应滞后多久”这类关键判据完全失真。
本案例针对一口受邻井压裂干扰的生产井(记为A井),完成三件事:压力数据清洗(剔除异常点、统一单位与量纲)、时间戳统一(全部换算到同一时区与同一时间基准)、多井曲线对齐(重采样到统一时间栅格并做时钟偏差校正),最终输出可直接用于压窜识别的标准化压力序列。
数据对象与字段约定
假设手上有三份原始数据(示例数据,非实测):
- A井井口压力:字段
ts_local(本地时间,格式YYYY-MM-DD HH:MM:SS)、p_MPa(单位MPa),采样间隔5 min,存在少量重复时间戳与跳变毛刺。 - B井压裂施工压力/排量:字段
ts_utc(UTC时间)、p_frac_MPa、rate_m3min,采样间隔1 s。 - 井时钟偏差记录:现场校时记录显示A井RTU时钟比标准时间慢 92 s(示例值),需在统一时间基准时补偿。
约定:所有时间最终统一为UTC,所有压力统一为MPa(表压),统一时间栅格为1 min。参照约定一旦确定,后续正文、代码与图中保持一致。
处理链路与对齐逻辑
下图给出从原始数据到标准化序列的完整处理链,重点标出时钟偏差补偿与重采样两个容易出错的环节:
关键实现:清洗、时间统一与重采样
下面给出可直接运行的 Python 实现(依赖 pandas、numpy)。示例数据在代码内构造,便于独立验证;实际使用时替换为读取 CSV 即可。
import pandas as pd
import numpy as np
# ---------- 1. 构造示例原始数据(非实测) ----------
# A井:本地时间,5 min 间隔,含重复戳与毛刺
tA = pd.date_range("2024-05-01 08:00:00", periods=60, freq="5min")
pA = 28.0 + np.linspace(0, -1.2, 60) # 正常递减趋势
pA[20] = 45.0 # 人为毛刺
dfA = pd.DataFrame({"ts_local": tA, "p_MPa": pA})
dfA = pd.concat([dfA, dfA.iloc[[10]]], ignore_index=True) # 重复时间戳
# B井:UTC时间,1 s 间隔,施工段
tB = pd.date_range("2024-05-01 00:00:00", periods=3600, freq="1s")
pB = np.where((tB >= "2024-05-01 00:20:00") & (tB < "2024-05-01 00:40:00"),
60 + 8*np.sin(np.linspace(0, 6.28, 3600)), np.nan)
rB = np.where(~np.isnan(pB), 12.0, 0.0)
dfB = pd.DataFrame({"ts_utc": tB, "p_frac_MPa": pB, "rate_m3min": rB})
# ---------- 2. 清洗 ----------
# A井:去重(保留最后一条)、去毛刺(3σ 或绝对阈值)
dfA = dfA.drop_duplicates(subset="ts_local", keep="last").sort_values("ts_local")
med = dfA["p_MPa"].rolling(5, center=True, min_periods=1).median()
dfA["p_MPa"] = np.where((dfA["p_MPa"] - med).abs() > 5.0, med, dfA["p_MPa"])
# B井:仅保留有效施工段(排量>0 且压力非空)
dfB = dfB[(dfB["rate_m3min"] > 0) & dfB["p_frac_MPa"].notna()].copy()
# ---------- 3. 时间戳统一到 UTC ----------
# 假设本地时区为 UTC+8,A井RTU慢92s → 真实UTC = 本地 - 8h + 92s
TZ_OFFSET = pd.Timedelta(hours=8)
CLOCK_SKEW = pd.Timedelta(seconds=92)
dfA["ts_utc"] = dfA["ts_local"] - TZ_OFFSET + CLOCK_SKEW
dfA = dfA[["ts_utc", "p_MPa"]].set_index("ts_utc").sort_index()
dfB = dfB.set_index("ts_utc").sort_index()
# ---------- 4. 重采样到 1 min 统一栅格 ----------
start = min(dfA.index.min(), dfB.index.min()).floor("min")
end = max(dfA.index.max(), dfB.index.max()).ceil("min")
grid = pd.date_range(start, end, freq="1min")
A_1min = dfA["p_MPa"].reindex(dfA.index.union(grid)).interpolate("time").reindex(grid)
B_1min = dfB["p_frac_MPa"].resample("1min").mean().reindex(grid)
R_1min = dfB["rate_m3min"].resample("1min").mean().reindex(grid)
aligned = pd.DataFrame({"p_A_MPa": A_1min, "p_frac_MPa": B_1min, "rate_m3min": R_1min})
print(aligned.head())
print("对齐后长度:", len(aligned), "时间范围:", aligned.index.min(), "→", aligned.index.max())
结果判读与对齐校验
运行后应检查三点,任何一点不满足都说明对齐环节有问题:
- 时间范围一致:
aligned的索引从两井最早时刻到最晚时刻,步长严格1 min,无缺口。 - 补偿量可验证:把
CLOCK_SKEW改为0再跑一次,A井压力序列整体应平移92 s;若平移方向相反,说明补偿符号写反了。 - 起跳点对齐:在B井排量开始大于0的时刻附近,A井压力若出现同步抬升,则两井起跳时刻差应≤1 min;若差出数分钟,优先怀疑时区偏移或时钟偏差未补偿。
不要对缺失段做前向填充后再参与压窜判读:前向填充会把停泵期的恒定值伪装成“压力平台”,掩盖真实响应。缺失就保留为 NaN,在后续诊断中显式跳过。
若现场无法获得校时记录,可用两井共同事件(如B井开泵瞬间的A井压力微扰动)做互相关,估计时钟偏差;但该方法需要足够信噪比,且估计结果应记录为“估计值”而非实测值。