Exponential Moving Average (EMA) Filters

Article by:
Date Published:
Last Modified:

Overview

The exponential moving average (EMA) filter is a discrete, low-pass, infinite-impulse response (IIR) filter. It places more weight on recent data by discounting old data in an exponential fashion, and behaves similarly to the discrete first-order low-pass RC filter.

Unlike a simple moving average (SMA) filter, most EMA filters is not windowed, and the next value depends on all previous inputs. Thus most EMA filters are a form of infinite impulse response (IIR) filter, whilst a SMA is a finite impulse response (FIR) filter. There are exceptions, and you can indeed build a windowed exponential moving average filter in where the coefficients are weighted exponentially.

TIP

The EMA filter is my personal go-to filter if I ever want to do some basic filtering on a microcontroller. It’s fast, memory efficient, and easy to implement.

EMA Equation

The difference equation for an exponential moving average filter is:

$$\begin{align} y[i] = \alpha \cdot x[i] + (1 - \alpha) \cdot y[i-1] \end{align}$$

where:
\( y \) is the output (\([i]\) denotes the sample number)
\( x \) is the input
\( \alpha \) is a constant which sets the cutoff frequency (a value between \(0\) and \(1\))

Notice that the calculation does not require the storage of past values of \(x\) and only the previous value of \(y\), which makes this filter memory and computation friendly (especially relevant for microcontrollers). Only one addition, one subtraction, and two multiplication operations are needed.

The constant \( \alpha \) determines how aggressive the filter is. It can vary between \(0\) and \(1\) (inclusive). As \( \alpha \to 0 \), the filter gets more and more aggressive, until at \( \alpha = 0 \), where the input has no effect on the output (if the filter started like this, then the output would stay at \(0\)). As \( \alpha \to 1 \), the filter lets more of the raw input through at less filtered data, until at \( \alpha = 1 \), where the filter is not “filtering” at all (pass-through from input to output).

The filter is called exponential because the weighted contribution of previous inputs decreases exponentially the further the input is away in time. This can be seen in the difference equation if we substitute in previous inputs:

$$\begin{align} y[i] &= \alpha \cdot x[i] + (1 - \alpha) \cdot y[i-1] \nonumber \\ y[i] &= \alpha \cdot x[i] + (1 - \alpha) \cdot (\alpha \cdot x[i-1] + (1 - \alpha) \cdot y[i-2]) \nonumber \\ y[i] &= \alpha \cdot x[i] + (1 - \alpha) \cdot (\alpha \cdot x[i-1] + (1 - \alpha) \cdot \nonumber \\ &(\alpha \cdot x[i-2] + (1 - \alpha) \cdot y[i-3])) \nonumber \\ ... \nonumber \\ \label{eq:ema-sum} y[i] &= \alpha \sum_{k=0}^n (1-\alpha)^k x[n-k] \\ \end{align}$$

The following code implements a basic EMA filter in C++, suitable for microcontrollers and other embedded devices:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <cstdint>

class EmaFilter
{
public:
    EmaFilter(double alpha) : 
        m_alpha(alpha), m_lastOutput(0.0) {}

    double Run(double input)
    {
        m_lastOutput = m_alpha * input + (1 - m_alpha) * m_lastOutput;
        return m_lastOutput;
    }
private:
    double m_alpha;
    double m_lastOutput;
};

int main() {
    EmaFilter emaFilter(0.5);

    for(uint32_t i = 0; i < 20; i++)
    {
        double input = 1.0;
        double output = emaFilter.Run(input);
        printf("%f\n", output);
    }
}

When setting alpha=0.5 and hardcoding all input to 1.0 (which means a step change from 0.0 to 1.0 at t=0), it prints the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
0.500000
0.750000
0.875000
0.937500
0.968750
0.984375
0.992188
0.996094
0.998047
0.999023
0.999512
0.999756
0.999878
0.999939
0.999969
0.999985
0.999992
0.999996
0.999998
0.999999

This example can be modified and run at https://replit.com/@gbmhunter/cpp-ema-filter.

WARNING

In the code example above, you could replace the double types with integer types if you wanted fast operation and didn’t have an FPU. However, be mindful of the loss of precision that occurs, especially if you are using a small \(\alpha\) value. A small \(alpha\) results in a very small contribution from the input. This contribution (\(\alpha \cdot x_i\)) could end up rounding to zero if using an integer, resulting in the output being stuck at zero.

Frequency Response

The frequency response of the EMA filter can be found by using the Z transform. If we start with the time-domain equation for an EMA filter:

$$\begin{align} y[i] = \alpha \cdot x[i] + (1 - \alpha) \cdot y[i-1] \end{align}$$

And then take the Z transform of it:

$$\begin{align} Y(z) = aX(z) + (1 - \alpha) z^{-1} Y(z) \end{align}$$

Then re-arrange to find the transfer function \(H(z)\):

$$\begin{align} H(z) &= \frac{Y(z)}{X(z)} \nonumber \\ &= \frac{\alpha}{1 - (1-\alpha)z^{-1}} \\ \end{align}$$

This transfer function can be used to create bode plots of the magnitude and phase response of the EMA filter. The below bode plot shows the response of an EMA filter with \(\alpha=0.25\). The x-axis frequency is the normalized frequency, in units \(Hz/sample\), which makes the plot applicable for any sampling frequency.

Bode plot showing the magnitude and phase of an EMA filter with \( \alpha=0.25 \).

Bode plot showing the magnitude and phase of an EMA filter with \( \alpha=0.25 \).

The cut-off frequency (-3dB point) of an EMA filter is given by1:

$$\begin{align} f_c = \frac{f_s}{2\pi} \cos^{-1}{\left[1 - \frac{\alpha^2}{2(1-\alpha)}\right]} \end{align}$$

where:
\(f_s\) is the sampling frequency in \(Hz\)

An important consequence of this is that the filter’s response is dependent both on \(\alpha\) and the sampling frequency. Remember that if you adjust the sampling frequency, you also have to adjust \(\alpha\) to keep the same response.

Note that not all valid values of \(\alpha\) will result in a valid cut-off frequency. This is when the filter response is so gradual that it does not drop to -3dB or below before the Nyquist frequency. Mathematically, this is when:

$$\begin{align} \left|1 - \frac{\alpha^2}{2(1 - \alpha)}\right| > 1 \end{align}$$

and thus the inverse cosine function is undefined. The expression above becomes greater than 1 when \(\alpha\) is at or above1:

$$\begin{align} \alpha = 0.82843 \end{align}$$

Impulse Response

The discrete unit sample function is defined as:

$$ \begin{align} \delta[n] = \begin{cases} 1 & n = 0 \\ 0 & n \neq 0 \\ \end{cases} \end{align} $$

If we use this as our input into \(Eq.\ \ref{eq:ema-sum}\):

$$\begin{align} y[i] &= \alpha \sum_{k=0}^n (1-\alpha)^k \delta[n-k] \\ \end{align}$$

Given the unit sample function is 0 at most points, the only sum term that matters is when \(k = n\), so we can simplify this to:

$$\begin{align} y[i] &= \alpha (1-\alpha)^n \\ \end{align}$$

From this, we can plot what the response will look like for impulse as the input. As you can see in the following graph, the output starts off at \(y[0] = \alpha\) and then decays towards 0. A larger alpha makes the initial response larger but also the decay faster.

Impulse response for an EMA filter with different \(\alpha\) values.

Impulse response for an EMA filter with different \(\alpha\) values.

Initializing (Priming) The Filter

When initializing the filter, the initial value of \(y\) is important. If you set it to 0, then the filter will take a while to “warm up” and reach the correct output. If you set it to the first input value, then the filter will get a “head start”. This may or may not be important for your application. I typically have a boolean flag called firstTime which is set to true when the filter is first created, and then set to false after the first input is processed. On the first pass, the filter just sets the output \(y\) to the input \(x\). I call this priming the filter, to avoid confusion with “initialisation” of an object (i.e. constructor).

Here is an example of the C++ class from above but reworked to include priming:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class EmaFilterWithPriming
{
public:
    EmaFilterWithPriming(double alpha) : 
        m_alpha(alpha), m_lastOutput(0.0), m_firstRun(true) {}

    double Run(double input)
    {
        if (m_firstRun)
        {
            m_firstRun = false;
            m_lastOutput = input; // Bypass filter and prime with input
        }
        else
        {
            m_lastOutput = m_alpha * input + (1 - m_alpha) * m_lastOutput;
        }
        return m_lastOutput;
    }
private:
    double m_alpha;
    double m_lastOutput;
    bool m_firstRun;
};

This example can be modified and run at https://replit.com/@gbmhunter/cpp-ema-filter.

Further Reading

https://stratifylabs.co/embedded%20design%20tips/2013/10/04/Tips-An-Easy-to-Use-Digital-Filter/ is a great page explaining the exponential moving average filter.

References


  1. Andy Walls (2017, Apr 22). Exponential moving average cut-off frequency (Q/A). StackExchange: Signal Processing. Retrieved 2022-05-25, from https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency↩︎ ↩︎


Authors

Geoffrey Hunter

Dude making stuff.

Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License .

Related Content:

Tags

comments powered by Disqus