on master help sign in
Add doc, powersums and mean

Rename "whole" to "cumulative"

commit0eadd04adae4f676a5a8177d4da25bada9e9dabb authorHarendra Kumar <harendra@composewell.com> dateThu 24 Mar 2022, 22:19 (5 years ago) parentf95fcbd
4 files changed+135−21
benchmark/Streamly/Benchmark/Data/Fold/Window.hs+20−1 core/src/Streamly/Internal/Data/Fold/Window.hs+109−19 core/streamly-core.cabal+1−1 test/Streamly/Test/Data/Fold/Window.hs+5
benchmark/Streamly/Benchmark/Data/Fold/Window.hs+20−1
@@ -89,9 +89,24 @@ main =
8989 , benchWithFold numElements "sum (window size 1000)"
9090 (Ring.slidingWindow 1000 Window.sum)
9191 , benchWithFold numElements "sum (entire stream)"
92− (Window.whole Window.sum)
92+ (Window.cumulative Window.sum)
9393 , benchWithFold numElements "sum (Data.Fold)"
9494 Fold.sum
95+
96+ , benchWithFold numElements "mean (window size 100)"
97+ (Ring.slidingWindow 100 Window.mean)
98+ , benchWithFold numElements "mean (window size 1000)"
99+ (Ring.slidingWindow 1000 Window.mean)
100+ , benchWithFold numElements "mean (entire stream)"
101+ (Window.cumulative Window.mean)
102+ , benchWithFold numElements "mean (Data.Fold)"
103+ Fold.mean
104+
105+ , benchWithFold numElements "powerSum 2 (window size 100)"
106+ (Ring.slidingWindow 100 (Window.powerSum 2))
107+ , benchWithFold numElements "powerSum 2 (entire stream)"
108+ (Window.cumulative (Window.powerSum 2))
109+
95110 ]
96111 , bgroup
97112 "scan"
@@ -111,5 +126,9 @@ main =
111126 (Ring.slidingWindow 100 Window.sum)
112127 , benchWithPostscan numElements "sum (window size 1000)"
113128 (Ring.slidingWindow 1000 Window.sum)
129+ , benchWithPostscan numElements "mean (window size 100)"
130+ (Ring.slidingWindow 100 Window.mean)
131+ , benchWithPostscan numElements "mean (window size 1000)"
132+ (Ring.slidingWindow 1000 Window.mean)
114133 ]
115134 ]
core/src/Streamly/Internal/Data/Fold/Window.hs+109−19
@@ -1,13 +1,54 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Fold.Window
3+-- Copyright : (c) 2020 Composewell Technologies
4+-- License : Apache-2.0
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+-- Simple incremental statistical measures over a stream of data. All
10+-- operations use numerically stable floating point arithmetic.
11+--
12+-- Measurements can be performed over the entire input stream or on a sliding
13+-- window of fixed or variable size. Where possible, measures are computed
14+-- online without buffering the input stream.
15+--
16+-- Currently there is no overflow detection.
17+--
18+-- For more advanced statistical measures see the @streamly-statistics@
19+-- package.
20+
121module Streamly.Internal.Data.Fold.Window
2−( lmap
3−, length
4−, whole
5−, sum
6−, sumInt
7−, minimum
8−, maximum
9−, range
10−)
22+ (
23+ -- * Incremental Folds
24+ -- | Folds of type @Fold m (a, Maybe a) b@ are incremental sliding window
25+ -- folds. An input of type @(a, Nothing)@ indicates that the input element
26+ -- @a@ is being inserted in the window without ejecting an old value
27+ -- increasing the window size by 1. An input of type @(a, Just a)@
28+ -- indicates that the first element is being inserted in the window and the
29+ -- second element is being removed from the window, the window size remains
30+ -- the same. The window size can only increase and never decrease.
31+ --
32+ -- You can compute the statistics over the entire stream using sliding
33+ -- window folds by keeping the second element of the input tuple as
34+ -- @Nothing@.
35+ --
36+ lmap
37+ , cumulative
38+
39+ -- ** Sums
40+ , length
41+ , sum
42+ , sumInt
43+ , powerSum
44+ , powerSumFrac
45+
46+ -- ** Location
47+ , minimum
48+ , maximum
49+ , range
50+ , mean
51+ )
1152where
1253
1354import Data.Bifunctor(bimap)
@@ -40,14 +81,14 @@ import qualified Streamly.Data.Fold as Fold
4081lmap :: (c -> a) -> Fold m (a, Maybe a) b -> Fold m (c, Maybe c) b
4182lmap f = Fold.lmap (bimap f (f <$>))
4283
43−-- | Convert a rolling fold to a normal fold using the entire input stream as a
44−-- single window.
84+-- | Convert an incremental fold to a cumulative fold using the entire input
85+-- stream as a single window.
4586--
46−-- >>> whole f = Fold.lmap (\x -> (x, Nothing)) f
87+-- >>> cumulative f = Fold.lmap (\x -> (x, Nothing)) f
4788--
48−{-# INLINE whole #-}
49−whole :: Fold m (a, Maybe a) b -> Fold m a b
50−whole = Fold.lmap (, Nothing)
89+{-# INLINE cumulative #-}
90+cumulative :: Fold m (a, Maybe a) b -> Fold m a b
91+cumulative = Fold.lmap (, Nothing)
5192
5293-------------------------------------------------------------------------------
5394-- Sum
@@ -85,6 +126,12 @@ sumInt = Fold step initial extract
85126--
86127-- | Sum of all the elements in a rolling window:
87128--
129+-- \(S = \sum_{i=1}^n x_{i}\)
130+--
131+-- This is the first power sum.
132+--
133+-- >>> sum = powerSum 1
134+--
88135-- Uses Kahan-Babuska-Neumaier style summation for numerical stability of
89136-- floating precision arithmetic.
90137--
@@ -127,6 +174,10 @@ sum = Fold step initial extract
127174
128175-- | The number of elements in the rolling window.
129176--
177+-- This is the \(0\)th power sum.
178+--
179+-- >>> length = powerSum 0
180+--
130181{-# INLINE length #-}
131182length :: (Monad m, Num b) => Fold m (a, Maybe a) b
132183length = Fold.foldl' step 0
@@ -136,6 +187,28 @@ length = Fold.foldl' step 0
136187 step w (_, Nothing) = w + 1
137188 step w _ = w
138189
190+-- | Sum of the \(k\)th power of all the elements in a rolling window:
191+--
192+-- \(S_k = \sum_{i=1}^n x_{i}^k\)
193+--
194+-- >>> powerSum k = lmap (^ k) sum
195+--
196+-- /Space/: \(\mathcal{O}(1)\)
197+--
198+-- /Time/: \(\mathcal{O}(n)\)
199+{-# INLINE powerSum #-}
200+powerSum :: (Monad m, Num a) => Int -> Fold m (a, Maybe a) a
201+powerSum k = lmap (^ k) sum
202+
203+-- | Like 'powerSum' but powers can be negative or fractional. This is slower
204+-- than 'powerSum' for positive intergal powers.
205+--
206+-- >>> powerSumFrac p = lmap (** p) sum
207+--
208+{-# INLINE powerSumFrac #-}
209+powerSumFrac :: (Monad m, Floating a) => a -> Fold m (a, Maybe a) a
210+powerSumFrac p = lmap (** p) sum
211+
139212-------------------------------------------------------------------------------
140213-- Location
141214-------------------------------------------------------------------------------
@@ -149,8 +222,8 @@ length = Fold.foldl' step 0
149222--
150223-- | The minimum element in a rolling window.
151224--
152−-- If you want to compute the minimum of the entire stream Fold.minimum from streamly
153−-- package would be much faster.
225+-- If you want to compute the minimum of the entire stream Fold.minimum from
226+-- streamly package would be much faster.
154227--
155228-- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size.
156229--
@@ -199,8 +272,8 @@ minimum = Fold step initial extract
199272--
200273-- | The maximum element in a rolling window.
201274--
202−-- If you want to compute the maximum of the entire stream Fold.maximum from streamly
203−-- package would be much faster.
275+-- If you want to compute the maximum of the entire stream Fold.maximum from
276+-- streamly package would be much faster.
204277--
205278-- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size.
206279--
@@ -246,6 +319,23 @@ maximum = Fold step initial extract
246319 $ fromMaybe (0, error "max: Empty stream")
247320 $ DQ.head q
248321
322+-- | Arithmetic mean of elements in a sliding window:
323+--
324+-- \(\mu = \frac{\sum_{i=1}^n x_{i}}{n}\)
325+--
326+-- This is also known as the Simple Moving Average (SMA) when used in the
327+-- sliding window and Cumulative Moving Avergae (CMA) when used on the entire
328+-- stream.
329+--
330+-- >>> mean = Fold.teeWith (/) sum length
331+--
332+-- /Space/: \(\mathcal{O}(1)\)
333+--
334+-- /Time/: \(\mathcal{O}(n)\)
335+{-# INLINE mean #-}
336+mean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a
337+mean = Fold.teeWith (/) sum length
338+
249339
250340-- | The difference between the maximum and minimum elements of a rolling window.
251341--
core/streamly-core.cabal+1−1
@@ -244,7 +244,6 @@ library
244244 , Streamly.Internal.Data.Fold.Step
245245 , Streamly.Internal.Data.Refold.Type
246246 , Streamly.Internal.Data.Fold.Type
247− , Streamly.Internal.Data.Fold.Window
248247 , Streamly.Internal.Data.Stream.StreamD.Step
249248 , Streamly.Internal.Data.Stream.StreamD.Type
250249 , Streamly.Internal.Data.Stream.StreamDK.Type
@@ -286,6 +285,7 @@ library
286285 , Streamly.Internal.Data.Unfold.Enumeration
287286 , Streamly.Internal.Data.Fold.Tee
288287 , Streamly.Internal.Data.Fold
288+ , Streamly.Internal.Data.Fold.Window
289289 , Streamly.Internal.Data.Parser
290290 , Streamly.Internal.Data.Pipe
291291 , Streamly.Internal.Data.Stream.Serial
test/Streamly/Test/Data/Fold/Window.hs+5
@@ -29,6 +29,7 @@ main = hspec $ do
2929 $ c1 >= -1 * deviationLimit && c1 <= deviationLimit
3030
3131 describe "Sum" $ testFunc sum
32+ describe "mean" $ testFunc mean
3233
3334 describe "Correctness" $ do
3435 let winSize = 3
@@ -59,3 +60,7 @@ main = hspec $ do
5960 let scanInf = [1, 2, 3, 4, 5, 12] :: [Double]
6061 scanWin = [1, 2, 3, 3, 3, 9] :: [Double]
6162 testFunc testCase2 sum scanInf scanWin
63+ describe "mean" $ do
64+ let scanInf = [1, 1, 1, 1, 1, 2] :: [Double]
65+ scanWin = [1, 1, 1, 1, 1, 3] :: [Double]
66+ testFunc testCase2 mean scanInf scanWin