packages feed

welford-online-mean-variance 0.1.0.4 → 0.2.0.0

raw patch · 3 files changed

+163/−60 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Statistics.Sample.WelfordOnlineMeanVariance: clipValue :: WelfordOnline a => Double -> a -> a
+ Statistics.Sample.WelfordOnlineMeanVariance: constInt :: WelfordOnline a => a -> Int -> a
+ Statistics.Sample.WelfordOnlineMeanVariance: denormaliseFromZeroMeanUnitVariance :: WelfordOnline a => WelfordExistingAggregate a -> a -> a
+ Statistics.Sample.WelfordOnlineMeanVariance: divide :: WelfordOnline a => a -> a -> a
+ Statistics.Sample.WelfordOnlineMeanVariance: infixl 6 `plus`
+ Statistics.Sample.WelfordOnlineMeanVariance: infixl 7 `divideInt`
+ Statistics.Sample.WelfordOnlineMeanVariance: normaliseToZeroMeanUnitVariance :: WelfordOnline a => WelfordExistingAggregate a -> a -> a
+ Statistics.Sample.WelfordOnlineMeanVariance: squareRootMax :: WelfordOnline a => a -> a

Files

src/Statistics/Sample/WelfordOnlineMeanVariance.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_GHC -Wno-partial-fields #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveAnyClass    #-} {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE FlexibleInstances #-}@@ -16,6 +17,8 @@   , finalize   , nextValue   , isWelfordExistingAggregateEmpty+  , normaliseToZeroMeanUnitVariance+  , denormaliseFromZeroMeanUnitVariance   , Mean   , Variance   , SampleVariance@@ -44,85 +47,59 @@       }   deriving (Eq, Show, Read, Generic, NFData, Serialize) + -- | Create a new empty Aggreate for the calculation. newWelfordAggregate :: WelfordExistingAggregate a newWelfordAggregate = WelfordExistingAggregateEmpty + -- | Create a new empty Aggreate by specifying an example `a` value. It is safe to use the `*Unsafe` record field selectors from `WelfordExistingAggregate a`, when creating the data structure using -- this fuction. newWelfordAggregateDef :: (WelfordOnline a) => a -> WelfordExistingAggregate a newWelfordAggregateDef a = WelfordExistingAggregate 0 (a `minus` a) (a `minus` a) + -- | Check if it is aggregate is empty isWelfordExistingAggregateEmpty :: WelfordExistingAggregate a -> Bool isWelfordExistingAggregateEmpty WelfordExistingAggregateEmpty = True isWelfordExistingAggregateEmpty _                             = False --- | Get counter safely, returns Nothing if `WelfordExistingAggregateEmpty`.++-- | Get counter. welfordCount :: WelfordExistingAggregate a -> Int welfordCount WelfordExistingAggregateEmpty    = 0 welfordCount (WelfordExistingAggregate c _ _) = c --- | Get counter safely, returns Nothing if `WelfordExistingAggregateEmpty`.++-- | Get mean safely, returns Nothing if `WelfordExistingAggregateEmpty` as the type of `a` (e.g. length of vector) is unknown. mWelfordMean :: WelfordExistingAggregate a -> Maybe a mWelfordMean WelfordExistingAggregateEmpty    = Nothing -- Cannot create value `a` mWelfordMean (WelfordExistingAggregate _ m _) = Just m --- | Get counter with specifying a default value.++-- | Get mean with specifying a default value. welfordMeanDef :: (WelfordOnline a) => a -> WelfordExistingAggregate a -> a welfordMeanDef a WelfordExistingAggregateEmpty    = a `minus` a welfordMeanDef _ (WelfordExistingAggregate _ m _) = m --- | Add one value to the current aggregate.-addValue :: (WelfordOnline a) => WelfordExistingAggregate a -> a -> WelfordExistingAggregate a-addValue (WelfordExistingAggregate count mean m2) val =-  let count' = count + 1-      delta = val `minus` mean-      mean' = mean `plus` (delta `divideInt` count')-      delta2 = val `minus` mean'-      m2' = m2 `plus` (delta `multiply` delta2)-   in WelfordExistingAggregate count' mean' m2'-addValue WelfordExistingAggregateEmpty val =-  let count' = 1-      delta' = val-      mean' = delta' `divideInt` count'-      delta2' = val `minus` mean'-      m2' = delta' `multiply` delta2'-   in WelfordExistingAggregate count' mean' m2' --- | Add multiple values to the current aggregate. This is `foldl addValue`.-addValues :: (WelfordOnline a, Foldable f) => WelfordExistingAggregate a -> f a -> WelfordExistingAggregate a-addValues = foldl addValue---- | Calculate mean, variance and sample variance from aggregate. Calls `error` for `WelfordExistingAggregateEmpty`.-finalize :: (WelfordOnline a) => WelfordExistingAggregate a -> (Mean a, Variance a, SampleVariance a)-finalize = fromMaybe err . mFinalize-  where err = error "Statistics.Sample.WelfordOnlineMeanVariance.finalize: Emtpy Welford Online Aggreate. Add data first!"---- | Calculate mean, variance and sample variance from aggregate. Safe function.-mFinalize :: (WelfordOnline a) => WelfordExistingAggregate a -> Maybe (Mean a, Variance a, SampleVariance a)-mFinalize (WelfordExistingAggregate count mean m2)-  | count < 2 = Just (mean, m2, m2)-  | otherwise = Just (mean, m2 `divideInt` count, m2 `divideInt` (count - 1))-mFinalize WelfordExistingAggregateEmpty = Nothing----- | Add a new sample to the aggregate and compute mean and variances.-nextValue :: (WelfordOnline a) => WelfordExistingAggregate a -> a -> (WelfordExistingAggregate a, (Mean a, Variance a, SampleVariance a))-nextValue agg val =-  let agg' = addValue agg val-   in (agg', finalize agg')----- | Class for all data strucutres that can be used to computer the Welford approximation. For instance, this can be used to compute the Welford algorithm on a `Vector`s of `Fractional`, while only--- requiring to handle one `WelfordExistingAggregate`.+-- | Class for all data structures that can be used to computer the Welford approximation.+-- For instance, this can be used to compute the Welford algorithm on a `Vector`s of `Fractional`,+-- while only requiring to handle one `WelfordExistingAggregate`. class WelfordOnline a where-  plus :: a -> a -> a-  minus :: a -> a -> a-  multiply :: a -> a -> a-  divideInt :: a -> Int -> a+  plus :: a -> a -> a           -- ^ Addition.+  minus :: a -> a -> a          -- ^ Subtraction.+  multiply :: a -> a -> a       -- ^ Multiplication.+  divide :: a -> a -> a         -- ^ Division.+  divideInt :: a -> Int -> a    -- ^ Division by Integer.+  constInt :: a -> Int -> a     -- ^ Takes one example vector and a integer value and returns a vector of this integer.+  squareRootMax :: a -> a       -- ^ Compute the square root. Ensure the output is >=1e-3. Used for normalisation.+  clipValue :: Double -> a -> a -- ^ Clip the value(s) to the given range. Used for normalisation. +infixl 7  `multiply`, `divideInt`, `divide`+infixl 6  `plus`, `minus` + instance (WelfordOnline a) => WelfordOnline (VB.Vector a) where   plus          = VB.zipWith plus   {-# INLINE plus #-}@@ -130,8 +107,16 @@   {-# INLINE minus #-}   multiply      = VB.zipWith multiply   {-# INLINE multiply #-}+  divide        = VB.zipWith divide+  {-# INLINE divide #-}   divideInt x i = VB.map (`divideInt` i) x   {-# INLINE divideInt #-}+  constInt x n  = VB.map (`constInt` n) x+  {-# INLINE constInt #-}+  squareRootMax = VB.map squareRootMax+  {-# INLINE squareRootMax #-}+  clipValue v   = VB.map (clipValue v)+  {-# INLINE clipValue #-}  instance (WelfordOnline a, VS.Storable a) => WelfordOnline (VS.Vector a) where   plus          = VS.zipWith plus@@ -140,8 +125,16 @@   {-# INLINE minus #-}   multiply      = VS.zipWith multiply   {-# INLINE multiply #-}+  divide        = VS.zipWith divide+  {-# INLINE divide #-}   divideInt x i = VS.map (`divideInt` i) x   {-# INLINE divideInt #-}+  constInt x n  = VS.map (`constInt` n) x+  {-# INLINE constInt #-}+  squareRootMax = VS.map squareRootMax+  {-# INLINE squareRootMax #-}+  clipValue v   = VS.map (clipValue v)+  {-# INLINE clipValue #-}   instance (WelfordOnline a, VU.Unbox a) => WelfordOnline (VU.Vector a) where@@ -151,36 +144,137 @@   {-# INLINE minus #-}   multiply      = VU.zipWith multiply   {-# INLINE multiply #-}+  divide        = VU.zipWith divide+  {-# INLINE divide #-}   divideInt x i = VU.map (`divideInt` i) x   {-# INLINE divideInt #-}+  constInt x n  = VU.map (`constInt` n) x+  {-# INLINE constInt #-}+  squareRootMax = VU.map squareRootMax+  {-# INLINE squareRootMax #-}+  clipValue v   = VU.map (clipValue v)+  {-# INLINE clipValue #-}   instance WelfordOnline Double where-  plus = (+)+  plus          = (+)   {-# INLINE plus #-}-  minus = (-)+  minus         = (-)   {-# INLINE minus #-}-  multiply = (*)+  multiply      = (*)   {-# INLINE multiply #-}+  divide        = (/)+  {-# INLINE divide #-}   divideInt x i = x / fromIntegral i   {-# INLINE divideInt #-}+  constInt _ n  = fromIntegral n+  {-# INLINE constInt #-}+  squareRootMax = max 1e-3 . sqrt+  {-# INLINE squareRootMax #-}+  clipValue v   = min v . max (-v)+  {-# INLINE clipValue #-} + instance WelfordOnline Float where-  plus = (+)+  plus          = (+)   {-# INLINE plus #-}-  minus = (-)+  minus         = (-)   {-# INLINE minus #-}-  multiply = (*)+  multiply      = (*)   {-# INLINE multiply #-}+  divide        = (/)+  {-# INLINE divide #-}   divideInt x i = x / fromIntegral i   {-# INLINE divideInt #-}+  constInt _ n  = fromIntegral n+  {-# INLINE constInt #-}+  squareRootMax = max 1e-3 . sqrt+  {-# INLINE squareRootMax #-}+  clipValue v   = min v' . max (-v')+    where v' = realToFrac v+  {-# INLINE clipValue #-} + instance WelfordOnline Rational where-  plus = (+)+  plus          = (+)   {-# INLINE plus #-}-  minus = (-)+  minus         = (-)   {-# INLINE minus #-}-  multiply = (*)+  multiply      = (*)   {-# INLINE multiply #-}+  divide        = (/)+  {-# INLINE divide #-}   divideInt x i = x / fromIntegral i   {-# INLINE divideInt #-}+  constInt _ n  = fromIntegral n+  {-# INLINE constInt #-}+  squareRootMax = toRational . max (1e-3 :: Double) . sqrt . fromRational+  {-# INLINE squareRootMax #-}+  clipValue v   = min v' . max (-v')+    where v' = toRational v+  {-# INLINE clipValue #-}+++-- | Add one value to the current aggregate.+addValue :: (WelfordOnline a) => WelfordExistingAggregate a -> a -> WelfordExistingAggregate a+addValue (WelfordExistingAggregate count mean m2) val =+  let count' = count + 1+      delta = val `minus` mean+      mean' = mean `plus` (delta `divideInt` count')+      delta2 = val `minus` mean'+      m2' = m2 `plus` (delta `multiply` delta2)+   in WelfordExistingAggregate count' mean' m2'+addValue WelfordExistingAggregateEmpty val =+  let count' = 1+      delta' = val+      mean' = delta' `divideInt` count'+      delta2' = val `minus` mean'+      m2' = delta' `multiply` delta2'+   in WelfordExistingAggregate count' mean' m2'++-- | Add multiple values to the current aggregate. This is `foldl addValue`.+addValues :: (WelfordOnline a, Foldable f) => WelfordExistingAggregate a -> f a -> WelfordExistingAggregate a+addValues = foldl addValue+++-- | Calculate mean, variance and sample variance from aggregate. Calls `error` for `WelfordExistingAggregateEmpty`.+finalize :: (WelfordOnline a) => WelfordExistingAggregate a -> (Mean a, Variance a, SampleVariance a)+finalize = fromMaybe err . mFinalize+  where err = error "Statistics.Sample.WelfordOnlineMeanVariance.finalize: Emtpy Welford Online Aggreate. Add data first!"+++-- | Calculate mean, variance and sample variance from aggregate. Safe function.+mFinalize :: (WelfordOnline a) => WelfordExistingAggregate a -> Maybe (Mean a, Variance a, SampleVariance a)+mFinalize (WelfordExistingAggregate count mean m2)+  | count < 2 = Just (mean, m2, m2)+  | otherwise = Just (mean, m2 `divideInt` count, m2 `divideInt` (count - 1))+mFinalize WelfordExistingAggregateEmpty = Nothing+++-- | Add a new sample to the aggregate and compute mean and variances.+nextValue :: (WelfordOnline a) => WelfordExistingAggregate a -> a -> (WelfordExistingAggregate a, (Mean a, Variance a, SampleVariance a))+nextValue agg val =+  let agg' = addValue agg val+   in (agg', finalize agg')+++-- | Normalise the given input assuming the learned standard deviation with sample variance to zero mean+-- and unit variance. For the first 100 values the output is clipped to @(-3, 3)@.+normaliseToZeroMeanUnitVariance :: (WelfordOnline a) => WelfordExistingAggregate a -> a -> a+normaliseToZeroMeanUnitVariance WelfordExistingAggregateEmpty x = clipValue 3 x+normaliseToZeroMeanUnitVariance wel x+  | welfordCountUnsafe wel < 100 = clipValue 3 $ (x `minus` mean) `divide` squareRootMax variance+  | otherwise = (x `minus` mean) `divide` squareRootMax variance+  where+    (mean, _, variance) = finalize wel+++-- | Denormalise from a zero mean unit variance normalised value (see `normaliseToZeroMeanUnitVariance`) to+-- the original value(s).+denormaliseFromZeroMeanUnitVariance :: (WelfordOnline a) => WelfordExistingAggregate a -> a -> a+denormaliseFromZeroMeanUnitVariance WelfordExistingAggregateEmpty x = x+denormaliseFromZeroMeanUnitVariance wel x = (x `multiply` squareRootMax variance) `plus` mean+  where+    (mean, _, variance) = finalize wel++
test/Statistics/Sample/WelfordOnlineTest.hs view
@@ -61,3 +61,12 @@   -- print (wMean, wVarSample)       eps = min 0.01 $ max 0.001 (0.001 * mean)   return $ epsEqWith eps wMean mean .&&. epsEqWith eps wVarSample var+++prop_Normalise :: Gen Property+prop_Normalise = do+  vals <- sized $ \n -> VB.fromList <$> generateNValues (100 + 5 * n)+  let wel = addValues WelfordExistingAggregateEmpty vals :: WelfordExistingAggregate Double+  testVal <- arbitrary+  return $ epsEqWith 0.002 testVal (denormaliseFromZeroMeanUnitVariance wel (normaliseToZeroMeanUnitVariance wel testVal))+
welford-online-mean-variance.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.0.+-- This file has been generated from package.yaml by hpack version 0.35.1. -- -- see: https://github.com/sol/hpack  name:           welford-online-mean-variance-version:        0.1.0.4+version:        0.2.0.0 synopsis:       Online computation of mean and variance using the Welford algorithm. description:    Please see the README on GitHub at <https://github.com/githubuser/welford-online-mean-variance#readme> category:       Statistics