packages feed

hanalyze-0.1.0.0: demo/bayesian/NegBinomDemo.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
-- | NegativeBinomial(μ, α) — 過分散カウントデータのデモ。
--
-- 比較: 同じデータに対して Poisson と NegativeBinomial を fit。
-- データは μ=10, α=2 の NB から生成 (var = 10 + 100/2 = 60、Poisson の
-- var = 10 より遥かに大きい)。Poisson モデルでは過分散を捕えられない。
module Main where

import qualified Data.Map.Strict as Map
import Text.Printf (printf)
import System.Random.MWC (createSystemRandom)
import qualified System.Random.MWC.Distributions as MWC
import qualified System.Random.MWC as MWCBase

import Hanalyze.MCMC.NUTS (nuts, defaultNUTSConfig, NUTSConfig (..))
import Hanalyze.Model.HBM (ModelP, sample, observe, Distribution (..))
import Hanalyze.Viz.MCMC (printPosteriorSummary, posteriorSummaryFile)

cfg :: NUTSConfig
cfg = defaultNUTSConfig
        { nutsIterations = 800
        , nutsBurnIn     = 300
        , nutsStepSize   = 0.1
        , nutsMaxDepth   = 6
        }

-- 真のパラメタで NB データを生成
genNB :: Int -> Double -> Double -> IO [Double]
genNB n mu alpha = do
  gen <- createSystemRandom
  -- Gamma-Poisson mixture: λ ~ Gamma(α, μ/α), X ~ Poisson(λ)
  let drawOne = do
        lam <- MWC.gamma alpha (mu / alpha) gen
        let knuth k p = do
              u <- MWCBase.uniform gen :: IO Double
              let p' = p * u
              if p' < exp (-lam)
                then return (fromIntegral k)
                else knuth (k + 1) p'
        knuth (0 :: Int) (1 :: Double)
  mapM (const drawOne) [1 .. n]

poissonModel :: [Double] -> ModelP ()
poissonModel ys = do
  lam <- sample "lambda" (Gamma 1 0.1)
  observe "y" (Poisson lam) ys

nbModel :: [Double] -> ModelP ()
nbModel ys = do
  mu    <- sample "mu"    (Gamma 1 0.1)
  alpha <- sample "alpha" (Gamma 1 0.1)
  observe "y" (NegativeBinomial mu alpha) ys

main :: IO ()
main = do
  putStrLn "═══════════════════════════════════════════════════════════════"
  putStrLn "  NegativeBinomial vs Poisson (過分散カウント, Phase H1)"
  putStrLn "═══════════════════════════════════════════════════════════════"
  putStrLn ""

  putStrLn "真値: μ = 10, α = 2  (= var = 60, mean = 10 → 過分散)"
  ys <- genNB 80 10 2
  let n     = length ys
      muSm  = sum ys / fromIntegral n
      varSm = sum [(y - muSm)^(2::Int) | y <- ys] / fromIntegral (n - 1)
  printf "観測 (n=%d): 標本平均 = %.2f, 標本分散 = %.2f\n" n muSm varSm
  printf "  → 分散/平均 = %.2f (≫ 1 なら Poisson は不適合)\n"
         (varSm / muSm)
  putStrLn ""

  gen <- createSystemRandom

  putStrLn "[1] Poisson モデル (過分散を捕えない)"
  ch1 <- nuts (poissonModel ys) cfg
              (Map.fromList [("lambda", 5)]) gen
  printPosteriorSummary ["lambda"] [ch1]
  putStrLn ""

  putStrLn "[2] NegativeBinomial モデル (過分散を捕える)"
  ch2 <- nuts (nbModel ys) cfg
              (Map.fromList [("mu", 5), ("alpha", 1)]) gen
  printPosteriorSummary ["mu", "alpha"] [ch2]
  putStrLn ""

  posteriorSummaryFile "negbinom-poisson.html"  "Poisson"  ["lambda"]      [ch1]
  posteriorSummaryFile "negbinom-nb.html"       "NegBinom" ["mu", "alpha"] [ch2]
  putStrLn "  → negbinom-{poisson,nb}.html"
  putStrLn ""

  putStrLn "═══════════════════════════════════════════════════════════════"
  putStrLn "  ✓ NegativeBinomial で μ ≈ 10, α ≈ 2 を回復、過分散を表現"
  putStrLn "═══════════════════════════════════════════════════════════════"