Time Series Analysis: Understanding Machine Trends Over Time
What Are Time Series?
A time series is a sequence of data points ordered by time. In industry, nearly everything is a time series: motor temperatures, daily production counts, weekly energy bills. The time ordering carries information -- yesterday's vibration influences today's, and today's influences tomorrow's.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import timedelta
Seasonal Decomposition: Trend, Seasonality, and Noise
Every industrial time series combines three components: trend (long-term direction), seasonality (repeating patterns), and residual (random noise).
from statsmodels.tsa.seasonal import seasonal_decompose
np.random.seed(42)
days = 365
timestamps = pd.date_range("2025-01-01", periods=days, freq="D")
trend = np.linspace(1000, 1200, days)
weekly = 100 * np.sin(2 * np.pi * np.arange(days) / 7)
noise = np.random.normal(0, 30, days)
energy = pd.Series(trend + weekly + noise, index=timestamps, name="energy_kwh")
result = seasonal_decompose(energy, model="additive", period=7)
fig, axes = plt.subplots(4, 1, figsize=(14, 10), sharex=True)
result.observed.plot(ax=axes[0], title="Observed")
result.trend.plot(ax=axes[1], title="Trend")
result.seasonal.plot(ax=axes[2], title="Seasonality (7-day)")
result.resid.plot(ax=axes[3], title="Residual")
plt.tight_layout()
plt.show()
Moving Average and Exponential Smoothing
Moving average smooths fluctuations to reveal trends. Exponential smoothing gives more weight to recent observations.
energy_ma7 = energy.rolling(window=7).mean()
energy_ma30 = energy.rolling(window=30).mean()
energy_ewm = energy.ewm(span=7).mean()
fig, ax = plt.subplots(figsize=(14, 6))
energy.plot(ax=ax, alpha=0.3, label="Raw data")
energy_ma7.plot(ax=ax, label="7-day MA", linewidth=2)
energy_ma30.plot(ax=ax, label="30-day MA", linewidth=2)
energy_ewm.plot(ax=ax, label="EWM (span=7)", linewidth=2)
ax.legend()
plt.tight_layout()
plt.show()
A short window tracks changes quickly but retains noise. A long window gives a smooth trend but lags behind real changes. For predictive maintenance, responsiveness usually matters more.
ARIMA Model: Classical Forecasting
ARIMA captures the relationship between a value and its past values. Parameters (p, d, q) control autoregressive terms, differencing, and moving average terms.
from statsmodels.tsa.arima.model import ARIMA
from sklearn.metrics import mean_absolute_error
train = energy[:-30]
test = energy[-30:]
model = ARIMA(train, order=(2, 1, 2))
fit = model.fit()
forecast = fit.forecast(steps=30)
mae = mean_absolute_error(test, forecast)
print(f"ARIMA MAE: {mae:.2f} kWh ({mae / energy.mean() * 100:.1f}% of mean)")
fig, ax = plt.subplots(figsize=(14, 6))
train[-60:].plot(ax=ax, label="Training data")
test.plot(ax=ax, label="Actual")
forecast.plot(ax=ax, label="ARIMA Forecast", style="--")
ax.set_title("ARIMA Energy Consumption Forecast")
ax.legend()
plt.tight_layout()
plt.show()
Parameter Selection
best_aic = np.inf
best_order = None
for p in range(3):
for d in range(2):
for q in range(3):
try:
m = ARIMA(train, order=(p, d, q)).fit()
if m.aic < best_aic:
best_aic = m.aic
best_order = (p, d, q)
except:
continue
print(f"Best ARIMA order: {best_order}, AIC: {best_aic:.1f}")
Prophet: Simple Yet Powerful Forecasting
Prophet handles missing data, outliers, and multiple seasonalities with minimal tuning -- perfect for engineers who need results quickly.
from prophet import Prophet
df_prophet = pd.DataFrame({"ds": energy.index, "y": energy.values})
train_p = df_prophet[:-30]
test_p = df_prophet[-30:]
model_p = Prophet(yearly_seasonality=True, weekly_seasonality=True,
daily_seasonality=False)
model_p.fit(train_p)
future = model_p.make_future_dataframe(periods=30)
forecast_p = model_p.predict(future)
mae_p = mean_absolute_error(test_p["y"], forecast_p["yhat"].tail(30))
print(f"Prophet MAE: {mae_p:.2f} kWh")
fig = model_p.plot_components(forecast_p)
plt.tight_layout()
plt.show()
Prophet automatically decomposes the forecast into trend and seasonal components, making it easy to explain to plant managers.
Practical Example: Predicting Motor Maintenance From Vibration Data
A factory monitors a critical pump motor and wants to predict when maintenance will be needed.
np.random.seed(42)
days = 180
timestamps = pd.date_range("2025-01-01", periods=days, freq="D")
trend = np.linspace(2.0, 5.5, days)
weekly = 0.3 * np.sin(2 * np.pi * np.arange(days) / 7)
noise = np.random.normal(0, 0.2, days)
vibration = pd.Series(trend + weekly + noise, index=timestamps)
train = vibration[:150]
test = vibration[150:]
model = ARIMA(train, order=(2, 1, 1))
fit = model.fit()
forecast = fit.forecast(steps=30)
threshold = 5.0
days_to_threshold = None
for i, val in enumerate(forecast):
if val >= threshold:
days_to_threshold = i + 1
break
print(f"Forecast MAE: {mean_absolute_error(test, forecast):.3f} mm/s")
if days_to_threshold:
maint_date = train.index[-1] + timedelta(days=days_to_threshold)
print(f"WARNING: Vibration exceeds {threshold} mm/s in {days_to_threshold} days")
print(f"Schedule maintenance before: {maint_date.date()}")
fig, ax = plt.subplots(figsize=(14, 6))
train[-30:].plot(ax=ax, label="Historical")
test.plot(ax=ax, label="Actual")
forecast.plot(ax=ax, label="Forecast", style="--")
ax.axhline(threshold, color="red", linestyle=":", label="Threshold")
ax.set_ylabel("Vibration (mm/s)")
ax.set_title("Predictive Maintenance: Motor Vibration Forecast")
ax.legend()
plt.tight_layout()
plt.show()
Summary
In this lesson you learned to analyze and forecast industrial time series. You decomposed signals into trend, seasonality, and noise. You applied moving average and exponential smoothing for signal cleaning. You built ARIMA models with parameter selection and used Prophet for accessible forecasting. Finally, you predicted when a motor will need maintenance based on vibration trends. In the next lesson, you will learn proper model evaluation for production reliability.