1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
{-# LANGUAGE OverloadedStrings, PatternGuards,
DeriveDataTypeable, DeriveGeneric #-}
-- |
-- Module : Statistics.Distribution.NegativeBinomial
-- Copyright : (c) 2022 Lorenz Minder
-- License : BSD3
--
-- Maintainer : lminder@gmx.net
-- Stability : experimental
-- Portability : portable
--
-- The negative binomial distribution. This is the discrete probability
-- distribution of the number of failures in a sequence of independent
-- yes\/no experiments before a specified number of successes /r/. Each
-- Bernoulli trial has success probability /p/ in the range (0, 1]. The
-- parameter /r/ must be positive, but does not have to be integer.
module Statistics.Distribution.NegativeBinomial (
NegativeBinomialDistribution
-- * Constructors
, negativeBinomial
, negativeBinomialE
-- * Accessors
, nbdSuccesses
, nbdProbability
) where
import Control.Applicative
import Data.Aeson (FromJSON(..), ToJSON, Value(..), (.:))
import Data.Binary (Binary(..))
import Data.Data (Data, Typeable)
import Data.Foldable (foldl')
import GHC.Generics (Generic)
import Numeric.SpecFunctions (incompleteBeta, log1p)
import Numeric.SpecFunctions.Extra (logChooseFast)
import Numeric.MathFunctions.Constants (m_epsilon, m_tiny)
import qualified Statistics.Distribution as D
import Statistics.Internal
-- Math helper functions
-- | Generalized binomial coefficients.
--
-- These computes binomial coefficients with the small generalization
-- that the /n/ need not be integer, but can be real.
gChoose :: Double -> Int -> Double
gChoose n k
| k < 0 = 0
| k' >= 50 = exp $ logChooseFast n k'
| otherwise = foldl' (*) 1 factors
where factors = [ (n - k' + j) / j | j <- [1..k'] ]
k' = fromIntegral k
-- Implementation of Negative Binomial
-- | The negative binomial distribution.
data NegativeBinomialDistribution = NBD {
nbdSuccesses :: {-# UNPACK #-} !Double
-- ^ Number of successes until stop
, nbdProbability :: {-# UNPACK #-} !Double
-- ^ Success probability.
} deriving (Eq, Typeable, Data, Generic)
instance Show NegativeBinomialDistribution where
showsPrec i (NBD r p) = defaultShow2 "negativeBinomial" r p i
instance Read NegativeBinomialDistribution where
readPrec = defaultReadPrecM2 "negativeBinomial" negativeBinomialE
instance ToJSON NegativeBinomialDistribution
instance FromJSON NegativeBinomialDistribution where
parseJSON (Object v) = do
r <- v .: "nbdSuccesses"
p <- v .: "nbdProbability"
maybe (fail $ errMsg r p) return $ negativeBinomialE r p
parseJSON _ = empty
instance Binary NegativeBinomialDistribution where
put (NBD r p) = put r >> put p
get = do
r <- get
p <- get
maybe (fail $ errMsg r p) return $ negativeBinomialE r p
instance D.Distribution NegativeBinomialDistribution where
cumulative = cumulative
complCumulative = complCumulative
instance D.DiscreteDistr NegativeBinomialDistribution where
probability = probability
logProbability = logProbability
instance D.Mean NegativeBinomialDistribution where
mean = mean
instance D.Variance NegativeBinomialDistribution where
variance = variance
instance D.MaybeMean NegativeBinomialDistribution where
maybeMean = Just . D.mean
instance D.MaybeVariance NegativeBinomialDistribution where
maybeStdDev = Just . D.stdDev
maybeVariance = Just . D.variance
instance D.Entropy NegativeBinomialDistribution where
entropy = directEntropy
instance D.MaybeEntropy NegativeBinomialDistribution where
maybeEntropy = Just . D.entropy
-- This could be slow for big n
probability :: NegativeBinomialDistribution -> Int -> Double
probability d@(NBD r p) k
| k < 0 = 0
-- Switch to log domain for large k + r to avoid overflows.
--
-- We also want to avoid underflow when computing (1-p)^k &
-- p^r.
| k' + r < 1000
, pK >= m_tiny
, pR >= m_tiny = gChoose (k' + r - 1) k * pK * pR
| otherwise = exp $ logProbability d k
where
pK = exp $ log1p (-p) * k'
pR = p**r
k' = fromIntegral k
logProbability :: NegativeBinomialDistribution -> Int -> Double
logProbability (NBD r p) k
| k < 0 = (-1)/0
| otherwise = logChooseFast (k' + r - 1) k'
+ log1p (-p) * k'
+ log p * r
where k' = fromIntegral k
cumulative :: NegativeBinomialDistribution -> Double -> Double
cumulative (NBD r p) x
| isNaN x = error "Statistics.Distribution.NegativeBinomial.cumulative: NaN input"
| isInfinite x = if x > 0 then 1 else 0
| k < 0 = 0
| otherwise = incompleteBeta r (fromIntegral (k+1)) p
where
k = floor x :: Integer
complCumulative :: NegativeBinomialDistribution -> Double -> Double
complCumulative (NBD r p) x
| isNaN x = error "Statistics.Distribution.NegativeBinomial.complCumulative: NaN input"
| isInfinite x = if x > 0 then 0 else 1
| k < 0 = 1
| otherwise = incompleteBeta (fromIntegral (k+1)) r (1 - p)
where
k = (floor x)::Integer
mean :: NegativeBinomialDistribution -> Double
mean (NBD r p) = r * (1 - p)/p
variance :: NegativeBinomialDistribution -> Double
variance (NBD r p) = r * (1 - p)/(p * p)
directEntropy :: NegativeBinomialDistribution -> Double
directEntropy d =
negate . sum $
takeWhile (< -m_epsilon) $
dropWhile (>= -m_epsilon) $
[ let x = probability d k in x * log x | k <- [0..]]
-- | Construct negative binomial distribution. Number of failures /r/
-- must be positive and probability must be in (0,1] range
negativeBinomial :: Double -- ^ Number of successes.
-> Double -- ^ Success probability.
-> NegativeBinomialDistribution
negativeBinomial r p = maybe (error $ errMsg r p) id $ negativeBinomialE r p
-- | Construct negative binomial distribution. Number of failures /r/
-- must be positive and probability must be in (0,1] range
negativeBinomialE :: Double -- ^ Number of successes.
-> Double -- ^ Success probability.
-> Maybe NegativeBinomialDistribution
negativeBinomialE r p
| r > 0 && 0 < p && p <= 1 = Just (NBD r p)
| otherwise = Nothing
errMsg :: Double -> Double -> String
errMsg r p
= "Statistics.Distribution.NegativeBinomial.negativeBinomial: r=" ++ show r
++ " p=" ++ show p ++ ", but need r>0 and p in (0,1]"
|