Difference equation

The difference equation of the Simple Moving Average filter is derived from the mathematical definition of the average of N values: the sum of the values divided by the number of values. y[n]=1Ni=0N1x[ni] In this equation, y[n] is the current output, x[n] is the current input, x[n1] is the previous input, etc.
N is the length of the average.

Impulse and step response

From the previous equation, we can now easily calculate the impulse and step response.

The impulse response is the output of the filter when a Kronecker delta function is applied to the input.
Recall the definition of the Kronecker delta: δ[n]={1n=00n0. The impulse response of the SMA is yimpulse[n]=h[n]=1Ni=0N1δ[ni]={1/N0n<N0otherwise. For example, if N=15, the impulse response is as follows:

The step response is the output of the filter when a Heaviside step function is applied to the input.
The Heaviside step function is defined as H[n]={0n<01n0. The step response of the SMA is ystep[n]={0n<0(n+1)/N0n<N1nN. For example, if N=15, the step response is as follows:

Transfer function

The output of discrete-time linear time-invariant (DTLTI) systems, of which the SMA is an example, can be expressed as the convolution of the input with the impulse response. In other words, the impulse response describes the behavior of the system, for all possible inputs.

To prove the previous statement, we'll start with the following trivial property: any signal x[n] can be expressed as a convolution of the Kronecker delta function with itself, that is x[n]=x[n]δ[n]=k=0+x[k] δ[nk]. You can easily see that all terms where kn are zero, because the Kronecker delta is zero in that case. Only the term for k=n is non-zero, in which case the Kronecker delta is one, so the result is just x[n].
You can also interpret this as the signal being made up of a sum of infinitely many scaled and shifted Kronecker delta functions.

Let T be the transformation performed by the DTLTI system, then y[n] is the output after applying T to the input signal x[n]. The following derivation makes use of the fact that T is a linear transformation and that it is time-invariant: y[n]=T(x[n])=T(k=0x[k] δ[kn])=k=0T(x[k] δ[kn])=k=0x[k] T(δ[kn])=k=0x[k] h[kn]=x[n]h[n] Since the factor x[k] is independent of time n, it can be moved outside of the T operator. T applied to the Kronecker delta is (by definition) the impulse response of T, h[n], but shifted by k time steps.

Analysis of such systems is usually easier in the Z-domain, in which the convolution is reduced to a simple product.
The (unilateral) Z-transform is defined as: Z{x[n]}=n=0x[n]zn If X(z)=Z{x[n]}, Y(z)=Z{y[n]} and H(z)=Z{h[n]} are the Z-transforms of the input, output and impulse response respectively, then: Z{y[n]}=Z{x[n]h[n]}Y(z)=n=0x[n]h[n]zn=n=0k=0x[k]h[nk]zn=k=0n=0x[k]h[nk]zn+kk=k=0x[k]zkn=0h[nk]z(nk)Y(z)=X(z)H(z)H(z)=Y(z)X(z) H(z) is called the transfer function of the system.

Let's calculate the transfer function of the SMA.
We can use one of two approaches: use the difference equation and use some of the properties of the Z-transform to calculate Y(z)X(z), or apply the definition of the Z-transform to the impulse response h[n] directly.

Using the difference equation

All you have to do is apply the time shifting property of the Z transform: n0N:Z{y[nn0]}=zn0Z{y[n]} Then just use the fact that the Z-transform is linear, and the derivation is trivial: y[n]=1Ni=0N1x[ni]Z{y[n]}=Z{1Ni=0N1x[ni]}=1Ni=0N1Z{x[ni]}=1Ni=0N1ziZ{x[n]}Y(z)=1Ni=0N1ziX(z)H(z)Y(z)X(z)=1Ni=0N1zi=1Ni=0N1zi+(N1)(N1)=1Nz(N1)i=0N1z(N1)ijN1i=1Nj=0N1zjzN1=1NzN1+zN2++z2+z+1zN1 In the last steps, we just rearrange some terms and keep only positive powers of z in both the numerator and the denominator.

Using the impulse response

For this derivation, we can use the fact that the impulse response h[n] is only non-zero for the first N terms. H(z)=Z{h[n]}=n=0h[n]zn=n=0N11Nzn It is clear that this expression is identical to the previous one.

We can solve the summation in a different way using the formula for the sum of a geometric series: n=0N1rn=1rN1r The transfer function then becomes: H(z)=1Nn=0N1zn=1Nn=0N1(1z)n=1N1zN1z1=1NzN1zN1(z1)

In these expressions, z is a complex variable, and H(z) is a complex function.
There are a couple of interesting values for z: values that result in the transfer function becoming zero, called zeros, and values that result in the transfer function becoming undefined, called poles.
They can be found as the roots of the numerator and the denominator respectively, after eliminating any common factors.

The roots of the numerator are the solutions of zN1=0 or zN=1. Note that this equation has N solutions, namely all N-th roots of unity. One of the solutions is z=1, but there are N1 complex solutions as well, evenly spaced along the unit circle: e2πjn/N | n[0,N1].

The roots of the denominator are the solutions of zN1(z1)=0. This has just two distinct solutions: z=0 with a multiplicity of N1 and z=1 with a multiplicity of one.
Since both the numerator and the denominator have the root z=1 in common, we have to factor out the common factor (z1).
This leaves us with the following N1 poles and zeros: zeros={e2πjn/N | n[1,N1]}poles={0}.

Frequency response

An important property of discrete-time linear time-invariant systems is that it preserves the pulsatance (angular frequency) of sinusoidal signals, only the phase shift and the amplitude are altered. In other words, sines and cosines are eigenfunctions of DTLTI systems.
This makes it relatively easy to express the frequency response (sometimes called magnitude response) of a filter.

We're interested in the spectrum of frequencies that a signal contains, so it makes sense to decompose it as a sum of sines and cosines. That's exactly what the discrete-time Fourier transform does: X2π(ω)=n=x[n]eiωn Note that this is just a special case of the Z-transform, where z=eiω.

The frequency response of the filter describes how the spectrum of a signal is altered when it passes through the filter. It relates the spectrum of the output signal Y(ω) to the spectrum of the input signal X(ω). We already had an expression for the spectrum of the output divided by the spectrum of the input, we called it the transfer function H(z)=Y(z)/X(z). To get the frequency response of the filter, we can just evaluate the transfer function for z=eiω. Also note that this is the DTFT of the impulse response h[k].
We use the formula ejθejθ=2jsin(θ). FDTFT{h[k]}=n=h[n]eiωn=H(eiω)=1N1ejωN1ejω=1NejωN/2ejω/2ejωN/2ejωN/2ejω/2ejω/2=1NejωN/2ejω/22jsin(ωN2)2jsin(ω2)=ejω2(N1)Nsin(ωN2)sin(ω2) We can now calculate the amplitude of each frequency component in the output by taking the modulus of H(eiω). For reasons that will become apparent in a minute, we'll calculate the modulus squared. |H(eiω)|2=|ejω2(N1)Nsin(ωN2)sin(ω2)|2=1N2sin2(ωN2)sin2(ω2) ω is the normalized pulsatance (angular frequency) in radians per sample. You can substitute it with ω=2πffs where f is the frequency in Hertz, and fs is the sample frequency of the system in Hertz.

We can now plot the filter's gain in function of the frequency. These plots often use a logarithmic scale, to show the gain in decibels. In order to calculate the power gain, the amplitude is squared. AdB(ω)=10 log10|H(eiω)|2 Note that when a frequency is not present in the output signal, the gain will be  dB. If a frequency has an amplitude of one in the output signal, the gain will be 0 dB.

We can also plot the phase shift introduced by the SMA. This is just the complex argument of the transfer function. ϕ(ω)=H(ejω)

The image below shows the bode plot of an SMA with a length of N=9, and a sample frequency of fs=360Hz. The frequency axis is limited between 0 Hz and the Nyquist frequency fs/2. This corresponds to a normalized pulsatance of ω[0,π). You can clearly see the low-pass behavior of the SMA: low frequencies have a near-unit gain, and high frequencies are attenuated.
Also note the zeros, where the magnitude plot goes to  dB. In a zero, the transfer function changes sign, so the phase jumps 180° as well.

To get a better understanding of where this curve comes from, we can plot the entire |H(z)| surface in the Z-domain. As mentioned above, the DTFT is just a special case of the Z-transform, where z=eiω, i.e. the unit circle in the complex plane. Remember that the point z=eiω is a point with a distance of 1 to the origin, and with an angle of ω rad between its position vector and the positive x axis.

images/SMA-H-surf-N9.png
Image source code

The image of the unit circle is shown in red. Notice that this is the same curve as the blue curve in the magnitude response graph above: close to 0 when ω0 (the right half of the circle) and negative when ωπ (the left half of the circle). The positive x axis points to the right-hand side, the y axis points away from you, and the z axis points up).

Cutoff frequency

The cutoff frequency is defined as the frequency of the half-power point, where the power gain is a half. It's often called the 3 dB-point, because 10log10(12)3.01 dB.
To find it, just solve the following equation: |H(eiωc)|2=12 1N2sin2(ωcN2)sin2(ωc2)=12sin2(ωcN2)=N22sin2(ωc2)sin2(ωcN2)N22sin2(ωc2)=0 This equation doesn't have a general analytical solution, but we can use numerical methods to determine the cut-off frequency. Note that the intersections at zero and 2π are not valid solutions. In these points the transfer function is undefined, because it evaluates to 0/0. We only care about the first solution for ωc(0,π).
A possible approach to find the intersection is to use the Newton-Raphson method.
A good starting point seems to be at half the period of the high-frequency sine wave.
We don't have to keep the squares, because both sines will be positive in the region of the intersection. f(ωc)=sin(ωcN2)N2sin(ωc2)f(ωc)=N2cos(ωcN2)N22cos(ωc2)ωc(0)=π/N A Python implementation can be found in the get_sma_cutoff function in the snippet below.

Plotting the frequency response, impulse response and step response in Python

We can use the SciPy and Matplotlib modules to plot the frequency response in Python.
This is the script that was used to generate the plots in the previous paragraphs.

from scipy.optimize import newton
from scipy.signal import freqz, dimpulse, dstep
from math import sin, cos, sqrt, pi
import numpy as np
import matplotlib.pyplot as plt

# Function for calculating the cut-off frequency of a moving average filter
def get_sma_cutoff(N, **kwargs):
    func = lambda w: sin(N*w/2) - N/sqrt(2) * sin(w/2)  # |H(e^jω)| = √2/2
    deriv = lambda w: cos(N*w/2) * N/2 - N/sqrt(2) * cos(w/2) / 2  # dfunc/dx
    omega_0 = pi/N  # Starting condition: halfway the first period of sin(Nω/2)
    return newton(func, omega_0, deriv, **kwargs)

# Simple moving average design parameters
f_s = 100
N = 5

# Find the cut-off frequency of the SMA
w_c = get_sma_cutoff(N)
f_c = w_c * f_s / (2 * pi)

# SMA coefficients
b = np.ones(N)
a = np.array([N] + [0]*(N-1))

# Calculate the frequency response
w, h = freqz(b, a, worN=4096)
w *= f_s / (2 * pi)                      # Convert from rad/sample to Hz

# Plot the amplitude response
plt.subplot(2, 1, 1)
plt.suptitle('Bode Plot')
plt.plot(w, 20 * np.log10(abs(h)))       # Convert modulus to dB
plt.ylabel('Magnitude [dB]')
plt.xlim(0, f_s / 2)
plt.ylim(-60, 10)
plt.axvline(f_c, color='red')
plt.axhline(-3.01, linewidth=0.8, color='black', linestyle=':')

# Plot the phase response
plt.subplot(2, 1, 2)
plt.plot(w, 180 * np.angle(h) / pi)      # Convert argument to degrees
plt.xlabel('Frequency [Hz]')
plt.ylabel('Phase [°]')
plt.xlim(0, f_s / 2)
plt.ylim(-180, 90)
plt.yticks([-180, -135, -90, -45, 0, 45, 90])
plt.axvline(f_c, color='red')
plt.show()

# Plot the impulse response
t, y = dimpulse((b, a, 1/f_s), n=2*N)
plt.suptitle('Impulse Response')
_, _, baseline = plt.stem(t, y[0], basefmt='k:')
plt.setp(baseline, 'linewidth', 1)
baseline.set_xdata([0,1])
baseline.set_transform(plt.gca().get_yaxis_transform())
plt.xlabel('Time [seconds]')
plt.ylabel('Output')
plt.xlim(-1/f_s, 2*N/f_s)
plt.yticks([0, 0.5/N, 1.0/N])
plt.show()

# Plot the step response
t, y = dstep((b, a, 1/f_s), n=2*N)
plt.suptitle('Step Response')
_, _, baseline = plt.stem(t, y[0], basefmt='k:')
plt.setp(baseline, 'linewidth', 1)
baseline.set_xdata([0,1])
baseline.set_transform(plt.gca().get_yaxis_transform())
plt.xlabel('Time [seconds]')
plt.ylabel('Output')
plt.xlim(-1/f_s, 2*N/f_s)
plt.yticks([0, 0.2, 0.4, 0.6, 0.8, 1])
plt.show()

Comparing the Simple Moving Average filter to the Exponential Moving Average filter

Using the same Python functions as before, we can plot the responses of the EMA and the SMA on top of each other.
First, the length N of the SMA is chosen, then its 3 dB cut-off frequency is calculated, and this frequency is then used to design the EMA. Do note that this is a fairly arbitrary decision.
from scipy.optimize import newton
from scipy.signal import freqz, dimpulse, dstep
from math import sin, cos, sqrt, pi
import numpy as np
import matplotlib.pyplot as plt

# Function for calculating the cut-off frequency of a moving average filter
def get_sma_cutoff(N, **kwargs):
    func = lambda w: sin(N*w/2) - N/sqrt(2) * sin(w/2)  # |H(e^jω)| = √2/2
    deriv = lambda w: cos(N*w/2) * N/2 - N/sqrt(2) * cos(w/2) / 2  # dfunc/dx
    omega_0 = pi/N  # Starting condition: halfway the first period of sin(Nω/2)
    return newton(func, omega_0, deriv, **kwargs)

# Simple moving average design parameters
f_s = 360
N = 9

# Find the cut-off frequency of the SMA
w_c = get_sma_cutoff(N)
f_c = w_c * f_s / (2 * pi)

# Calculate the pole location of the EMA with the same cut-off frequency
b = 2 - 2*cos(w_c)
alpha = (-b + sqrt(b**2 + 4*b)) / 2

# SMA & EMA coefficients
b_sma = np.ones(N)
a_sma = np.array([N] + [0]*(N-1))

b_ema = np.array((alpha, 0))
a_ema = np.array((1, alpha - 1))

# Calculate the frequency response
w, h_sma = freqz(b_sma, a_sma, worN=4096)
w, h_ema = freqz(b_ema, a_ema, w)
w *= f_s / (2 * pi)                      # Convert from rad/sample to Hz

# Plot the amplitude response
plt.subplot(2, 1, 1)
plt.suptitle('Bode Plot')
plt.plot(w, 20 * np.log10(abs(h_sma)),   # Convert modulus to dB
         color='blue', label='SMA')
plt.plot(w, 20 * np.log10(abs(h_ema)),
         color='green', label='EMA')
plt.ylabel('Magnitude [dB]')
plt.xlim(0, f_s / 2)
plt.ylim(-60, 10)
plt.axvline(f_c, color='red')
plt.axhline(-3.01, linewidth=0.8, color='black', linestyle=':')
plt.legend()

# Plot the phase response
plt.subplot(2, 1, 2)
plt.plot(w, 180 * np.angle(h_sma) / pi,  # Convert argument to degrees
         color='blue')
plt.plot(w, 180 * np.angle(h_ema) / pi,
         color='green')
plt.xlabel('Frequency [Hz]')
plt.ylabel('Phase [°]')
plt.xlim(0, f_s / 2)
plt.ylim(-180, 90)
plt.yticks([-180, -135, -90, -45, 0, 45, 90])
plt.axvline(f_c, color='red')
plt.show()

# Plot the impulse response
t, y_sma = dimpulse((b_sma, a_sma, 1/f_s), n=2*N)
t, y_ema = dimpulse((b_ema, a_ema, 1/f_s), n=2*N)
plt.suptitle('Impulse Response')
plt.plot(t, y_sma[0], 'o-',
         color='blue', label='SMA')
plt.plot(t, y_ema[0], 'o-',
         color='green', label='EMA')
plt.xlabel('Time [seconds]')
plt.ylabel('Output')
plt.xlim(-1/f_s, 2*N/f_s)
plt.legend()
plt.show()

# Plot the step response
t, y_sma = dstep((b_sma, a_sma, 1/f_s), n=2*N)
t, y_ema = dstep((b_ema, a_ema, 1/f_s), n=2*N)
plt.suptitle('Step Response')
plt.plot(t, y_sma[0], 'o-',
         color='blue', label='SMA')
plt.plot(t, y_ema[0], 'o-',
         color='green', label='EMA')
plt.xlabel('Time [seconds]')
plt.ylabel('Output')
plt.xlim(-1/f_s, 2*N/f_s)
plt.legend()
plt.show()