如何用Python将月度预测值平滑拆分至每日?
月度销售预测值的跨月平滑日拆分实现方案
要实现月度预测值的跨月平滑日拆分(避免简单均分的数值突变,同时保证月度总和/均值匹配原预测),没有直接开箱即用的numpy/pandas函数,需要结合日期处理、权重计算和自定义逻辑来实现,以下是具体方案:
核心思路
- 跨月过渡加权:在月初/月末的过渡周期内,让相邻月份的预测值参与当日预测的加权计算,消除月末到月初的数值突变。
- 总和校准:初步计算后,按月份调整每日值的总和,确保完全匹配原月度预测值(均值自然符合要求)。
- 灵活权重曲线:用余弦/线性曲线生成过渡权重,比生硬的线性过渡更平滑。
关键依赖函数
- pandas的
dt属性:提取日期的年、月、日、当月天数等字段 - pandas的
melt:将宽表(jan/feb...列)转长表,方便关联每日数据 - pandas的
merge:关联当月及相邻月份的预测值 - numpy的
cos:生成平滑的余弦过渡权重
完整代码实现
1. 数据预处理
先将月度预测的宽表转长表,同时处理每日数据的日期字段:
import pandas as pd import numpy as np # 转换月度预测宽表为长表 df_monthly = dataframe1.melt( id_vars=['id'], var_name='month_str', value_name='monthly_pred' ) # 将月份字符串转为数字(jan→1,feb→2...) df_monthly['month'] = pd.to_datetime(df_monthly['month_str'], format='%b').dt.month df_monthly = df_monthly.drop('month_str', axis=1) # 处理每日数据的日期字段 df_daily = dataframe2.copy() df_daily['date'] = pd.to_datetime(df_daily['date'], format='%d-%m-%Y') df_daily['year'] = df_daily['date'].dt.year df_daily['month'] = df_daily['date'].dt.month df_daily['day_of_month'] = df_daily['date'].dt.day df_daily['days_in_month'] = df_daily['date'].dt.days_in_month
2. 关联当月及相邻月份预测值
处理跨年场景(如1月的上月为去年12月),关联上月、当月、下月的预测值:
# 关联当月预测 df_daily = df_daily.merge(df_monthly, on=['id', 'month'], how='left') # 关联上月预测(处理跨年) df_prev_month = df_monthly.copy() df_prev_month['month'] = df_prev_month['month'] + 1 df_prev_month.loc[df_prev_month['month'] == 13, 'month'] = 1 df_prev_month.rename(columns={'monthly_pred': 'prev_month_pred'}, inplace=True) df_daily = df_daily.merge(df_prev_month, on=['id', 'month'], how='left') # 关联下月预测(处理跨年) df_next_month = df_monthly.copy() df_next_month['month'] = df_next_month['month'] - 1 df_next_month.loc[df_next_month['month'] == 0, 'month'] = 12 df_next_month.rename(columns={'monthly_pred': 'next_month_pred'}, inplace=True) df_daily = df_daily.merge(df_next_month, on=['id', 'month'], how='left')
3. 计算平滑过渡权重
以余弦曲线为例(平滑度优于线性过渡),定义过渡天数(如前后各5天):
def calculate_cosine_weights(row): day = row['day_of_month'] total_days = row['days_in_month'] transition_days = 5 # 可根据业务调整过渡天数 # 月初过渡:权重从上个月平滑切换到当月 if day <= transition_days: weight_current = (1 - np.cos(np.pi * (day - 1) / transition_days)) / 2 weight_prev = 1 - weight_current weight_next = 0 # 月末过渡:权重从当月平滑切换到下个月 elif day >= total_days - transition_days + 1: days_from_end = total_days - day + 1 weight_current = (1 - np.cos(np.pi * (days_from_end - 1) / transition_days)) / 2 weight_next = 1 - weight_current weight_prev = 0 # 月中:全权重分配给当月预测 else: weight_current = 1 weight_prev = 0 weight_next = 0 return pd.Series([weight_prev, weight_current, weight_next], index=['w_prev', 'w_current', 'w_next']) # 生成权重列 df_daily[['w_prev', 'w_current', 'w_next']] = df_daily.apply(calculate_cosine_weights, axis=1)
4. 计算并校准每日预测值
先根据权重计算初始每日值,再按月份校准总和,确保匹配原月度预测:
# 初始每日预测计算 df_daily['daily_pred_raw'] = ( df_daily['prev_month_pred'] * df_daily['w_prev'] + df_daily['monthly_pred'] * df_daily['w_current'] + df_daily['next_month_pred'] * df_daily['w_next'] ) # 校准月度总和,保证当月每日值总和等于原月度预测 def adjust_monthly_total(group): target_sum = group['monthly_pred'].iloc[0] raw_sum = group['daily_pred_raw'].sum() # 避免除以0的情况 adjust_factor = target_sum / raw_sum if raw_sum != 0 else 1 group['daily_predictions'] = group['daily_pred_raw'] * adjust_factor return group df_daily = df_daily.groupby(['id', 'year', 'month']).apply(adjust_monthly_total).reset_index(drop=True)
可选调整方案
- 线性过渡权重:如果不需要余弦曲线的平滑度,可替换为线性权重函数,计算逻辑更简单:
def calculate_linear_weights(row): day = row['day_of_month'] total_days = row['days_in_month'] transition_days = 5 if day <= transition_days: weight_current = day / transition_days weight_prev = 1 - weight_current weight_next = 0 elif day >= total_days - transition_days + 1: days_from_end = total_days - day + 1 weight_current = days_from_end / transition_days weight_next = 1 - weight_current weight_prev = 0 else: weight_current = 1 weight_prev = 0 weight_next = 0 return pd.Series([weight_prev, weight_current, weight_next], index=['w_prev', 'w_current', 'w_next'])
- 调整过渡天数:根据业务需求修改
transition_days,比如改为3天或7天,控制跨月过渡的周期长度。
验证
对于id=1、2022年1月的场景,daily_predictions列的总和会等于原jan列的100,且月初前5天的数值会从去年12月的预测值平滑过渡到100,月末后5天会平滑过渡到2月的预测值,避免了简单均分的突变问题。
内容的提问来源于stack exchange,提问作者NigelBlainey
相关产品推荐
相关产品推荐

