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 +3/−3
- src/Statistics/Sample/WelfordOnlineMeanVariance.hs +76/−43
- test/Statistics/Sample/WelfordOnlineTest.hs +14/−0
- welford-online-mean-variance.cabal +3/−3
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