[Haskell-beginners] Simple Moving Average of a list of real numbers

Michael Orlitzky michael at orlitzky.com
Wed Nov 27 02:39:17 UTC 2013


On 11/26/2013 12:54 PM, Kim-Ee Yeoh wrote:
> 
> Hi Michael,
> 
> When I read OP's request, I understood it as what Ozgur had interpreted,
> i.e. last N items for a /constant/ N, also known as a moving window average.
> 
> Your running average solution is interesting too!

Oh, sorry. Statistics is down the hall =)

You need to modify the scanl1 solution a little bit, but it should still
work somewhat-efficiently only making one pass through the list and not
using any partial functions.

If the "window size" n is fixed, we need more information at each point
in the list:

  1. The item itself
  2. What number we should subtract from the previous sum
  3. What the previous divisor was
  4. What the new divisor is

(I'm assuming you want the first n items to be the running average, i.e.
my first implementation.)

The divisor that we'll use at each point is,

  [1, 2, ..., n, n, n...]

and that can be zipped with the samples just like before. The other
thing that we need is the item that will be "dropped" from the average.
If the window is fixed, we'll be dropping one number and adding one
number every time we move to a new item in the list (once we've
processed n of them, at least).

Before we've processed n of them, we don't want to subtract anything
from the previous sum -- we want the running average. We can accomplish
this by sticking n-1 zeros on the head of the samples, and zipping
*that* with the samples.

I haven't commented this version, but if you understand the last one,
you can figure out what it does. Here's the example Ozgur used:

  *Main> :l moving_average.hs
  [1 of 1] Compiling Main             ( moving_average.hs, interpreted )
  Ok, modules loaded: Main.
  *Main> let samples = [1,2,3,4,5,6,7,8,9,10] :: [Float]
  *Main> moving_average 3 samples
  [1.0,1.5,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]

(They agree.)

It can do a million items pretty quickly considering we're in ghci:

  *Main> let samples = [1..1000000] :: [Float]
  *Main> :set +s
  *Main> length $ moving_average 10 samples
  1000000
  (1.08 secs, 545400640 bytes)


--------

module Main
where

fst3 :: (a,b,c) -> a
fst3 (x, _, _) = x

moving_average :: (Fractional a) => Int -> [a] -> [a]
moving_average _ [] = []
moving_average n samples
  | n <= 0 = []
  | otherwise =
      map fst3 $ scanl1 average sample_triples
      where
        divisors = map fromIntegral $ [1..n] ++ (repeat n)
        n_agos = (map fromIntegral (replicate (n-1) 0)) ++ samples
        sample_triples = zip3 samples divisors n_agos

        average :: (Fractional b) => (b,b,b) -> (b,b,b) -> (b,b,b)
        average (prev_avg, prev_div, dropme) (sample, divisor, n_ago) =
          (new_avg, divisor, n_ago)
          where
            prev_sum = prev_avg * prev_div
            new_avg = (prev_sum + sample - dropme) / divisor



More information about the Beginners mailing list