You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python将月度预测值平滑拆分至每日?

月度销售预测值的跨月平滑日拆分实现方案

要实现月度预测值的跨月平滑日拆分(避免简单均分的数值突变,同时保证月度总和/均值匹配原预测),没有直接开箱即用的numpy/pandas函数,需要结合日期处理、权重计算和自定义逻辑来实现,以下是具体方案:

核心思路

  1. 跨月过渡加权:在月初/月末的过渡周期内,让相邻月份的预测值参与当日预测的加权计算,消除月末到月初的数值突变。
  2. 总和校准:初步计算后,按月份调整每日值的总和,确保完全匹配原月度预测值(均值自然符合要求)。
  3. 灵活权重曲线:用余弦/线性曲线生成过渡权重,比生硬的线性过渡更平滑。

关键依赖函数

  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 18:28:13