packages feed

hanalyze-0.2.0.0: test/Hanalyze/Stat/EffectSpec.hs

{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Hanalyze.Stat.EffectSpec (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.Stat.Effect          as Eff
import SpecHelper

spec :: Spec
spec = do
  describe "Hanalyze.Stat.Effect" $ do
    it "cohenD: 同じ分布で 0、平均差 1 SD で ~1" $ do
      let xs = LA.fromList [1, 2, 3, 4, 5] :: LA.Vector Double
          ys = LA.fromList [1, 2, 3, 4, 5] :: LA.Vector Double
      Eff.cohenD xs ys `shouldBe` 0.0

      let zs = LA.fromList [2, 3, 4, 5, 6] :: LA.Vector Double  -- shifted
          d2 = Eff.cohenD zs xs
      d2 `shouldSatisfy` (> 0.5)

    it "hedgesG ≤ |cohenD| (small-sample correction)" $ do
      let xs = LA.fromList [1, 2, 3, 4, 5] :: LA.Vector Double
          ys = LA.fromList [3, 4, 5, 6, 7] :: LA.Vector Double
          d  = Eff.cohenD xs ys
          g  = Eff.hedgesG xs ys
      abs g `shouldSatisfy` (<= abs d + 1e-9)

    it "eta2: 完全分離で大、同分布で 0" $ do
      let g1 = LA.fromList [1, 2, 3] :: LA.Vector Double
          g2 = LA.fromList [10, 11, 12] :: LA.Vector Double
          g3 = LA.fromList [20, 21, 22] :: LA.Vector Double
      Eff.eta2 [g1, g2, g3] `shouldSatisfy` (> 0.9)

    it "powerTTest: d = 0 で α 付近 (= ~0.05)" $ do
      let p = Eff.powerTTest 30 0.05 0.0
      p `shouldSatisfy` (\x -> abs (x - 0.05) < 0.01)

    it "powerTTest: 大きい d で高い power" $ do
      let p = Eff.powerTTest 30 0.05 0.8
      p `shouldSatisfy` (> 0.85)

    it "sampleSizeTTest: 小 d ほど大 n が必要" $ do
      let n1 = Eff.sampleSizeTTest 0.80 0.05 0.2  -- small effect
          n2 = Eff.sampleSizeTTest 0.80 0.05 0.5  -- medium
          n3 = Eff.sampleSizeTTest 0.80 0.05 0.8  -- large
      n1 `shouldSatisfy` (> n2)
      n2 `shouldSatisfy` (> n3)

    it "cramerV: 2×2 完全独立で ~0、強従属で大" $ do
      Eff.cramerV 0 100 2 2 `shouldBe` 0.0
      Eff.cramerV 50 100 2 2 `shouldSatisfy` (> 0.5)

  -- ===========================================================================
  -- Hanalyze.Model.DecisionTree (Phase 10)
  -- ===========================================================================

  describe "Hanalyze.Stat.Effect CI (Phase 13.2)" $ do
    it "cohenDCI: 大効果 d ≈ 2 で CI が 0 を含まない" $ do
      let xs = LA.fromList [0, 0.1, -0.1, 0.2, -0.2, 0.05, -0.05, 0.15, -0.15, 0.0]
          ys = LA.fromList [2, 2.1, 1.9, 2.2, 1.8, 2.05, 1.95, 2.15, 1.85, 2.0]
          (d, (lo, hi)) = Eff.cohenDCI xs ys 0.05
      d `shouldSatisfy` (< 0)
      hi `shouldSatisfy` (< 0)
      lo `shouldSatisfy` (< hi)
    it "cohenDCI: 同分布で CI が 0 を含む" $ do
      let xs = LA.fromList [1, 2, 3, 2, 1, 2, 3, 2, 1, 2]
          ys = LA.fromList [1.1, 2.1, 2.9, 2.05, 1.05, 1.95, 3.05, 2.1, 0.95, 2.0]
          (_, (lo, hi)) = Eff.cohenDCI xs ys 0.05
      lo `shouldSatisfy` (< 0)
      hi `shouldSatisfy` (> 0)
    it "eta2CI: 大 F で点推定が 0 より十分大きい" $ do
      let (eta, _) = Eff.eta2CI 50.0 (2, 27) 0.05
      eta `shouldSatisfy` (> 0.5)
    it "eta2CI: 小 F で CI 下端が 0 に近い" $ do
      let (_, (lo, _)) = Eff.eta2CI 0.1 (2, 27) 0.05
      lo `shouldSatisfy` (< 0.01)