packages feed

hanalyze-0.2.0.0: test/Hanalyze/Model/TimeSeriesSpec.hs

{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Hanalyze.Model.TimeSeriesSpec (spec) where

import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck
import Hanalyze.Model.Formula
import Hanalyze.Model.Formula.Frame
import Hanalyze.Model.Formula.Design
import Hanalyze.Model.Formula.RFormula
import Hanalyze.Model.Formula.Nonlinear
import Hanalyze.Model.Formula.Mixed
import Hanalyze.Model.GLMM
import Hanalyze.Model.GLM (Family (..), LinkFn (..))
import Hanalyze.Stat.Distribution (Transform)
import Data.List (sort, nub)
import Control.Monad (forM, forM_)
import System.IO.Temp (withSystemTempFile)
import System.IO     (hPutStr, hClose)
import           Hanalyze.Model.HBM.Ast (Expr (..), Lit (..), DoStmt (..), Err)
import           Data.IORef         (newIORef, readIORef, modifyIORef')
import qualified Numeric.LinearAlgebra as LA
import qualified Hanalyze.Model.TimeSeries   as TS
import SpecHelper

spec :: Spec
spec = do
  describe "Hanalyze.Model.TimeSeries" $ do
    it "autocorrelation: lag 0 = 1.0" $ do
      let y = LA.fromList [1.0, 2.0, 1.5, 2.5, 1.8]
          acf = TS.autocorrelation 5 y
      LA.atIndex acf 0 `shouldBe` 1.0

    it "autocorrelation: 周期 4 の sin 系列で lag 4 が高い" $ do
      let y = LA.fromList [sin (2*pi*fromIntegral t / 4) | t <- [0..40::Int]]
          acf = TS.autocorrelation 8 y
      LA.atIndex acf 4 `shouldSatisfy` (> 0.5)

    it "fitAR(1) on quasi-AR(1) data: φ̂ in (0, 1)" $ do
      -- Quasi-AR(1) with φ=0.7 + small deterministic perturbation
      let phi = 0.7
          go _    0   = []
          go prev n   =
            let yi = phi * prev + 0.01 * sin (fromIntegral n :: Double)
            in yi : go yi (n - 1)
          ys  = take 100 (drop 50 (go 1.0 200))  -- burn-in 50
          y = LA.fromList ys
          fit = TS.fitAR 1 y
      let phiHat = LA.atIndex (TS.arPhi fit) 0
      -- AR(1) coefficient should be in (0, 1) for stationary positive AR
      phiHat `shouldSatisfy` (\p -> p > 0 && p < 1)

    it "forecastAR: h-step forecast 同 size" $ do
      let y = LA.fromList [1.0, 1.5, 2.0, 2.5, 3.0, 2.5, 2.0, 1.5, 1.0, 1.5,
                          2.0, 2.5, 3.0, 2.5, 2.0]
          fit = TS.fitAR 2 y
          fc = TS.forecastAR fit y 5
      LA.size fc `shouldBe` 5

    it "differencing: y' length = n - 1" $ do
      let y = LA.fromList [1.0, 3.0, 2.0, 5.0, 4.0]
          d1 = TS.differencing y
      LA.size d1 `shouldBe` 4
      LA.toList d1 `shouldBe` [2.0, -1.0, 3.0, -1.0]

    it "simpleExpSmoothing: α=1 で原系列、α=0 で初期値固定" $ do
      let y = LA.fromList [1.0, 2.0, 3.0, 4.0, 5.0]
          s1 = TS.simpleExpSmoothing 1.0 y    -- α=1
          s0 = TS.simpleExpSmoothing 0.0 y    -- α=0
      LA.toList s1 `shouldBe` LA.toList y    -- exact
      all (== 1.0) (LA.toList s0) `shouldBe` True

    it "holtWinters: 線形 trend + 周期 4 を再現" $ do
      let -- y_t = t + 5 sin(2πt/4)
          ys = [ fromIntegral t + 3 * sin (2 * pi * fromIntegral t / 4)
               | t <- [0 .. 39 :: Int] ]
          y  = LA.fromList ys
          fit = TS.holtWinters TS.HWAdditive 4 y
          fc  = TS.hwForecast fit 4
      LA.size fc `shouldBe` 4
      -- Forecast at t=40,41,42,43 should be roughly 40 + sin pattern
      LA.atIndex fc 0 `shouldSatisfy` (> 35)

    it "stlDecompose: 周期成分が period 倍で繰り返す" $ do
      let ys = [ fromIntegral t / 10 + 2 * sin (2 * pi * fromIntegral t / 4)
               | t <- [0 .. 39 :: Int] ]
          y  = LA.fromList ys
          (_trend, seasonal, _resid) = TS.stlDecompose 4 y
      LA.size seasonal `shouldBe` LA.size y

  -- ===========================================================================
  -- Hanalyze.Model.Survival (Phase 12)
  -- ===========================================================================