寻求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




