{
  "method": "MSTL",
  "slug": "mstl",
  "title": "Alternate between seasonal periods",
  "summary": "MSTL repeatedly estimates one seasonal period while removing the current estimates of the others, then returns a trend and remainder.",
  "steps": [
    {
      "title": "Specify the seasonal periods",
      "text": "Supply the periods and their seasonal smoothing windows. This synthetic signal contains periods 12 and 36, and both are specified explicitly. The FFT panel describes the input; it is not an automatic period-selection stage in the wrapper."
    },
    {
      "title": "Estimate seasonal terms",
      "text": "Initialize seasonal arrays at zero. For each period in the upstream processing order, fit STL to the currently adjusted series and subtract the estimated seasonal term before processing the next period."
    },
    {
      "title": "Add back and refine each term",
      "text": "On each later pass, add back the previous estimate for the period being updated, refit that seasonal term, and subtract its replacement. This preserves the other current seasonal estimates while refining one term at a time."
    },
    {
      "title": "Aggregate the common output",
      "text": "Use the trend from the last STL fit and subtract it from the deseasonalized series to obtain residual. DeTime sums separate upstream seasonal columns into result.season. This animation uses lmbda=None; transformed fits require the wrapper's documented original-scale allocation."
    }
  ],
  "parameters": [
    {
      "name": "periods",
      "effect": "One or more integer cycle lengths. Upstream MSTL sorts them and filters periods that are too long for the record."
    },
    {
      "name": "windows",
      "effect": "Odd seasonal-smoothing spans paired with the periods; this example uses 11 and 15."
    },
    {
      "name": "iterate",
      "effect": "Number of passes over the seasonal terms; this example uses two."
    },
    {
      "name": "lmbda",
      "effect": "Optional Box-Cox transform. None keeps the additive trace in the original scale; transformed components cannot be inverted independently and then simply added."
    },
    {
      "name": "stl_kwargs",
      "effect": "Options forwarded to each STL fit, including its robustness setting."
    }
  ],
  "cautions": [
    "Choose periods with enough observed repetitions. The example is illustrative and does not identify periods from data.",
    "Closely related periods and smoothing choices can change how variation is allocated between seasonal terms.",
    "DeTime's common season field is the sum of upstream seasonal columns; individual periods in this animation are instrumented from the upstream procedure.",
    "For Box-Cox fits, DeTime uses inverse(T) for trend and inverse(T+S)-inverse(T) for combined season, with the remaining difference in residual. This no-transform trace does not animate that nonlinear allocation."
  ],
  "references": [
    {
      "title": "Bandara, Hyndman, and Bergmeir (2025), MSTL",
      "url": "https://www.inderscience.com/info/e_inarticle.php?artid=143957"
    },
    {
      "title": "statsmodels MSTL implementation contract",
      "url": "https://www.statsmodels.org/stable/generated/statsmodels.tsa.seasonal.MSTL.html"
    },
    {
      "title": "Cleveland et al. (1990), STL: A Seasonal-Trend Decomposition Procedure Based on Loess",
      "url": "https://www.scb.se/contentassets/ca21efb41fee47d293bbee5bf7be7fb3/stl-a-seasonal-trend-decomposition-procedure-based-on-loess.pdf"
    }
  ],
  "trace_kind": "computed upstream procedure trace",
  "trace_note": "The alternating STL loop follows the installed statsmodels MSTL.fit procedure with an explicit two-period, no-transform setup. Its separate seasonal estimates, final trend, and public DeTime output are checked numerically.",
  "input_note": "Deterministic synthetic teaching signal; not a benchmark.",
  "example_config": {
    "method": "MSTL",
    "backend": "python",
    "params": {
      "periods": [
        12,
        36
      ],
      "windows": [
        11,
        15
      ],
      "iterate": 2,
      "lmbda": null
    }
  },
  "checks": {
    "separate_seasons_vs_upstream_max_abs_error": 0.0,
    "trace_trend_vs_upstream_max_abs_error": 0.0,
    "aggregate_season_max_abs_error": 0.0,
    "trend_max_abs_error": 0.0,
    "residual_max_abs_error": 0.0,
    "additive_reconstruction_max_abs_error": 4.440892098500626e-16
  },
  "source_files": [
    "src/detime/methods/stl.py"
  ],
  "frames": 32,
  "duration_ms": 12050,
  "poster_frame": 15,
  "width": 1280,
  "height": 820,
  "artifacts": {
    "gif": {
      "file": "mstl.gif",
      "bytes": 443564,
      "sha256": "2775454700149bfd468fd77d953f94d099377b368e371f982d75815ccbb8c205"
    },
    "poster": {
      "file": "mstl.png",
      "bytes": 169208,
      "sha256": "c8a5d7e26fa2a51077a7801928422906a45e5ff1355eb1dabc00b29534d95bc1"
    },
    "data": {
      "file": "mstl.npz",
      "bytes": 29722,
      "sha256": "9888a9226aad8f91e5195507c1d22d2dbf73c0e9867310895a74a09bdffdd1a1"
    }
  },
  "source_arrays": {
    "t": [
      144
    ],
    "observed": [
      144
    ],
    "periods": [
      2
    ],
    "windows": [
      2
    ],
    "snapshot_inputs": [
      4,
      144
    ],
    "snapshot_old_seasons": [
      4,
      144
    ],
    "snapshot_new_seasons": [
      4,
      144
    ],
    "snapshot_all_seasons": [
      4,
      2,
      144
    ],
    "snapshot_adjusted": [
      4,
      144
    ],
    "upstream_seasonal_columns": [
      144,
      2
    ],
    "trend": [
      144
    ],
    "season": [
      144
    ],
    "residual": [
      144
    ]
  },
  "render_provenance": {
    "python_version": "3.12.14",
    "dependencies": {
      "numpy": "2.3.5",
      "scipy": "1.17.0",
      "statsmodels": "0.15.0",
      "matplotlib": "3.10.8",
      "Pillow": "12.3.0",
      "PyWavelets": "1.10.0",
      "EMD-signal": "1.10.0",
      "vmdpy": "0.2",
      "PySDKit": "0.5.0"
    },
    "source_hashes": {
      "scripts/generate_method_animations.py": "f62989af55a7e48495c24afcfee31751d3785e5f78bb4b900bef23a429deaa84",
      "scripts/method_animations/classical.py": "8a8a9ef82936f0aa5b1c5775c6c0158c3b48d3bc7dca1de691b83d95e5c23ba3",
      "scripts/method_animations/common.py": "73ad7e8b4e5b67f87d13e59adab2c93e983fe1aaf06ee627d22cb7c7e696016a",
      "src/detime/backends.py": "7432dcf59e382580dfb32f1f57e61ff71a30567c049b35243e24e3644523eda6",
      "src/detime/core.py": "d95b348cd8b16b56d18840965579c947a11628d969dd6c32c4af7723f5a027f4",
      "src/detime/methods/stl.py": "bb9db3808691490849d06b6eff3f1d6083b6d79fa4d0ea526201e6cec11bdd4d",
      "src/detime/registry.py": "e934ffca17880cefe5add728aa497e63ea811d7ae43abec1ea498de718094066"
    }
  }
}
