Search code examples
arrayshaskellmoving-averageweighted-average

Moving average in Haskell


Given a list of weights:

let weights = [0.1, 0.2, 0.4, 0.2, 0.1]

and an array of measurements, I want to implement the weighted average.

This is how I would do it in Python:

y=[]
w = length(weights)
for n in range(w,len(x)-w):
    y[n-w/2-1]=sum([a*b for a,b in zip(weights,x[n-w/2:n+w/2+1])])
    #y[n-3]=W[1]*x[n-2]+W[2]*x[n-1]+W[3]*x[n]+W[4]*x[n+1]+W[5]*x[n+2]

I know Haskell doesn't have arrays, what I'm trying to achieve is a low-pass-filter, in which I can define the weights manually.


Solution

  • tails gives you a list of the tails of the input list. So tails [1,2,3] = [[1,2,3],[2,3],[3],[]]. Since we don't need the last empty list we use (init.tails) to get everything in the tails list except the last element.

    import Data.List (tails)
    averages :: Num a => [a] -> [a] -> [a]
    averages weights xs = sum . zipWith (*) weights <$> (init.tails) xs
    

    Note that this very likely doesn't behave the way you want at the start and end of the list. Especially because it behaves different at the start then at the end. The first element will be the average of the first length weight element, but the last element will only be head weight * last xs.

    If you want the behaviour of the end at the start you can use something like this:

    import Data.List (tails)
    averages :: Num a => [a] -> [a] -> [a]
    averages weights xs = sum . zipWith (*) weights <$>
      (init.tails) (replicate (length weights - 1) 0 ++ xs)
    

    If you want the behaviour of the end at the start you can use this:

    import Data.List (tails)
    averages :: Num a => [a] -> [a] -> [a]
    averages weights xs = sum . zipWith (*) weights <$>
      takeWhile (not . null . drop (l-1)) (tails xs)
      where l = length weights
    

    If you want to start and end with the first/last element being multiplied with the center element of the weights list we have to use a combination of the two above answers:

    import Data.List (tails)
    averages :: Num a => [a] -> [a] -> [a]
    averages weights xs = sum . zipWith (*) weights <$>
      takeWhile (not . null . drop half) (replicate half 0 ++ xs)
      where half = length weights `quot` 2