Скользящая средняя в Хаскеле
Дан список весов:
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 нет массивов, я пытаюсь добиться фильтра нижних частот, в котором я могу определять веса вручную.
3 ответа
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
Если вы хотите начать и закончить с умножением первого / последнего элемента на центральный элемент списка весов, мы должны использовать комбинацию из двух приведенных выше ответов:
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
Скользящее среднее можно рассчитать с помощью мучного автомата, где внутренним состоянием являются предыдущие значения.
Я приведу пример скользящего среднего по трем аргументам, вы можете поиграться, например, сделать его параметризуемым по размеру.
Мили-машина - это, по сути, начальное состояние и функция "состояние + вход" для функции "новое состояние + выход":
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]
Вы можете сбросить первые полученные значения, так как внутреннее состояние машины "прогревается".
Для машины скользящего среднего произвольного размера вы можете использовать Data.Sequence
, так как это намного лучше структура данных, когда вы нажимаете на один конец, в то время как всплывающее окно с другого, а затем один связанный список, []
,
Почему я говорю о машине Мили? Потому что в какой-то момент вы, скорее всего, столкнетесь с ситуацией, когда вам нужно использовать некоторую потоковую библиотеку в Haskell: pipes
, conduit
или же machines
, Тогда подход Мили-машины будет единственным разумным решением.
Также вы можете делать авторегрессивные модели!
Застежка-молния обеспечивает автоматическое выравнивание:
wma :: Num a => [a] -> [a] -> [a]
wma weights = map (sum . zipWith (*) weights ) -- weighted-moving-average
. foldr (zipWith (:)) (repeat [])
. take (length weights)
. tails
( см. также).