# Make EWMA as fast as pandas

**URL:** https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416
**Category:** General Usage
**Tags:** performance, dataframes
**Created:** [November 18, 2023, 7:06pm UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416 "2023-11-18T19:06:20Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![AUK1939](https://avatars.discourse-cdn.com/v4/letter/a/c57346/32.png) [@AUK1939](https://discourse.julialang.org/u/AUK1939)
#### Post date: [November 18, 2023, 7:06pm UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/1 "2023-11-18T19:06:20Z")

</div>

Hi

I don’t think Julia has a EWMA implementation I could easily use (there are some old packages but they’re not maintained), so I tried to build my own based on the pandas implementation. This is what I’ve come up with

```
function ewma(x::AbstractArray{T}; n::Int64=10, alpha::T=2.0/(n+1)) where {T<:Real}
  N = length(x)
  @assert n<N && n>0 "Argument n out of bounds."
  out = Vector{T}(undef, N) 
  w = Vector{T}(undef, N) 

  w = [(1-alpha)^i for i in N-1:-1:0]
  w_sum = sum(w[end-n+2:end])
  @inbounds for i in Iterators.drop(eachindex(x), n-1)
     w_sum += w[end-i+1]
     out[i] = dot(@view(w[end-i+1:end]), @view(x[1:i])) / w_sum  
  end   
    out[1:(n-1)] .= NaN
    return out
  end

```

which is equivalent to the pandas call

> df[“col”].ewm(span=14, min\_periods=14, adjust=True).mean()

My julia code produces the same output but is over 5 times slower for an input vector of size 6000ish (on my machine).

**I was wondering can we make my julia code as fast as the pandas ewma implementation?** The formula for how pandas implements ewma is given [here](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html) (see the `adjust` parameter). Advice appreciated

I did try the following optimization but that turned out to be twice as slow as the original, not sure why. Why does the line `w[end - i] = w[end - i + 1] * c` cause such a performance hit?

```
function ewma(x::AbstractArray{T}; n::Int64=10, alpha::T=2.0/(n+1)) where {T<:Real}
  N = length(x)
  @assert n<N && n>0 "Argument n out of bounds."
  out = Vector{T}(undef, N) 
  w = Vector{T}(undef, N) 

  c = 1-alpha
  w_sum = 1.0
  w[end] = 1.0
  @inbounds for i in Iterators.take(eachindex(x), N-1)
      w[end - i] = w[end - i + 1] * c # c^i - why is multiplication slower than exponentiation?
      out[i] = dot(@view(w[end-i+1:end]), @view(x[1:i])) / w_sum  
      w_sum += w[end-i]
   end
   out[end] = dot(w, x) / w_sum    

   out[1:(n-1)] .= NaN
   return out
end

```

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 1:26am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/2 "2023-11-19T01:26:15Z")

</div>

> [@AUK1939](#):
>
> ```julia
> w = Vector{T}(undef, N) 
> 
> w = [(1-alpha)^i for i in N-1:-1:0]
> 
> ```

This might just be a typo, but note that the first line has no effect, except possibly hurting performance, as the following line overwrites `w` anyway.

EDIT: deleted lots of irrelevant content.

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [November 19, 2023, 2:07am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/3 "2023-11-19T02:07:12Z")

</div>

Not sure I have the right formula, but it checked out in one test-case 😛 :

```julia
function ewma3(x::Vector{Float64}; n=10, alpha=2.0/(n+1))
    N = length(x)
    c = 1-alpha
    ci = 1.0
    r = 0.0
    res = similar(x)
    @inbounds for i in eachindex(res)
        r = c*r + alpha*x[i]
        ci *= c
        res[i] = i < n ? NaN : r/(1-ci)
    end
    return res
end

```

It is also a few times faster.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 2:17am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/4 "2023-11-19T02:17:56Z")

</div>

> [@Dan](#):
>
> `1-c`

Presumably this should just be `alpha`?

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [November 19, 2023, 2:18am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/5 "2023-11-19T02:18:32Z")

</div>

`1-c` looks better (doesn’t change benchmark)

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 2:20am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/6 "2023-11-19T02:20:20Z")

</div>

Not sure, but an extra subtraction seems like it could decrease the accuracy a tiny bit?

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [November 19, 2023, 2:21am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/7 "2023-11-19T02:21:26Z")

</div>

Maybe… I can change it. But what would really speed things up is somehow avoiding a division each iteration

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 2:30am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/8 "2023-11-19T02:30:50Z")

</div>

> [@Dan](#):
>
> `r = c*r + alpha*x[i]`

Maybe slightly faster: `r += alpha*(x[i] - r)`?

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [November 19, 2023, 2:36am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/9 "2023-11-19T02:36:03Z")

</div>

> [@nsajko](#):
>
> r += alpha\*(x[i] - r)

Not faster, but deffos educational: it shows `r` performing a ‘gradient step’ towards `x[i]` with learning rate `alpha`.

---

<div class="post-metadata">

### Author: ![AUK1939](https://avatars.discourse-cdn.com/v4/letter/a/c57346/32.png) [@AUK1939](https://discourse.julialang.org/u/AUK1939)
#### Post date: [November 19, 2023, 3:01am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/11 "2023-11-19T03:01:22Z")

</div>

@Dan This seems to check out for me and it’s way faster than pandas (by at least 20 times on my machine). I’m trying to work out the Maths in the general case. Did you rearrange the formula somehow? If I read the code correctly you’re computing the following

$$\frac{\alpha (1-\alpha )^2 x\_1+\alpha (1-\alpha ) x\_2+\alpha x\_3}{1-(1-\alpha )^3}$$

What’s the logic behind this improvement?

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [November 19, 2023, 3:08am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/12 "2023-11-19T03:08:37Z")

</div>

The logic goes back to the idea of Exponentially Weighted Moving Average. The benefit of exponential weights is that the sum of all previous weights in the current step, is the sum of weights in the previous step times a factor plus a correction. So by multiplying by this factor, both numerator and denominator of the moving average can be calculated simply. The denominator is a simple geometric series, for which there is a formula. The numerator correction depends on the new data element `x[i]`.  
So no trick beyond the geometric series formula was used.

---

<div class="post-metadata">

### Author: ![AUK1939](https://avatars.discourse-cdn.com/v4/letter/a/c57346/32.png) [@AUK1939](https://discourse.julialang.org/u/AUK1939)
#### Post date: [November 19, 2023, 3:12am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/13 "2023-11-19T03:12:16Z")

</div>

@Dan ah I see the truncation is equivalent to the formula on the pandas page. Thanks

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 3:42am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/14 "2023-11-19T03:42:40Z")

</div>

Here’s a version that may be slightly faster, and I think more accurate:

```julia
function ewma_4(
  x::AbstractArray{T,m},
  n::Int = 10,
  α::T = T(T(UInt8(2))/(n+1)),
  c::T = true - α,
) where {T, m}
  res = similar(x)
  num = zero(T)::T
  den = zero(T)::T
  for i ∈ Base.OneTo(length(x))
    j = i - 1
    num = fma(num, c, x[begin + j])
    den = fma(den, c, true)
    res[begin + j] = num/den
  end
  res::AbstractArray{T,m}
end

```

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 3:54am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/15 "2023-11-19T03:54:38Z")

</div>

A slight modification allows using Symbolics for verifying that the formula checks out:

```julia
function ewma_4_sym(x::AbstractArray{T,m}, c::T) where {T, m}
  res = zeros(T, size(x))
  num = zero(T)::T
  den = zero(T)::T
  for i ∈ Base.OneTo(length(x))
    j = i - 1
    num = muladd(num, c, x[begin + j])
    den = muladd(den, c, true)
    res[begin + j] = num/den
  end
  res::AbstractArray{T,m}
end

```

```julia-repl
julia> include("/home/nsajko/EWMA/EWMA.jl");

julia> using Symbolics

julia> @variables x[1:6]
1-element Vector{Symbolics.Arr{Num, 1}}:
 x[1:6]

julia> @variables c
1-element Vector{Num}:
 c

julia> EWMA.ewma_4_sym(x, c)
6-element Vector{Num}:
                                                               x[1]
                                             (x[2] + c*x[1]) / (true + c)
                                  (x[3] + c*(x[2] + c*x[1])) / (true + (true + c)*c)
                       (x[4] + c*(x[3] + c*(x[2] + c*x[1]))) / (true + c*(true + (true + c)*c))
            (x[5] + c*(x[4] + c*(x[3] + c*(x[2] + c*x[1])))) / (true + c*(true + c*(true + (true + c)*c)))
 (x[6] + c*(x[5] + c*(x[4] + c*(x[3] + c*(x[2] + c*x[1]))))) / (true + c*(true + c*(true + c*(true + (true + c)*c))))

```

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [November 19, 2023, 3:57am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/16 "2023-11-19T03:57:23Z")

</div>

Adding an `@inbounds` helps the benchmark. Also, limiting parameter to a Vector instead of `where {m}` seems acceptable in this case (unless a `dim` parameter is added).

---

<div class="post-metadata">

### Author: ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)
#### Post date: [November 19, 2023, 4:00am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/17 "2023-11-19T04:00:37Z")

</div>

on my machine gets about another 2x as fast if you use `fma(num, c, x[i])` instead of `num*c + x[i]` and same for `den`

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 19, 2023, 4:01am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/18 "2023-11-19T04:01:20Z")

</div>

> [@adienes](#):
>
> fma

Already made the edit a few minutes ago 😁

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [November 19, 2023, 11:26am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/19 "2023-11-19T11:26:12Z")

</div>

> [@nsajko](#):
>
> `res::AbstractArray{T,m}`

What’s the purpose of this type conversion? Particularly to an abstract type?

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [November 19, 2023, 11:37am UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/20 "2023-11-19T11:37:30Z")

</div>

Unfortunately I can’t test now. But how much faster is the fastest alternative relative to the simpler:

```julia
function ewma5(x::AbstractArray{T}, c::T) where {T}
  res = zeros(T, size(x))
  num = zero(T)
  den = zero(T)
  for i ∈ eachindex(x)
    j = i - 1
    num += c * x[begin + j]
    den += c
    res[begin + j] = num / den
  end
  return res
end

```

And to this one with `@turbo` (or `@inbounds @simd`) in the loop?

I’m asking because I feel that these excessive optimizations (although cool) scary new users and cause an impression that proper performance in Julia is only achieved with these tricks.

---

<div class="post-metadata">

### Author: ![AUK1939](https://avatars.discourse-cdn.com/v4/letter/a/c57346/32.png) [@AUK1939](https://discourse.julialang.org/u/AUK1939)
#### Post date: [November 19, 2023, 2:05pm UTC](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416/21 "2023-11-19T14:05:52Z")

</div>

amazing! started off at ~5ms with my crappy algo, and this is now runs under 5µs.

[Next page](https://discourse.julialang.org/t/make-ewma-as-fast-as-pandas/106416.md?page=2)
