Python中满足月度均值约束的月度转日度三次样条插值
带月度均值约束的三次样条插值方案
针对你的需求——生成满足月度均值约束的日度三次样条插值数据,有两种实用方案,以下是具体实现细节:
方案一:插值后均值调整(简单高效)
常规三次样条插值仅保证节点值匹配,无法直接满足区间均值约束。最快捷的解决方式是先完成插值,再对每个月的结果做整体偏移调整,既保留样条的平滑趋势,又严格满足月度均值要求。
代码实现
import pandas as pd import numpy as np from scipy.interpolate import CubicSpline # 1. 准备原始月度数据 monthly_data = pd.DataFrame({ 'Month': ['01/10/2024', '01/11/2024', '01/12/2024'], 'Value': [100, 150, 400] }) monthly_data['Month'] = pd.to_datetime(monthly_data['Month'], format='%d/%m/%Y') monthly_data['Month_Period'] = monthly_data['Month'].dt.to_period('M') monthly_mean_map = monthly_data.set_index('Month_Period')['Value'].to_dict() # 2. 生成完整日度日期序列 date_range = pd.date_range(start='2024-10-01', end='2024-12-31', freq='D') # 3. 执行常规三次样条插值 # 将月度日期转换为起始日的偏移天数作为插值x轴 x_nodes = (monthly_data['Month'] - date_range[0]).dt.days.values y_nodes = monthly_data['Value'].values cs = CubicSpline(x_nodes, y_nodes) daily_interpolated = cs((date_range - date_range[0]).dt.days.values) # 4. 构建初始日度DataFrame并按月份调整均值 daily_df = pd.DataFrame({ 'Date': date_range, 'Interpolated_Value': daily_interpolated, 'Month': date_range.to_period('M') }) # 分组调整:给每个月的插值数据加偏移量,使均值匹配原始月度值 adjusted_daily_df = daily_df.groupby('Month').apply( lambda group: group.assign( Adjusted_Value=group['Interpolated_Value'] + (monthly_mean_map[group.name] - group['Interpolated_Value'].mean()) ) ).reset_index(drop=True) # 验证月度均值(可选) print(adjusted_daily_df.groupby('Month')['Adjusted_Value'].mean())
原理说明
该方法通过整体偏移每个月的插值结果来满足均值约束,完全保留了三次样条的平滑曲线形状,仅调整整体高度,实现成本极低,适合绝大多数业务场景。
方案二:带均值约束的优化型三次样条(严谨可控)
若要求插值曲线本身就满足均值约束(而非事后调整),可以通过scipy.optimize优化样条的边界导数,使每个月的曲线积分等于「月度均值×当月天数」,同时保证曲线平滑性。
代码实现
import pandas as pd import numpy as np from scipy.interpolate import CubicSpline from scipy.optimize import minimize # 1. 准备原始数据与日期序列 monthly_data = pd.DataFrame({ 'Month': ['01/10/2024', '01/11/2024', '01/12/2024'], 'Value': [100, 150, 400] }) monthly_data['Month'] = pd.to_datetime(monthly_data['Month'], format='%d/%m/%Y') monthly_data['days_in_month'] = monthly_data['Month'].dt.days_in_month date_range = pd.date_range(start='2024-10-01', end='2024-12-31', freq='D') # 2. 定义插值节点与目标积分值 x_nodes = (monthly_data['Month'] - date_range[0]).dt.days.values y_nodes = monthly_data['Value'].values target_integrals = (monthly_data['Value'] * monthly_data['days_in_month']).values # 3. 定义优化目标函数:最小化积分误差+曲线曲率(保证平滑) def objective(derivatives): # 用指定的边界导数构建三次样条 cs = CubicSpline(x_nodes, y_nodes, bc_type=((1, derivatives[0]), (1, derivatives[1]))) # 计算每个月的积分值 integrals = [ cs.integrate(0, 30), # 10月:第0天(10/1)到第30天(10/31) cs.integrate(31, 60), # 11月:第31天(11/1)到第60天(11/30) cs.integrate(61, 91) # 12月:第61天(12/1)到第91天(12/31) ] # 积分误差平方和 + 曲率惩罚项(控制平滑度) error = np.sum((np.array(integrals) - target_integrals)**2) curvature = np.sum(cs(x_nodes, 2)**2) return error + 0.01 * curvature # 4. 优化边界导数 initial_guess = [0, 0] # 初始导数猜测值 result = minimize(objective, initial_guess, method='L-BFGS-B') # 5. 生成满足约束的日度插值数据 optimal_cs = CubicSpline(x_nodes, y_nodes, bc_type=((1, result.x[0]), (1, result.x[1]))) optimal_daily_values = optimal_cs((date_range - date_range[0]).dt.days.values) optimal_daily_df = pd.DataFrame({ 'Date': date_range, 'Optimal_Value': optimal_daily_values }) # 验证月度均值(可选) print(optimal_daily_df.groupby(optimal_daily_df['Date'].dt.to_period('M'))['Optimal_Value'].mean())
原理说明
该方法通过优化样条的边界导数,让每个月的曲线积分严格匹配目标值,同时加入曲率惩罚项保证曲线平滑。实现相对复杂,但能得到原生满足约束的插值曲线,适合对曲线形态有严格要求的场景。
内容的提问来源于stack exchange,提问作者user24834135
相关产品推荐
相关产品推荐

