I implemented exponentially weighted moving average (ewma) in python3 and in Haskell (compiled). It takes about the same time. However when this function is applied twice, haskell version slows down unpredictably (more than 1000 times, whereas python version is only about 2 times slower).
Python3 version:
import numpy as np
def ewma_f(y, tau):
a = 1/tau
avg = np.zeros_like(y)
for i in range(1, len(y)):
avg[i] = a*y[i-1]+(1-a)*avg[i-1]
return avg
Haskell with lists:
ewmaL :: [Double] -> Double -> [Double]
ewmaL ys tau = reverse $ e (reverse ys) (1.0/tau)
where e [x] a = [a*x]
e (x:xs) a = (a*x + (1-a)*(head $ e xs a) : e xs a)
Haskell with arrays:
import qualified Data.Vector as V
ewmaV :: V.Vector Double -> Double -> V.Vector Double
ewmaV x tau = V.map f $ V.enumFromN 0 (V.length x)
where
f (-1) = 0
f n = (x V.! n)*a + (1-a)*(f (n-1))
a = 1/tau
In all cases, computation takes about the same time (tested for an array with 10000 elements). Haskell code was compiled without any flags, though "ghc -O2" didn't make any difference.
I used computed ewma to compute absolute deviation from this ewma; I then applied ewma function to this deviation.
Python3:
def ewmd_f(y, tau):
ewma = ewma_f(y, tau)
return ewma_f(np.abs(y-ewma), tau)
It runs twice longer compared to ewma.
Haskell with lists:
ewmdL :: [Double] -> Double -> [Double]
ewmdL xs tau = ewmaL devs tau
where devs = zipWith (\ x y -> abs $ x-y) xs avg
avg = (ewmaL xs tau)
Haskell with vectors:
ewmdV :: V.Vector Double -> Double -> V.Vector Double
ewmdV xs tau = ewmaV devs tau
where devs = V.zipWith (\ x y -> abs $ x-y) xs avg
avg = ewmaV xs tau
Both ewmd run > 1000 slower than their ewma counterparts.
I evaluated python3 code with:
from time import time
x = np.sin(np.arange(10000))
tau = 100.0
t1 = time()
ewma = ewma_f(x, tau)
t2 = time()
ewmd = ewmd_f(x, tau)
t3 = time()
print("EWMA took {} s".format(t2-t1))
print("EWMD took {} s".format(t3-t2))
I evaluated Haskell code with:
import System.CPUTime
timeIt f = do
start <- getCPUTime
end <- seq f getCPUTime
let d = (fromIntegral (end - start)) / (10^12) in
return (show d)
main = do
let n = 10000 :: Int
let tau = 100.0
let l = map sin [0.0..(fromIntegral $ n-1)]
let x = V.map sin $ V.enumFromN 0 n
putStrLn "Vectors"
aV <- timeIt $ V.last $ ewmaV x tau
putStrLn $ "EWMA (vector) took "++aV
dV <- timeIt $ V.last $ ewmdV x tau
putStrLn $ "EWMD (vector) took "++dV
putStrLn ""
putStrLn "Lists"
lV <- timeIt $ last $ ewmaL l tau
putStrLn $ "EWMA (list) took "++lV
lD <- timeIt $ last $ ewmdL l tau
putStrLn $ "EWMD (list) took "++lD