File: Deseasonality.cs
Web Access
Project: src\src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj (Microsoft.ML.TimeSeries)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
 
namespace Microsoft.ML.TimeSeries
{
    internal interface IDeseasonality
    {
        /// <summary>
        /// Remove the seasonality component from the given time-series.
        /// </summary>
        /// <param name="values">An array representing the input time-series.</param>
        /// <param name="period">The period value of the time-series.</param>
        /// <param name="results">The de-seasonalized time-series.</param>
        public abstract void Deseasonality(ref double[] values, int period, ref double[] results);
    }
 
    internal sealed class MeanDeseasonality : IDeseasonality
    {
        private double[] _circularComponent;
 
        public void Deseasonality(ref double[] values, int period, ref double[] results)
        {
            Array.Resize(ref _circularComponent, period);
 
            var length = values.Length;
 
            // Initialize the circular component to 0.
            for (int i = 0; i < period; ++i)
            {
                _circularComponent[i] = 0;
            }
 
            // Sum up values that locate at the same position in one period.
            for (int i = 0; i < length; ++i)
            {
                var indexInPeriod = i % period;
                _circularComponent[indexInPeriod] += values[i];
            }
 
            // Calculate the mean value as circular component.
            var cnt = (length - 1) / period;
            var rest = (length - 1) % period;
            for (int i = 0; i < period; ++i)
            {
                var lastCircle = i <= rest ? 1 : 0;
                _circularComponent[i] = _circularComponent[i] / (cnt + lastCircle);
            }
 
            // Substract the circular component from the original series.
            for (int i = 0; i < length; ++i)
            {
                var indexInPeriod = i % period;
                results[i] -= _circularComponent[indexInPeriod];
            }
        }
    }
 
    internal sealed class MedianDeseasonality : IDeseasonality
    {
        private List<double>[] _subSeries;
        private double[] _circularComponent;
 
        public void Deseasonality(ref double[] values, int period, ref double[] results)
        {
            Array.Resize(ref _circularComponent, period);
            Array.Resize(ref _subSeries, period);
 
            var length = values.Length;
 
            for (int i = 0; i < period; ++i)
            {
                _subSeries[i] = new List<double>();
            }
 
            // Split the original series into #period subseries.
            for (int i = 0; i < length; ++i)
            {
                var indexInPeriod = i % period;
                _subSeries[indexInPeriod].Add(values[i]);
            }
 
            // Calculate the median value as circular component.
            for (int i = 0; i < period; ++i)
            {
                _circularComponent[i] = MathUtility.QuickMedian(_subSeries[i]);
            }
 
            // Substract the circular component from the original series.
            for (int i = 0; i < length; ++i)
            {
                var indexInPeriod = i % period;
                results[i] -= _circularComponent[indexInPeriod];
            }
        }
    }
 
    /// <summary>
    /// This class takes the residual component of stl decompose as the deseasonality result.
    /// </summary>
    internal sealed class StlDeseasonality : IDeseasonality
    {
        private readonly InnerStl _stl;
        private readonly IDeseasonality _backupFunc;
 
        public StlDeseasonality()
        {
            _stl = new InnerStl(true);
            _backupFunc = new MedianDeseasonality();
        }
 
        public void Deseasonality(ref double[] values, int period, ref double[] results)
        {
            bool success = _stl.Decomposition(values, period);
            if (success)
            {
                for (int i = 0; i < _stl.Residual.Count; ++i)
                {
                    results[i] = _stl.Residual[i];
                }
            }
            // invoke the back up deseasonality method if stl decompose fails.
            else
            {
                _backupFunc.Deseasonality(ref values, period, ref results);
            }
        }
    }
}