packages feed

welford-online-mean-variance (empty) → 0.1.0.0

raw patch · 9 files changed

+334/−0 lines, 9 filesdep +QuickCheckdep +basedep +cerealsetup-changed

Dependencies added: QuickCheck, base, cereal, deepseq, tasty, tasty-discover, tasty-quickcheck, vector, welford-online-mean-variance

Files

+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog for `welford`++All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the+[Haskell Package Versioning Policy](https://pvp.haskell.org/).++## Unreleased++## 0.1.0.0 - YYYY-MM-DD
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2022++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,1 @@+# Welford: Online mean and variance computation
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Lib.hs view
@@ -0,0 +1,6 @@+module Lib+    ( someFunc+    ) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ src/Statistics/Sample/WelfordOnlineMeanVariance.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE DeriveAnyClass    #-}+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE FlexibleInstances #-}+module Statistics.Sample.WelfordOnlineMeanVariance+  ( WelfordExistingAggregate(..)+  , addValue+  , finalize+  , nextValue+  , newWelfordAggregate+  , Mean+  , Variance+  , SampleVariance+  ) where++import           Control.DeepSeq+import           Data.Serialize+import qualified Data.Vector          as VB+import qualified Data.Vector.Storable as VS+import qualified Data.Vector.Unboxed  as VU+import           GHC.Generics++type Mean a = a+type Variance a = a+type SampleVariance a = a+++-- | For the storage of required information.+data WelfordExistingAggregate a+  = WelfordExistingAggregateEmpty+  | WelfordExistingAggregate+      { welfordCount :: !Int+      , welfordMean  :: !a+      , welfordM2    :: !a+      }+  deriving (Eq, Show, Read, Generic, NFData, Serialize)+++-- | Create a new empty Aggreate for the calculation.+newWelfordAggregate :: WelfordExistingAggregate a+newWelfordAggregate = WelfordExistingAggregateEmpty+++-- | 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+  plus :: a -> a -> a+  minus :: a -> a -> a+  multiply :: a -> a -> a+  divideInt :: a -> Int -> a+++instance (WelfordOnline a) => WelfordOnline (VB.Vector a) where+  plus          = VB.zipWith plus+  {-# INLINE plus #-}+  minus         = VB.zipWith minus+  {-# INLINE minus #-}+  multiply      = VB.zipWith multiply+  {-# INLINE multiply #-}+  divideInt x i = VB.map (`divideInt` i) x+  {-# INLINE divideInt #-}++instance (WelfordOnline a, VS.Storable a) => WelfordOnline (VS.Vector a) where+  plus          = VS.zipWith plus+  {-# INLINE plus #-}+  minus         = VS.zipWith minus+  {-# INLINE minus #-}+  multiply      = VS.zipWith multiply+  {-# INLINE multiply #-}+  divideInt x i = VS.map (`divideInt` i) x+  {-# INLINE divideInt #-}+++instance (WelfordOnline a, VU.Unbox a) => WelfordOnline (VU.Vector a) where+  plus          = VU.zipWith plus+  {-# INLINE plus #-}+  minus         = VU.zipWith minus+  {-# INLINE minus #-}+  multiply      = VU.zipWith multiply+  {-# INLINE multiply #-}+  divideInt x i = VU.map (`divideInt` i) x+  {-# INLINE divideInt #-}+++instance WelfordOnline Double where+  plus = (+)+  {-# INLINE plus #-}+  minus = (-)+  {-# INLINE minus #-}+  multiply = (*)+  {-# INLINE multiply #-}+  divideInt x i = x / fromIntegral i+  {-# INLINE divideInt #-}++instance WelfordOnline Float where+  plus = (+)+  {-# INLINE plus #-}+  minus = (-)+  {-# INLINE minus #-}+  multiply = (*)+  {-# INLINE multiply #-}+  divideInt x i = x / fromIntegral i+  {-# INLINE divideInt #-}++instance WelfordOnline Rational where+  plus = (+)+  {-# INLINE plus #-}+  minus = (-)+  {-# INLINE minus #-}+  multiply = (*)+  {-# 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'+++-- | 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')+++-- # For a new value newValue, compute the new count, new mean, the new M2.+-- # mean accumulates the mean of the entire dataset+-- # M2 aggregates the squared distance from the mean+-- # count aggregates the number of samples seen so far+-- def update(existingAggregate, newValue):+--     (count, mean, M2) = existingAggregate+--     count' += 1+--     delta = newValue - mean+--     mean' += delta / count'+--     delta2 = newValue - mean'+--     M2' += delta * delta2+--     return (count', mean', M2')++-- # Retrieve the mean, variance and sample variance from an aggregate+-- def finalize(existingAggregate):+--     (count, mean, M2) = existingAggregate+--     if count < 2:+--         return float("nan")+--     else:+--         (mean, variance, sampleVariance) = (mean, M2 / count, M2 / (count - 1))+--         return (mean, variance, sampleVariance)
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
+ test/Statistics/Sample/WelfordOnlineTest.hs view
@@ -0,0 +1,53 @@+module Statistics.Sample.WelfordOnlineTest where++import           Control.Monad+import           Data.List                                   (foldl')+import qualified Data.Vector                                 as VB+import           Debug.Trace+import           Test.Tasty.QuickCheck++import           Statistics.Sample.WelfordOnlineMeanVariance++main :: IO ()+main = putStrLn "Test suite not yet implemented"+++-- | Recursively generate candles+generateNValues :: Int -> Gen [Double]+generateNValues n+  | n <= 0 = return []+  | otherwise =  do+      x <- arbitrary+      (x:) <$> generateNValues (n - 1)++epsEqWith :: (Ord a, Fractional a, Show a) => a -> a -> a -> Property+epsEqWith eps a b+  | abs (a - b) <= eps = a === a+  | otherwise = a === b+++prop_MeanAndVariance :: Gen Property+prop_MeanAndVariance = do+  vals <- sized $ \n -> generateNValues (10 + 5 * n)+  let n = fromIntegral (length vals)+      mean = sum vals / n+      var = sum (map (\x -> (x - mean) ^ 2) vals) / (n - 1)+      (wMean, _, wVarSample) = snd $ foldl' (nextValue . fst) (WelfordExistingAggregateEmpty, (0, 0, 0)) vals+      eps = min 0.01 $ max 0.001 (0.001 * mean)+  return $ epsEqWith eps wMean mean .&&. epsEqWith eps wVarSample var+++prop_MeanAndVarianceVector :: Gen Property+prop_MeanAndVarianceVector = do+  len <- chooseInt (1, 100)+  vecs <- sized $ \n -> replicateM len (VB.fromList <$> generateNValues (10 + 5 * n))+  let mkRes vals =+        let n = fromIntegral (VB.length vals)+            mean = VB.sum vals / n+            var = VB.sum (VB.map (\x -> (x - mean) ^ 2) vals) / (n - 1)+            (wMean, _, wVarSample) = snd $ foldl' (nextValue . fst) (WelfordExistingAggregateEmpty, (0, 0, 0)) (VB.toList vals)+            eps = min 0.01 $ max 0.001 (0.001 * mean)+        in (eps, wMean, mean, wVarSample, var)+      ress = map mkRes vecs+  return $+    foldl1 (.&&.) $ map (\(eps, wMean, mean, wVarSample, var) -> epsEqWith eps wMean mean .&&. epsEqWith eps wVarSample var) ress
+ welford-online-mean-variance.cabal view
@@ -0,0 +1,63 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           welford-online-mean-variance+version:        0.1.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+homepage:       https://github.com/schnecki/welford-online-mean-variance#readme+bug-reports:    https://github.com/schnecki/welford-online-mean-variance/issues+author:         Manuel Schneckenreither+maintainer:     manuel.schnecki@gmail.com+copyright:      2022 Manuel Schneckenreither+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    CHANGELOG.md++source-repository head+  type: git+  location: https://github.com/schnecki/welford-online-mean-variance++library+  exposed-modules:+      Lib+      Statistics.Sample.WelfordOnlineMeanVariance+  other-modules:+      Paths_welford_online_mean_variance+  hs-source-dirs:+      src+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+  build-depends:+      base >=4.7 && <5+    , cereal+    , deepseq+    , vector+  default-language: Haskell2010++test-suite welford-online-mean-variance-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Statistics.Sample.WelfordOnlineTest+      Paths_welford_online_mean_variance+  hs-source-dirs:+      test+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      QuickCheck+    , base >=4.7 && <5+    , cereal+    , deepseq+    , tasty+    , tasty-discover+    , tasty-quickcheck+    , vector+    , welford-online-mean-variance+  default-language: Haskell2010