#time-series #analysis #forecasting #data-model #outlier-detection #modeling

augurs-mstl

使用 augurs 时间序列库进行多重季节性趋势分解(MSTL)

7 个版本

0.3.1 2024年7月30日
0.3.0 2024年7月30日
0.2.0 2024年6月5日
0.1.2 2024年2月20日
0.1.0 2023年9月25日

#1 in #forecasting

Download history 4/week @ 2024-04-22 2/week @ 2024-04-29 13/week @ 2024-05-20 3/week @ 2024-05-27 192/week @ 2024-06-03 16/week @ 2024-06-10 4/week @ 2024-06-17 4/week @ 2024-06-24 32/week @ 2024-07-08 270/week @ 2024-07-29

302 每月下载量
用于 4 crates

MIT/Apache

185KB
1K SLoC

多重季节性趋势分解(MSTL)

快速、有效的预测具有多重季节性和趋势的时间序列。

简介

MSTL 算法,如本文献 所述,提供了一种将季节性趋势分解应用于多重季节性的方法。这允许对具有多个复杂成分的时间序列进行有效建模。

除了 MSTL 算法,这个包还提供了 MSTLModel 结构体,它可以对某些时间序列数据进行 MSTL 算法运算,然后使用给定趋势预测器对最终趋势成分进行建模。然后,它可以将预测的趋势与分解的季节性重新组合以生成样本内和样本外预测。

后一种用例是这个包的主要入口点。

使用方法

use augurs_core::prelude::*;
use augurs_mstl::MSTLModel;

# fn main() -> Result<(), Box<dyn std::error::Error>> {
// Input data must be a `&[f64]` for the MSTL algorithm.
let y = &[1.5, 3.0, 2.5, 4.2, 2.7, 1.9, 1.0, 1.2, 0.8];

// Define the number of seasonal observations per period.
// In this example we have hourly data and both daily and
// weekly seasonality.
let periods = vec![3, 4];
// Create an MSTL model using a naive trend forecaster.
// Note: in real life you may want to use a different
// trend forecaster - see below.
let mstl = MSTLModel::naive(periods);
// Fit the model. Note this consumes `mstl` and returns
// a fitted version.
let fit = mstl.fit(y)?;

// Obtain in-sample and out-of-sample predictions, along
// with prediction intervals.
let in_sample = fit.predict_in_sample(0.95)?;
let out_of_sample = fit.predict(10, 0.95)?;
# Ok(())
# }

使用替代趋势模型

MSTLModel 对所使用的趋势模型是泛型的。只要传入的模型实现了这个包中的 TrendModel 特性,就可以用来对分解后的趋势进行建模。例如,可以使用 ets 包中的 AutoETS 结构体。首先,将 augurs_ets 包添加到您的 Cargo.toml 中,并启用 mstl 功能

[dependencies]
augurs_ets = { version = "*", features = ["mstl"] }
use augurs_ets::AutoETS;
use augurs_mstl::MSTLModel;

let y = vec![1.5, 3.0, 2.5, 4.2, 2.7, 1.9, 1.0, 1.2, 0.8];

let periods = vec![24, 24 * 7];
let trend_model = AutoETS::non_seasonal();
let mstl = MSTLModel::new(periods, trend_model);
let fit = mstl.fit(y)?;

let in_sample = fit.predict_in_sample(0.95)?;
let out_of_sample = fit.predict(10, 0.95)?;

(注意,上述示例由于循环依赖无法编译此包,但在单独的包中可以正常工作!)

实现趋势模型

要使用您自己的趋势模型,您需要一个实现了 TrendModel 特性的结构体。以下是一个示例模型,该模型预测所有时间点的值都为常数。

use std::borrow::Cow;

use augurs_core::{Forecast, ForecastIntervals};
use augurs_mstl::{FittedTrendModel, TrendModel};

// The unfitted model. Sometimes this will need state!
#[derive(Debug)]
struct ConstantTrendModel {
    // The constant value to predict.
    constant: f64,
}

// The fitted model. This will invariable need state.
#[derive(Debug)]
struct FittedConstantTrendModel {
    // The constant value to predict.
    constant: f64,
    // The number of values in the training data.
    y_len: usize,
}

impl TrendModel for ConstantTrendModel {
    fn name(&self) -> Cow<'_, str> {
        Cow::Borrowed("Constant")
    }

    fn fit(
        &self,
        y: &[f64],
    ) -> Result<
        Box<dyn FittedTrendModel + Sync + Send>,
        Box<dyn std::error::Error + Send + Sync + 'static>,
    > {
        // Your algorithm should do whatever it needs to do to fit the model.
        // You need to return a boxed implementation of `FittedTrendModel`.
        Ok(Box::new(FittedConstantTrendModel {
            constant: self.constant,
            y_len: y.len(),
        }))
    }
}

impl FittedTrendModel for FittedConstantTrendModel {
    fn predict_inplace(
        &self,
        horizon: usize,
        level: Option<f64>,
        forecast: &mut Forecast,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
        forecast.point = vec![self.constant; horizon];
        if let Some(level) = level {
            let mut intervals = forecast
                .intervals
                .get_or_insert_with(|| ForecastIntervals::with_capacity(level, horizon));
            intervals.lower = vec![self.constant; horizon];
            intervals.upper = vec![self.constant; horizon];
        }
        Ok(())
    }

    fn predict_in_sample_inplace(
        &self,
        level: Option<f64>,
        forecast: &mut Forecast,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
        forecast.point = vec![self.constant; self.y_len];
        if let Some(level) = level {
            let mut intervals = forecast
                .intervals
                .get_or_insert_with(|| ForecastIntervals::with_capacity(level, self.y_len));
            intervals.lower = vec![self.constant; self.y_len];
            intervals.upper = vec![self.constant; self.y_len];
        }
        Ok(())
    }

    fn training_data_size(&self) -> Option<usize> {
        Some(self.y_len)
    }
}

致谢

此实现主要基于 R 实现 和 statsforecast 实现,并且大量使用了 [stlrs][] 包。

参考文献

Bandara, Kasun & Hyndman, Rob & Bergmeir, Christoph. (2021). “MSTL: A Seasonal-Trend Decomposition Algorithm for Time Series with Multiple Seasonal Patterns”。

许可

双许可,兼容 Rust 项目。根据您的选择,许可协议为 Apache 许可协议第 2.0 版或 MIT 许可协议 <http://www.apache.org/licenses/LICENSE-2.0><http://opensource.org/licenses/MIT>

依赖项

~0.7–1.3MB
~26K SLoC