packages feed

welford-online-mean-variance 0.1.0.2 → 0.1.0.4

raw patch · 4 files changed

+96/−49 lines, 4 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Statistics.Sample.WelfordOnlineMeanVariance: [welfordCount] :: WelfordExistingAggregate a -> !Int
- Statistics.Sample.WelfordOnlineMeanVariance: [welfordM2] :: WelfordExistingAggregate a -> !a
- Statistics.Sample.WelfordOnlineMeanVariance: [welfordMean] :: WelfordExistingAggregate a -> !a
+ Statistics.Sample.WelfordOnlineMeanVariance: [welfordCountUnsafe] :: WelfordExistingAggregate a -> !Int
+ Statistics.Sample.WelfordOnlineMeanVariance: [welfordM2Unsafe] :: WelfordExistingAggregate a -> !a
+ Statistics.Sample.WelfordOnlineMeanVariance: [welfordMeanUnsafe] :: WelfordExistingAggregate a -> !a
+ Statistics.Sample.WelfordOnlineMeanVariance: mFinalize :: WelfordOnline a => WelfordExistingAggregate a -> Maybe (Mean a, Variance a, SampleVariance a)
+ Statistics.Sample.WelfordOnlineMeanVariance: mWelfordMean :: WelfordExistingAggregate a -> Maybe a
+ Statistics.Sample.WelfordOnlineMeanVariance: newWelfordAggregateDef :: WelfordOnline a => a -> WelfordExistingAggregate a
+ Statistics.Sample.WelfordOnlineMeanVariance: welfordCount :: WelfordExistingAggregate a -> Int
+ Statistics.Sample.WelfordOnlineMeanVariance: welfordMeanDef :: WelfordOnline a => a -> WelfordExistingAggregate a -> a

Files

README.md view
@@ -6,9 +6,9 @@     example :: [Double] -> IO ()     example vals = do       let n = fromIntegral (length vals)-      mean = VB.sum vals / n-      var = VB.sum (VB.map (\x -> (x - mean) ^ 2) vals) / (n - 1)-      (wMean, _, wVarSample) = finalize $ foldl' addValue WelfordExistingAggregateEmpty (VB.toList vals)+          mean = sum vals / n+          var = sum (map (\x -> (x - mean) ^ 2) vals) / (n - 1)+          (wMean, _, wVarSample) = finalize $ foldl' addValue WelfordExistingAggregateEmpty vals       print (mean, var)       print (wMean, wVarSample) 
src/Statistics/Sample/WelfordOnlineMeanVariance.hs view
@@ -1,14 +1,20 @@+{-# OPTIONS_GHC -Wno-partial-fields #-} {-# LANGUAGE DeriveAnyClass    #-} {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE FlexibleInstances #-} module Statistics.Sample.WelfordOnlineMeanVariance   ( WelfordExistingAggregate(..)   , WelfordOnline (..)+  , newWelfordAggregateDef+  , newWelfordAggregate+  , welfordCount+  , mWelfordMean+  , welfordMeanDef   , addValue   , addValues+  , mFinalize   , finalize   , nextValue-  , newWelfordAggregate   , isWelfordExistingAggregateEmpty   , Mean   , Variance@@ -16,6 +22,7 @@   ) where  import           Control.DeepSeq+import           Data.Maybe           (fromMaybe) import           Data.Serialize import qualified Data.Vector          as VB import qualified Data.Vector.Storable as VS@@ -29,23 +36,84 @@  -- | For the storage of required information. data WelfordExistingAggregate a-  = WelfordExistingAggregateEmpty+  = WelfordExistingAggregateEmpty -- ^ Emtpy aggregate. Needed as `a` can be of any type, which, hence, allows us to postpone determining `a` to when we receive the first value.   | WelfordExistingAggregate-      { welfordCount :: !Int-      , welfordMean  :: !a-      , welfordM2    :: !a+      { welfordCountUnsafe :: !Int+      , welfordMeanUnsafe  :: !a+      , welfordM2Unsafe    :: !a       }   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 --- | Create a new empty Aggreate for the calculation.-newWelfordAggregate :: WelfordExistingAggregate a-newWelfordAggregate = WelfordExistingAggregateEmpty+-- | Get counter safely, returns Nothing if `WelfordExistingAggregateEmpty`.+welfordCount :: WelfordExistingAggregate a -> Int+welfordCount WelfordExistingAggregateEmpty    = 0+welfordCount (WelfordExistingAggregate c _ _) = c +-- | Get counter safely, returns Nothing if `WelfordExistingAggregateEmpty`.+mWelfordMean :: WelfordExistingAggregate a -> Maybe a+mWelfordMean WelfordExistingAggregateEmpty    = Nothing -- Cannot create value `a`+mWelfordMean (WelfordExistingAggregate _ m _) = Just m +-- | Get counter 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 WelfordOnline a where@@ -116,38 +184,3 @@   {-# INLINE multiply #-}   divideInt x i = x / fromIntegral i   {-# INLINE divideInt #-}----- | 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.-finalize :: (WelfordOnline a) => WelfordExistingAggregate a -> (Mean a, Variance a, SampleVariance a)-finalize (WelfordExistingAggregate count mean m2)-  | count < 2 = (mean, m2, m2)-  | otherwise = (mean, m2 `divideInt` count, m2 `divideInt` (count - 1))-finalize WelfordExistingAggregateEmpty = error "finalize: Emtpy Welford Online Aggreate. Add data first!"---- | 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')
test/Statistics/Sample/WelfordOnlineTest.hs view
@@ -1,6 +1,7 @@ module Statistics.Sample.WelfordOnlineTest where  import           Control.Monad+import           Control.Monad.IO.Class import           Data.List                                   (foldl') import qualified Data.Vector                                 as VB import           Test.Tasty.QuickCheck@@ -47,3 +48,16 @@       ress = map mkRes vecs   return $     foldl1 (.&&.) $ map (\(eps, wMean, mean, wVarSample, var) -> epsEqWith eps wMean mean .&&. epsEqWith eps wVarSample var) ress+++prop_readme_example :: Int -> Gen Property+prop_readme_example nr = do+  vals <- generateNValues (abs nr + 2)+  let n = fromIntegral (length vals)+      mean = sum vals / n+      var = sum (map (\x -> (x - mean) ^ 2) vals) / (n - 1)+      (wMean, _, wVarSample) = finalize $ foldl' addValue WelfordExistingAggregateEmpty vals+  -- print (mean, var)+  -- print (wMean, wVarSample)+      eps = min 0.01 $ max 0.001 (0.001 * mean)+  return $ epsEqWith eps wMean mean .&&. epsEqWith eps wVarSample var
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.34.4.+-- This file has been generated from package.yaml by hpack version 0.35.0. -- -- see: https://github.com/sol/hpack  name:           welford-online-mean-variance-version:        0.1.0.2+version:        0.1.0.4 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@@ -13,7 +13,7 @@ bug-reports:    https://github.com/schnecki/welford-online-mean-variance/issues author:         Manuel Schneckenreither maintainer:     manuel.schnecki@gmail.com-copyright:      2022 Manuel Schneckenreither+copyright:      2023 Manuel Schneckenreither license:        BSD3 license-file:   LICENSE build-type:     Simple