使用Darts进行含缺失数据的时间序列建模:销量预测问题求助
解决Darts中TFT模型处理周末缺失销量数据的最优方案
针对你遇到的周末销量缺失、填充0效果差的问题,以下是几个优先级从高到低的处理方案:
1. 显式添加工作日标记协变量,保留缺失值并让模型学习规则
这是最推荐的方案,核心是让模型明确区分工作日和周末,而非盲目填充缺失值:
- 先构建包含所有日期(含周末)的完整时间索引,将原始工作日数据合并到这个索引上,让周末的销量自然为NaN;
- 添加一个布尔型协变量
is_working_day,工作日设为1,周末设为0; - 广告支出的缺失可根据实际情况填充(比如周末无广告则填0,有历史数据则用前向填充);
- 模型会通过
is_working_day学习到“非工作日销量为0”的规则,同时结合广告支出预测工作日销量。
代码示例:
import pandas as pd from darts import TimeSeries # 读取原始数据 df = pd.read_csv('sales_data.csv', parse_dates=['date']) # 生成包含所有日期的完整序列 full_date_range = pd.date_range(start=df['date'].min(), end=df['date'].max(), freq='D') full_df = pd.DataFrame({'date': full_date_range}) # 合并原始数据,周末的销量、广告支出自动设为NaN full_df = full_df.merge(df, on='date', how='left') # 添加工作日标记协变量 full_df['is_working_day'] = full_df['date'].dt.weekday < 5 # 周一至周五为1,周末为0 # 填充广告支出缺失值(示例:周末无广告则填0) full_df['ad_spend'] = full_df['ad_spend'].fillna(0) # 创建Darts序列,销量保留NaN sales_series = TimeSeries.from_dataframe(full_df, 'date', 'sales', fillna_value=None) covs_series = TimeSeries.from_dataframe(full_df, 'date', ['ad_spend', 'is_working_day'])
2. 自定义掩码损失函数,忽略缺失值的损失计算
如果不想修改数据结构,可以通过自定义损失函数,只对工作日的有效销量计算损失,避免NaN干扰:
- 构建一个掩码,标记哪些时间步是有真实销量的工作日;
- 在损失函数中仅计算掩码覆盖区域的误差,模型会自动学习周末销量应为0的模式。
代码示例:
import torch from darts.models import TFTModel def masked_mae(y_true, y_pred): # 生成掩码:仅保留y_true不为NaN的时间步 mask = ~torch.isnan(y_true) # 计算掩码区域内的MAE return torch.mean(torch.abs(y_true[mask] - y_pred[mask])) # 初始化TFT模型时指定自定义损失 model = TFTModel( input_chunk_length=14, output_chunk_length=7, loss_fn=masked_mae, # 其他模型参数(如hidden_size、attention_head_size等)按需设置 )
3. 调整数据频率为工作日模式
如果你的预测目标仅针对工作日,不需要输出周末的销量预测,可以直接将数据频率调整为工作日(B频率),彻底规避周末的缺失值问题:
- 将原始数据的索引转换为工作日频率,自动过滤周末日期;
- 确保协变量(广告支出)也同步调整为工作日频率,保持时间对齐。
代码示例:
df = pd.read_csv('sales_data.csv', parse_dates=['date']) # 设置索引并转换为工作日频率 df = df.set_index('date').asfreq('B') # 创建Darts序列 sales_series = TimeSeries.from_dataframe(df, value_cols='sales') covs_series = TimeSeries.from_dataframe(df, value_cols='ad_spend')
4. 合理插值填充(不推荐0填充)
如果必须填充周末销量,不要用0,而是选择更贴合业务逻辑的插值方式(比如线性插值、前向填充),但这种方法会引入人工假数据,仅作为备选:
# 线性插值填充销量缺失值 full_df['sales'] = full_df['sales'].interpolate(method='linear') # 或者前向填充+反向填充 full_df['sales'] = full_df['sales'].ffill().bfill()
内容的提问来源于stack exchange,提问作者Funzo
相关产品推荐
相关产品推荐

