Time Series Analysis
Overview
Time series analysis examines data points collected over time to identify patterns, trends, and seasonality for forecasting and understanding temporal dynamics.
When to Use
- Forecasting future values based on historical trends
- Detecting seasonality and cyclical patterns in data
- Analyzing trends over time in sales, stock prices, or website traffic
- Understanding autocorrelation and temporal dependencies
- Making time-based predictions with confidence intervals
- Decomposing data into trend, seasonal, and residual components
Core Components
- Trend: Long-term directional movement
- Seasonality: Repeating patterns at fixed intervals
- Cyclicity: Long-term oscillations (non-fixed periods)
- Stationarity: Constant mean, variance over time
- Autocorrelation: Correlation with past values
Key Techniques
- Decomposition: Separating trend, seasonal, residual components
- Differencing: Making data stationary
- ARIMA: AutoRegressive Integrated Moving Average models
- Exponential Smoothing: Weighted average of past values
- SARIMA: Seasonal ARIMA models
Implementation with Python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller, acf, pacf
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.holtwinters import ExponentialSmoothing
# Create sample time series data
dates = pd.date_range('2020-01-01', periods=365, freq='D')
values = 100 + np.sin(np.arange(365) * 2*np.pi / 365) * 20 + np.random.normal(0, 5, 365)
ts = pd.Series(values, index=dates)
# Visualize time series
fig, axes = plt.subplots(2, 2, figsize=(14, 8))
axes[0, 0].plot(ts)
axes[0, 0].set_title('Original Time Series')
axes[0, 0].set_ylabel('Value')
# Decomposition
decomposition = seasonal_decompose(ts, model='additive', period=30)
axes[0, 1].plot(decomposition.trend)
axes[0, 1].set_title('Trend Component')
axes[1, 0].plot(decomposition.seasonal)
axes[1, 0].set_title('Seasonal Component')
axes[1, 1].plot(decomposition.resid)
axes[1, 1].set_title('Residual Component')
plt.tight_layout()
plt.show()
# Test for stationarity (Augmented Dickey-Fuller)
result = adfuller(ts)
print(f"ADF Test Statistic: {result[0]:.6f}")
print(f"P-value: {result[1]:.6f}")
print(f"Critical Values: {result[4]}")
if result[1] <= 0.05:
print("Time series is stationary")
else:
print("Time series is non-stationary - differencing needed")
# First differencing for stationarity
ts_diff = ts.diff().dropna()
result_diff = adfuller(ts_diff)
print(f"\nAfter differencing - ADF p-value: {result_diff[1]:.6f}")
# Autocorrelation and Partial Autocorrelation
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(ts_diff, lags=40, ax=axes[0])
axes[0].set_title('ACF')
plot_pacf(ts_diff, lags=40, ax=axes[1])
axes[1].set_title('PACF')
plt.tight_layout()
plt.show()
# ARIMA Model
arima_model = ARIMA(ts, order=(1, 1, 1))
arima_result = arima_model.fit()
print(arima_result.summary())
# Forecast
forecast_steps = 30
forecast = arima_result.get_forecast(steps=forecast_steps)
forecast_df = forecast.conf_int()
forecast_mean = forecast.predicted_mean
# Plot forecast
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(ts.index[-90:], ts[-90:], label='Historical')
ax.plot(forecast_df.index, forecast_mean, label='Forecast', color='red')
ax.fill_between(
forecast_df.index,
forecast_df.iloc[:, 0],
forecast_df.iloc[:, 1],
color='red', alpha=0.2
)
ax.set_title('ARIMA Forecast with Confidence Interval')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()
# Exponential Smoothing
exp_smooth = ExponentialSmoothing(
ts, seasonal_periods=30, trend='add', seasonal='add', initialization_method='estimated'
)
exp_result = exp_smooth.fit()
# Model diagnostics
fig = exp_result.plot_diagnostics(figsize=(12, 8))
plt.tight_layout()
plt.show()
# Custom moving average analysis
window_sizes = [7, 30, 90]
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(ts.index, ts.values, label='Original', alpha=0.7)
for window in window_sizes:
ma = ts.rolling(window=window).mean()
ax.plot(ma.index, ma.values, label=f'MA({window})')
ax.set_title('Moving Averages')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()
# Seasonal subseries plot
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
for i, month in enumerate(range(1, 5)):
month_data = ts[ts.index.month == month]
axes[i // 2, i % 2].plot(month_data.values)
axes[i // 2, i % 2].set_title(f'Month {month} Pattern')
plt.tight_layout()
plt.show()
# Forecast accuracy metrics
def calculate_forecast_metrics(actual, predicted):
mae = np.mean(np.abs(actual - predicted))
rmse = np.sqrt(np.mean((actual - predicted) ** 2))
mape = np.mean(np.abs((actual - predicted) / actual)) * 100
return {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}
metrics = calculate_forecast_metrics(ts[-30:], forecast_mean[:30])
print(f"\nForecast Metrics:\n{metrics}")
# Additional analysis techniques
# Step 10: Seasonal subseries plots
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
for i, season in enumerate([1, 2, 3, 4]):
seasonal_ts = ts[ts.index.month % 4 == season % 4]
axes[i // 2, i % 2].plot(seasonal_ts.values)
axes[i // 2, i % 2].set_title(f'Season {season}')
plt.tight_layout()
plt.show()
# Step 11: Granger causality (for multiple series)
from statsmodels.tsa.stattools import grangercausalitytests
# Create another series for testing
ts2 = ts.shift(1).fillna(method='bfill')
try:
print("\nGranger Causality Test:")
print(f"Test whether ts2 Granger-causes ts:")
gc_result = grangercausalitytests(np.column_stack([ts.values, ts2.values]), maxlag=3)
except Exception as e:
print(f"Granger causality not performed: {str(e)[:50]}")
# Step 12: Autocorrelation and partial autocorrelation analysis
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
acf_values = acf(ts.dropna(), nlags=20)
pacf_values = pacf(ts.dropna(), nlags=20)
# Step 13: Seasonal strength
def seasonal_strength(series, seasonal_period=30):
seasonal = seasonal_decompose(series, model='additive', period=seasonal_period)
var_residual = np.var(seasonal.resid.dropna())
var_seasonal = np.var(seasonal.seasonal)
return 1 - (var_residual / (var_residual + var_seasonal)) if (var_residual + var_seasonal) > 0 else 0
ss = seasonal_strength(ts)
print(f"\nSeasonal Strength: {ss:.3f}")
# Step 14: Forecasting with uncertainty
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(ts.index[-60:], ts.values[-60:], label='Historical', linewidth=2)
# Multiple horizon forecasts
for steps_ahead in [10, 20, 30]:
try:
fc = arima_result.get_forecast(steps=steps_ahead)
fc_mean = fc.predicted_mean
ax.plot(pd.date_range(ts.index[-1], periods=steps_ahead+1)[1:],
fc_mean.values, marker='o', label=f'Forecast (+{steps_ahead})')
except:
pass
ax.set_title('Multi-step Ahead Forecasts')
ax.set_xlabel('Date')
ax.set_ylabel('Value')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Step 15: Model comparison summary
print("\nTime Series Analysis Complete!")
print(f"Original series length: {len(ts)}")
print(f"Trend strength: {1 - np.var(decomposition.resid.dropna()) / np.var((ts - ts.mean()).dropna()):.3f}")
print(f"Seasonal strength: {ss:.3f}")
Stationarity
- Stationary: Mean, variance, autocorrelation constant over time
- Non-stationary: Trend or seasonal patterns present
- Solution: Differencing, log transformation, or detrending
Model Selection
- ARIMA: Good for univariate forecasting
- SARIMA: Includes seasonal components
- Exponential Smoothing: Simpler, good for trends
- Prophet: Handles holidays and changepoints
Evaluation Metrics
- MAE: Mean Absolute Error
- RMSE: Root Mean Squared Error
- MAPE: Mean Absolute Percentage Error
Deliverables
- Decomposition analysis charts
- Stationarity test results
- ACF/PACF plots
- Fitted models with diagnostics
- Forecast with confidence intervals
- Accuracy metrics comparison
1---2name: time-series-analysis3description: Analyze temporal data patterns including trends, seasonality, autocorrelation, and forecasting for time series decomposition, trend analysis, and forecasting models4---5
6# Time Series Analysis
7
8## Overview
9
10Time series analysis examines data points collected over time to identify patterns, trends, and seasonality for forecasting and understanding temporal dynamics.
11
12## When to Use
13
14- Forecasting future values based on historical trends
15- Detecting seasonality and cyclical patterns in data
16- Analyzing trends over time in sales, stock prices, or website traffic
17- Understanding autocorrelation and temporal dependencies
18- Making time-based predictions with confidence intervals
19- Decomposing data into trend, seasonal, and residual components
20
21## Core Components
22
23- **Trend**: Long-term directional movement
24- **Seasonality**: Repeating patterns at fixed intervals
25- **Cyclicity**: Long-term oscillations (non-fixed periods)
26- **Stationarity**: Constant mean, variance over time
27- **Autocorrelation**: Correlation with past values
28
29## Key Techniques
30
31- **Decomposition**: Separating trend, seasonal, residual components
32- **Differencing**: Making data stationary
33- **ARIMA**: AutoRegressive Integrated Moving Average models
34- **Exponential Smoothing**: Weighted average of past values
35- **SARIMA**: Seasonal ARIMA models
36
37## Implementation with Python
38
39```python
40import pandas as pd
41import numpy as np
42import matplotlib.pyplot as plt
43from statsmodels.tsa.seasonal import seasonal_decompose
44from statsmodels.tsa.stattools import adfuller, acf, pacf
45from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
46from statsmodels.tsa.arima.model import ARIMA
47from statsmodels.tsa.holtwinters import ExponentialSmoothing
48
49# Create sample time series data
50dates = pd.date_range('2020-01-01', periods=365, freq='D')
51values = 100 + np.sin(np.arange(365) * 2*np.pi / 365) * 20 + np.random.normal(0, 5, 365)
52ts = pd.Series(values, index=dates)
53
54# Visualize time series
55fig, axes = plt.subplots(2, 2, figsize=(14, 8))
56
57axes[0, 0].plot(ts)
58axes[0, 0].set_title('Original Time Series')
59axes[0, 0].set_ylabel('Value')
60
61# Decomposition
62decomposition = seasonal_decompose(ts, model='additive', period=30)
63axes[0, 1].plot(decomposition.trend)
64axes[0, 1].set_title('Trend Component')
65
66axes[1, 0].plot(decomposition.seasonal)
67axes[1, 0].set_title('Seasonal Component')
68
69axes[1, 1].plot(decomposition.resid)
70axes[1, 1].set_title('Residual Component')
71
72plt.tight_layout()
73plt.show()
74
75# Test for stationarity (Augmented Dickey-Fuller)
76result = adfuller(ts)
77print(f"ADF Test Statistic: {result[0]:.6f}")
78print(f"P-value: {result[1]:.6f}")
79print(f"Critical Values: {result[4]}")
80
81if result[1] <= 0.05:
82 print("Time series is stationary")
83else:
84 print("Time series is non-stationary - differencing needed")
85
86# First differencing for stationarity
87ts_diff = ts.diff().dropna()
88result_diff = adfuller(ts_diff)
89print(f"\nAfter differencing - ADF p-value: {result_diff[1]:.6f}")
90
91# Autocorrelation and Partial Autocorrelation
92fig, axes = plt.subplots(1, 2, figsize=(12, 4))
93
94plot_acf(ts_diff, lags=40, ax=axes[0])
95axes[0].set_title('ACF')
96
97plot_pacf(ts_diff, lags=40, ax=axes[1])
98axes[1].set_title('PACF')
99
100plt.tight_layout()
101plt.show()
102
103# ARIMA Model
104arima_model = ARIMA(ts, order=(1, 1, 1))
105arima_result = arima_model.fit()
106print(arima_result.summary())
107
108# Forecast
109forecast_steps = 30
110forecast = arima_result.get_forecast(steps=forecast_steps)
111forecast_df = forecast.conf_int()
112forecast_mean = forecast.predicted_mean
113
114# Plot forecast
115fig, ax = plt.subplots(figsize=(12, 5))
116ax.plot(ts.index[-90:], ts[-90:], label='Historical')
117ax.plot(forecast_df.index, forecast_mean, label='Forecast', color='red')
118ax.fill_between(
119 forecast_df.index,
120 forecast_df.iloc[:, 0],
121 forecast_df.iloc[:, 1],
122 color='red', alpha=0.2
123)
124ax.set_title('ARIMA Forecast with Confidence Interval')
125ax.legend()
126ax.grid(True, alpha=0.3)
127plt.show()
128
129# Exponential Smoothing
130exp_smooth = ExponentialSmoothing(
131 ts, seasonal_periods=30, trend='add', seasonal='add', initialization_method='estimated'
132)
133exp_result = exp_smooth.fit()
134
135# Model diagnostics
136fig = exp_result.plot_diagnostics(figsize=(12, 8))
137plt.tight_layout()
138plt.show()
139
140# Custom moving average analysis
141window_sizes = [7, 30, 90]
142fig, ax = plt.subplots(figsize=(12, 5))
143
144ax.plot(ts.index, ts.values, label='Original', alpha=0.7)
145
146for window in window_sizes:
147 ma = ts.rolling(window=window).mean()
148 ax.plot(ma.index, ma.values, label=f'MA({window})')
149
150ax.set_title('Moving Averages')
151ax.legend()
152ax.grid(True, alpha=0.3)
153plt.show()
154
155# Seasonal subseries plot
156fig, axes = plt.subplots(2, 2, figsize=(12, 8))
157for i, month in enumerate(range(1, 5)):
158 month_data = ts[ts.index.month == month]
159 axes[i // 2, i % 2].plot(month_data.values)
160 axes[i // 2, i % 2].set_title(f'Month {month} Pattern')
161
162plt.tight_layout()
163plt.show()
164
165# Forecast accuracy metrics
166def calculate_forecast_metrics(actual, predicted):
167 mae = np.mean(np.abs(actual - predicted))
168 rmse = np.sqrt(np.mean((actual - predicted) ** 2))
169 mape = np.mean(np.abs((actual - predicted) / actual)) * 100
170 return {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}
171
172metrics = calculate_forecast_metrics(ts[-30:], forecast_mean[:30])
173print(f"\nForecast Metrics:\n{metrics}")
174
175# Additional analysis techniques
176
177# Step 10: Seasonal subseries plots
178fig, axes = plt.subplots(2, 2, figsize=(12, 8))
179for i, season in enumerate([1, 2, 3, 4]):
180 seasonal_ts = ts[ts.index.month % 4 == season % 4]
181 axes[i // 2, i % 2].plot(seasonal_ts.values)
182 axes[i // 2, i % 2].set_title(f'Season {season}')
183plt.tight_layout()
184plt.show()
185
186# Step 11: Granger causality (for multiple series)
187from statsmodels.tsa.stattools import grangercausalitytests
188
189# Create another series for testing
190ts2 = ts.shift(1).fillna(method='bfill')
191
192try:
193 print("\nGranger Causality Test:")
194 print(f"Test whether ts2 Granger-causes ts:")
195 gc_result = grangercausalitytests(np.column_stack([ts.values, ts2.values]), maxlag=3)
196except Exception as e:
197 print(f"Granger causality not performed: {str(e)[:50]}")
198
199# Step 12: Autocorrelation and partial autocorrelation analysis
200from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
201
202acf_values = acf(ts.dropna(), nlags=20)
203pacf_values = pacf(ts.dropna(), nlags=20)
204
205# Step 13: Seasonal strength
206def seasonal_strength(series, seasonal_period=30):
207 seasonal = seasonal_decompose(series, model='additive', period=seasonal_period)
208 var_residual = np.var(seasonal.resid.dropna())
209 var_seasonal = np.var(seasonal.seasonal)
210 return 1 - (var_residual / (var_residual + var_seasonal)) if (var_residual + var_seasonal) > 0 else 0
211
212ss = seasonal_strength(ts)
213print(f"\nSeasonal Strength: {ss:.3f}")
214
215# Step 14: Forecasting with uncertainty
216fig, ax = plt.subplots(figsize=(12, 5))
217ax.plot(ts.index[-60:], ts.values[-60:], label='Historical', linewidth=2)
218
219# Multiple horizon forecasts
220for steps_ahead in [10, 20, 30]:
221 try:
222 fc = arima_result.get_forecast(steps=steps_ahead)
223 fc_mean = fc.predicted_mean
224 ax.plot(pd.date_range(ts.index[-1], periods=steps_ahead+1)[1:],
225 fc_mean.values, marker='o', label=f'Forecast (+{steps_ahead})')
226 except:
227 pass
228
229ax.set_title('Multi-step Ahead Forecasts')
230ax.set_xlabel('Date')
231ax.set_ylabel('Value')
232ax.legend()
233ax.grid(True, alpha=0.3)
234plt.tight_layout()
235plt.show()
236
237# Step 15: Model comparison summary
238print("\nTime Series Analysis Complete!")
239print(f"Original series length: {len(ts)}")
240print(f"Trend strength: {1 - np.var(decomposition.resid.dropna()) / np.var((ts - ts.mean()).dropna()):.3f}")
241print(f"Seasonal strength: {ss:.3f}")
242```
243
244## Stationarity
245
246- **Stationary**: Mean, variance, autocorrelation constant over time
247- **Non-stationary**: Trend or seasonal patterns present
248- **Solution**: Differencing, log transformation, or detrending
249
250## Model Selection
251
252- **ARIMA**: Good for univariate forecasting
253- **SARIMA**: Includes seasonal components
254- **Exponential Smoothing**: Simpler, good for trends
255- **Prophet**: Handles holidays and changepoints
256
257## Evaluation Metrics
258
259- **MAE**: Mean Absolute Error
260- **RMSE**: Root Mean Squared Error
261- **MAPE**: Mean Absolute Percentage Error
262
263## Deliverables
264
265- Decomposition analysis charts
266- Stationarity test results
267- ACF/PACF plots
268- Fitted models with diagnostics
269- Forecast with confidence intervals
270- Accuracy metrics comparison