You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

寻求Python中满足均值一致性的时间序列插值方法实现

寻求Python中满足均值一致性的时间序列插值方法实现

我完全懂你的需求——手里的低分辨率时间序列每个点都是对应区间的平均值,现在要上采样到更高粒度,但必须保证每个原始区间内的高分辨率数据平均值严格等于原始值,还不想用生硬的分段常数,想要平滑的曲线对吧?下面给你几个实用的Python实现方案:

一、基准方案:分段常数插值(保均值但不够平滑)

这是最直接满足均值约束的方法,每个原始区间内的高分辨率点都取原始值,平均值自然和原始值一致。用Pandas就能快速实现:

import pandas as pd

# 原始数据
s_resample_test = pd.Series(
    [0, 1, 2, 1, 0, -1, -2, -1],
    index=pd.date_range("2025-01-01 00:30:00", periods=8, freq="1h")
)

# 上采样到15分钟分辨率,用分段常数填充
s_constant = s_resample_test.resample("15min").ffill()

# 验证均值:检查每个原始区间的平均值是否匹配
for i in range(len(s_resample_test)):
    start = s_resample_test.index[i] - pd.Timedelta(minutes=30)
    end = start + pd.Timedelta(hours=1)
    avg = s_constant[start:end].mean()
    print(f"区间{start}~{end}均值:{avg},原始值:{s_resample_test.iloc[i]}")

这个方法完全符合你的均值要求,但缺点是曲线呈阶梯状,平滑度不足。

二、约束平滑方案:兼顾平滑与均值一致性

如果想要平滑曲线,我们可以把问题转化为带约束的优化问题,要么先插值再调整,要么直接在插值过程中加入均值约束。

方法1:插值后归一化调整

先做普通的平滑插值(比如三次样条),再对每个区间做缩放调整,让其平均值符合原始值:

import pandas as pd
import numpy as np
from scipy.interpolate import CubicSpline

# 原始数据
s_resample_test = pd.Series(
    [0, 1, 2, 1, 0, -1, -2, -1],
    index=pd.date_range("2025-01-01 00:30:00", periods=8, freq="1h")
)

# 生成高分辨率时间索引(15分钟间隔)
high_res_idx = pd.date_range(
    start=s_resample_test.index[0] - pd.Timedelta(minutes=30),
    end=s_resample_test.index[-1] + pd.Timedelta(minutes=30),
    freq="15min"
)

# 把时间转成时间戳(便于插值计算)
original_times = s_resample_test.index.astype(np.int64) // 10**9
original_values = s_resample_test.values

# 三次样条插值得到初始平滑曲线
cs = CubicSpline(original_times, original_values)
initial_interp = cs(high_res_idx.astype(np.int64) // 10**9)
s_initial = pd.Series(initial_interp, index=high_res_idx)

# 逐区间调整,保证均值匹配原始值
s_adjusted = s_initial.copy()
for time, val in s_resample_test.items():
    interval_start = time - pd.Timedelta(minutes=30)
    interval_end = interval_start + pd.Timedelta(hours=1)
    interval_data = s_initial[interval_start:interval_end]
    current_avg = interval_data.mean()
    
    # 处理均值为0的特殊情况
    if not np.isclose(current_avg, 0):
        adjust_factor = val / current_avg
        s_adjusted[interval_start:interval_end] = interval_data * adjust_factor
    else:
        s_adjusted[interval_start:interval_end] = 0

# 验证约束
for time, val in s_resample_test.items():
    interval_start = time - pd.Timedelta(minutes=30)
    interval_end = interval_start + pd.Timedelta(hours=1)
    adjusted_avg = s_adjusted[interval_start:interval_end].mean()
    assert np.allclose(adjusted_avg, val), f"区间{interval_start}均值不匹配"

这个方法实现简单,曲线平滑,缺点是如果原始区间初始插值均值接近0,缩放可能会有小幅波动,但代码里已经做了特殊处理。

方法2:带约束的最小二乘平滑

直接把问题建模为约束优化问题:寻找平滑的高分辨率序列,满足每个原始区间均值等于原始值,同时让曲线的平滑度(二阶导数平方和)最小。用Scipy的优化模块就能实现:

import pandas as pd
import numpy as np
from scipy.optimize import minimize

# 原始数据
s_resample_test = pd.Series(
    [0, 1, 2, 1, 0, -1, -2, -1],
    index=pd.date_range("2025-01-01 00:30:00", periods=8, freq="1h")
)

# 高分辨率索引(15分钟,共32个点,对应8个原始区间)
high_res_idx = pd.date_range(
    start="2025-01-01 00:00:00",
    end="2025-01-01 08:00:00",
    freq="15min"
)
n_high = len(high_res_idx)
n_low = len(s_resample_test)

# 构建均值约束:每个区间的高分辨率点之和 = 原始值 * 区间内点数
constraints = []
for i in range(n_low):
    coeffs = np.zeros(n_high)
    coeffs[i*4 : (i+1)*4] = 1
    constraints.append({
        'type': 'eq',
        'fun': lambda x, idx=i: np.sum(x[idx*4 : (idx+1)*4]) - 4 * s_resample_test.iloc[idx]
    })

# 目标函数:最小化曲线的不平滑度(二阶差分平方和)
def smoothness_objective(x):
    second_diff = np.diff(x, n=2)
    return np.sum(second_diff ** 2)

# 初始猜测:用分段常数作为起点
x0 = np.repeat(s_resample_test.values, 4)

# 求解约束优化问题
result = minimize(smoothness_objective, x0, constraints=constraints, method='SLSQP')

# 得到最终平滑序列
s_smooth_constrained = pd.Series(result.x, index=high_res_idx)

# 验证你提供的avg_mtx约束(忽略边界)
init_mtx = np.eye(32)[0::4,:]
avg_mtx = np.empty((init_mtx.shape[0], init_mtx.shape[1] + 4))
for idx in range(init_mtx.shape[0]):
    avg_mtx[idx, :] = np.convolve([.5, 1, 1, 1, .5], init_mtx[idx,:])
avg_mtx /= avg_mtx.sum(axis=1).reshape(-1,1)
avg_mtx = avg_mtx[1:-1,2:-5]

assert np.allclose(avg_mtx @ s_smooth_constrained.values, s_resample_test[1:-1].values)

这个方法直接在优化过程中嵌入均值约束,得到的曲线既满足均值要求,又能达到最优平滑度,适合对曲线平滑性要求较高的场景。

三、进阶方案:自定义分段多项式插值

如果你想要更精细的控制,可以自定义分段多项式(比如二次或三次多项式),让每个区间的积分(对应平均值)等于原始值,同时保证区间端点的导数连续,实现无缝平滑。不过这个方法实现起来稍复杂,需要手动推导多项式系数,适合有一定数值计算基础的场景。


总结一下:

  • 追求简单直接:选分段常数插值
  • 兼顾平滑与实现成本:选插值后归一化调整
  • 追求最优平滑效果:选约束最小二乘平滑

备注:内容来源于stack exchange,提问作者Thomas Arildsen

火山引擎 最新活动