Haskell 的移动平均线

Moving average in Haskell

给定权重列表:

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

和一组测量值,我想实现加权平均。

这就是我在 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]

我知道 Haskell 没有数组,我想要实现的是一个 low-pass-filter,我可以在其中手动定义权重。

tails 给你一个输入列表尾部的列表。所以tails [1,2,3] = [[1,2,3],[2,3],[3],[]]。因为我们不需要最后一个空列表,所以我们使用 (init.tails) 来获取尾部列表中除最后一个元素之外的所有内容。

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

请注意,在列表的开头和结尾,这很可能不会按照您想要的方式运行。特别是因为它在开始时和结束时表现不同。第一个元素将是第一个 length weight 元素的平均值,但最后一个元素只会是 head weight * last xs.

如果你想要开始时结束的行为,你可以使用这样的东西:

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

如果你想要开始时结束的行为,你可以使用这个:

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

如果你想开始和结束 first/last 元素与权重列表的中心元素相乘,我们必须使用以上两个答案的组合:

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

移动平均线可以用粉状机器计算,其中内部状态是以前的值。

我将展示三个参数的移动平均线示例,您可以 fiddle使其大小可参数化。

Mealy machine本质上是一个初始状态,"state + input"到"new state + output"函数:

Mealy i o ~ (s, s -> i -> (o, s))

让我们假设初始状态全为零,并编写一个移动平均超过 3 的函数。

type S = (Double, Double)
type I = Double
type O = Double

initialState :: S
initialState = (0, 0)

weight0, weight1, weight2 :: Double
weight0 = 0.25
weight1 = 0.5
weight2 = 0.25

ma :: S -> I -> (O, S)
ma (x0, x1) x2 = (o, s)
    where
    s = (x1, x2)
    o = x0 * weight0 + x1 * weight1 + x2 * weight2

现在我们得到了所有的部分,让我们运行输入机器:

runMealy :: (S -> I -> (O, S)) -> S -> [I] -> [O]
runMealy _ _ [] = []
runMealy f s (x : xs) =
    let (o, s') = f s x
    in o : runMealy f s' xs

并尝试一下:

λ *Main > runMealy ma initialState [1,2,3,4,5,6,7,8,9]
[0.25,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0]

您可以删除第一个生成的值,因为机器内部状态是 "warming up"。


对于任意大小的移动平均机,您可以使用 Data.Sequence,因为当您推到一端时它是更好的数据结构,而从另一端弹出,然后 单链表, [].


为什么我要谈论 Mealy 机器?因为在某些时候,您很可能会 运行 遇到需要在 Haskell 中使用某些流媒体库的情况:pipesconduitmachines。那么Mealy机器的方法将是唯一合理的解决方案。

你也可以制作自回归模型!

压缩会自动处理对齐:

wma :: Num a => [a] -> [a] -> [a]
wma weights = map (sum . zipWith (*) weights )   -- weighted-moving-average
                . foldr (zipWith (:)) (repeat []) 
                . take (length weights) 
                . tails 

(see also).