
Moving averages are used to smooth time series data and observe underlying trends by averaging subsets of data points over a specific window. In this article, we’ll learn how to implement moving averages in Python using NumPy. We will explore a range of methods from simple moving averages to cumulative, weighted, and exponential moving averages.
What is a Moving Average?
A moving average is a calculation that helps to smooth out short-term fluctuations and highlight longer-term trends or cycles. Think of it as looking at your data through a lens that brings the bigger picture into focus – the noise and daily fluctuations blur away, leaving the underlying patterns clear. This technique is widely used in financial analysis, weather forecasting, and other data analysis applications where you need to see through the noise to identify meaningful trends.
Throughout this tutorial, we’ll use this sample dataset to demonstrate different types of moving averages:
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1. Simple Moving Average (SMA)
A Simple Moving Average (SMA) is the unweighted mean of a sliding window of values. Here’s how to implement it using NumPy:
import numpy as np
def simple_moving_average(data, window_size):
weights = np.ones(window_size) / window_size
sma = np.convolve(data, weights, mode='valid')
return sma
# Calculate SMA with window size of 3
window_size = 3
sma = simple_moving_average(data, window_size)
print("Simple Moving Average:", sma)
Output:
Simple Moving Average: [2. 3. 4. 5. 6. 7. 8. 9.]
The SMA with a window size of 3 produces 8 values (original length – window size + 1). Each value is the average of three consecutive numbers. For example:
- First value (2.0) = (1 + 2 + 3) / 3
- Second value (3.0) = (2 + 3 + 4) / 3
And so on…
2. Cumulative Moving Average (CMA)
A Cumulative Moving Average considers all previous data points up to the current position:
def cumulative_moving_average(data):
cma = np.cumsum(data) / np.arange(1, len(data) + 1)
return cma
cma = cumulative_moving_average(data)
print("Cumulative Moving Average:", cma)
Output:
Cumulative Moving Average: [1. 1.5 2. 2.5 3. 3.5 4. 4.5 5. 5.5]
The CMA shows how the average changes as we include more data points:
- First value (1.0) = 1
- Second value (1.5) = (1 + 2) / 2
- Third value (2.0) = (1 + 2 + 3) / 3
And so on…
3. Weighted Moving Average (WMA)
A Weighted Moving Average assigns different importance to data points within the window. We’ll create a version where more recent values have higher weights:
def weighted_moving_average(data, weights):
# Ensure weights sum to 1
weights = np.array(weights) / np.sum(weights)
wma = np.convolve(data, weights, mode='valid')
return wma
# Define weights in reverse order due to how convolution works
weights = [0.6, 0.3, 0.1] # Will be flipped during convolution
wma = weighted_moving_average(data, weights)
print("Weighted Moving Average:", wma)
Output:
Weighted Moving Average: [2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5]
When using np.convolve(), it’s important to understand that the function automatically flips the weights array before sliding it over the input data. This is a fundamental property of convolution. So even though we define our weights as [0.6, 0.3, 0.1], they get applied in reverse order. Let’s look at the first three calculations in detail:
Window 1: [1, 2, 3] × flipped weights [0.1, 0.3, 0.6] = 2.5 Calculation: (1×0.1 + 2×0.3 + 3×0.6) = 0.1 + 0.6 + 1.8 = 2.5 Window 2: [2, 3, 4] × flipped weights [0.1, 0.3, 0.6] = 3.5 Calculation: (2×0.1 + 3×0.3 + 4×0.6) = 0.2 + 0.9 + 2.4 = 3.5 Window 3: [3, 4, 5] × flipped weights [0.1, 0.3, 0.6] = 4.5 Calculation: (3×0.1 + 4×0.3 + 5×0.6) = 0.3 + 1.2 + 3.0 = 4.5
There are two important considerations when using weights:
1. The weights should sum to 1 to maintain the scale of your data (our function handles this normalization automatically).
2. Due to how convolution works, we define our weights in reverse order ([0.6, 0.3, 0.1]) so that after the convolution flip, recent values get higher weights. As shown in the calculations above, the last value in each window gets multiplied by 0.6, the middle by 0.3, and the first by 0.1.
This implementation allows us to give different importance to different points in our moving window, making it more flexible than the simple moving average when we want to emphasize more recent data points over older ones.
4. Exponential Moving Average (EMA)
An Exponential Moving Average gives more weight to recent data points using a decay factor:
def exponential_moving_average(data, alpha):
ema = [data[0]]
for value in data[1:]:
ema.append(alpha * value + (1 - alpha) * ema[-1])
return np.array(ema)
alpha = 0.2 # Smoothing factor
ema = exponential_moving_average(data, alpha)
print("Exponential Moving Average:", ema)
Output:
Exponential Moving Average: [1. 1.2 1.56 2.048
2.6384 3.31072 4.048576 4.8388608 5.67108864 6.53687091]
The EMA shows a smoother progression compared to other averages. With α=0.2:
- First value starts at 1.0
- Each subsequent value gives 20% weight to the new data point and 80% to the previous EMA
- Notice how the values lag behind the original data but smooth out fluctuations
Comparing the Different Averages
Looking at our results, we can observe how each type of moving average behaves differently:
- SMA provides equal weighting to all points in the window, resulting in uniform smoothing
- CMA shows the overall trend from the beginning, useful for seeing long-term progress
- WMA allows custom weighting within a window, good for emphasizing recent data while considering history
- EMA provides exponentially decreasing weights to past values, making it more responsive to recent changes while maintaining some historical influence
Choosing the Right Moving Average
- Use SMA when you want to smooth out short-term fluctuations with equal importance to all points in the window
- Use CMA when you need to track the overall average from the beginning of your data
- Use WMA when you want precise control over how much each point in the window influences the average
- Use EMA when you want to give more weight to recent data with a smooth decay of importance for older data
Conclusion
Moving averages transform noisy time series into clearer signals by applying different averaging techniques. Each method shapes the signal differently – choosing between them depends on your signal’s characteristics and your analytical goals. Understanding how information decays across time windows enables you to match the mathematical properties of each technique to your specific analysis requirements.
