As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!
When I first started working with time series data, I thought it was just about plotting points over time. I was wrong. Time series is its own world, with unique rules and tools. It’s about understanding the rhythm hidden within data points collected day after day, hour after hour. Over the years, I've learned that success here depends on a handful of reliable techniques. Let me walk you through eight practical methods I use regularly, from cleaning data to spotting outliers. We'll keep it straightforward.
First things first: getting your data in order. Real-world data is messy. Sensors fail, holidays create gaps, and different systems record at different intervals. Your first job is to create a consistent timeline. In Python, pandas is your best friend for this. It handles dates and times with a flexibility that saves hours of headache.
Let's say you have some temperature readings, but a few are missing. Here’s how you might clean it up. You create a complete daily timeline and fill in the blanks intelligently, perhaps using the last known value or an average.
import pandas as pd
import numpy as np
# Simulate messy data: daily readings with gaps
dates = pd.date_range('2024-06-01', periods=30, freq='d')
values = np.random.randint(15, 30, size=30) # Temperatures in Celsius
ts = pd.Series(values, index=dates, name='temperature')
# Simulate 5 missing days
ts = ts.drop(ts.sample(5).index)
print("Data with gaps:")
print(ts.head(10))
# Create a complete daily index
full_index = pd.date_range(start='2024-06-01', end='2024-06-30', freq='d')
# Reindex the series to this complete timeline
ts_complete = ts.reindex(full_index)
# Fill gaps: forward fill uses the last known value
ts_filled = ts_complete.ffill()
# Alternatively, use a rolling average to fill
ts_smoothed = ts_complete.interpolate(method='linear')
print("\nAfter filling gaps:")
print(ts_filled.head(10))
This process is called resampling and alignment. If you have minute-by-minute data but need a weekly report, you downsample and calculate the weekly average. If you have sporadic events and need a smooth curve, you upsample and interpolate. Getting this uniform timeline is the non-negotiable first step for any analysis that follows.
Once your data is on a regular schedule, you need to see what's in it. Most time series are made of three parts: a long-term direction (trend), a repeating pattern (seasonality), and random noise. Separating them helps you understand each piece. This is called decomposition.
I remember working with website traffic data and seeing a yearly cycle I hadn't noticed before. Decomposition made it obvious. Here’s how you do it.
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt
# Let's create a clear example: sales with an upward trend and weekly seasonality
np.random.seed(42)
periods = 90 # ~3 months of daily data
t = np.arange(periods)
# Components
trend = 0.5 * t # Sales slowly increasing
seasonal = 10 * np.sin(2 * np.pi * t / 7) # Weekly pattern (7-day cycle)
noise = np.random.normal(0, 2, periods) # Random fluctuations
sales = trend + seasonal + noise
dates = pd.date_range('2024-01-01', periods=periods, freq='d')
sales_series = pd.Series(sales, index=dates)
# Perform additive decomposition
result = seasonal_decompose(sales_series, model='additive', period=7)
# Plot the pieces
fig = result.plot()
fig.set_size_inches(12, 8)
plt.show()
# You can access each component
print("Trend for last 5 days:")
print(result.trend.tail())
print("\nSeasonal pattern for one week:")
print(result.seasonal.head(7))
The model='additive' argument assumes the components add together. If your seasonal swings get bigger as the trend rises, you might use model='multiplicative'. This simple breakdown tells you if your data has predictable cycles, if it's generally going up or down, and what's left over as unexplained noise. It’s a diagnostic tool.
Now, to build a forecast, you often need a "stationary" series. This is a key concept. It means the data's statistical properties—like its average and variance—don't change over time. A series with a strong trend is not stationary. You can make it stationary by differencing: subtracting each value from the previous one.
This process is linked to understanding how a value is connected to its own past, known as autocorrelation. These relationships guide our next models.
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# Let's use our sales data
# First, let's see if it's stationary by looking at the Autocorrelation Function (ACF)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(sales_series, lags=20, ax=axes[0])
axes[0].set_title('Autocorrelation Function (ACF)')
# Slow decay in ACF suggests non-stationarity (a trend is present)
# Let's difference the data to remove the trend
sales_differenced = sales_series.diff().dropna()
plot_acf(sales_differenced, lags=20, ax=axes[1])
axes[1].set_title('ACF After First Differencing')
plt.show()
# The Partial Autocorrelation Function (PACF) helps identify order for some models
fig, ax = plt.subplots(figsize=(8, 4))
plot_pacf(sales_differenced, lags=15, ax=ax, method='ywm')
ax.set_title('Partial Autocorrelation Function (PACF)')
plt.show()
The ACF plot shows how each observation relates to observations at previous time steps. If it drops slowly, you likely have a trend. The PACF helps identify the direct relationship between an observation and its lag, ignoring the intermediaries. These plots are your roadmap for choosing parameters in models like ARIMA.
For many forecasting tasks, especially with clear trends and seasonality, Exponential Smoothing is a powerful and intuitive starting point. It works by giving more weight to recent observations. The Holt-Winters method extends this to handle both trend and seasonality. I find it remarkably effective for business forecasts like monthly revenue.
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_absolute_percentage_error
# Split into training and testing periods
train = sales_series[:75] # First ~2.5 months
test = sales_series[75:] # Last ~2 weeks
# Fit a Holt-Winters model with additive trend and seasonality
model_hw = ExponentialSmoothing(train,
trend='add',
seasonal='add',
seasonal_periods=7).fit()
# Forecast for the length of our test set
forecast_hw = model_hw.forecast(len(test))
# Plot the results
plt.figure(figsize=(12, 6))
plt.plot(train.index, train, label='Training Data')
plt.plot(test.index, test, label='Actual Sales', color='green')
plt.plot(test.index, forecast_hw, label='HW Forecast', color='red', linestyle='--')
plt.legend()
plt.title('Holt-Winters Exponential Smoothing Forecast')
plt.grid(True, alpha=0.3)
plt.show()
# Check accuracy
hw_mape = mean_absolute_percentage_error(test, forecast_hw) * 100
print(f"Forecast Mean Absolute Percentage Error (MAPE): {hw_mape:.2f}%")
The seasonal_periods=7 tells the model to look for a weekly pattern. The model smooths out the noise, captures the trend, and learns the seasonal cycle. It then projects that combined pattern forward. The MAPE tells you, on average, how far off your forecast is as a percentage of the actual value.
When you need a more robust statistical model, especially for data without strong seasonality, ARIMA is a classic choice. The name stands for AutoRegressive Integrated Moving Average. It sounds complex, but it combines the ideas we just saw: using past values (AR), differencing to make data stationary (I), and modeling the error as a combination of past errors (MA).
Finding the right parameters (p,d,q) can be tricky. Thankfully, libraries can automate this search.
# Note: You may need to install pmdarima: pip install pmdarima
from pmdarima import auto_arima
# Let the library search for the best ARIMA parameters
auto_model = auto_arima(train,
start_p=0, start_q=0,
max_p=3, max_q=3,
d=None, # Let it test for differencing order
seasonal=False, # We'll handle non-seasonal data here
trace=True, # Print the search progress
error_action='ignore',
suppress_warnings=True,
stepwise=True)
print(f"\nBest model identified: {auto_model.order}")
# Fit the model with the best parameters
arima_model = auto_model.fit(train)
# Forecast
forecast_arima = arima_model.predict(n_periods=len(test))
# Calculate error
arima_mape = mean_absolute_percentage_error(test, forecast_arima) * 100
print(f"ARIMA Forecast MAPE: {arima_mape:.2f}%")
The auto_arima function tests many combinations and selects the one with the best statistical fit (usually the lowest AIC score). The order output (p, d, q) is your recipe. This model is powerful for short-term forecasting where the future looks a lot like the recent past.
For data with multiple layers of seasonality—like daily data with weekly and yearly patterns—or when you know holidays have a big impact, Facebook's Prophet library is a fantastic tool. It’s designed to be robust to missing data and sudden shifts. I’ve used it for forecasting energy demand, which dips on weekends and holidays.
from prophet import Prophet
# Prophet requires a specific dataframe format with columns 'ds' and 'y'
prophet_train = train.reset_index()
prophet_train.columns = ['ds', 'y']
# Initialize and fit the model
m = Prophet(yearly_seasonality=False, # Our sample is only 3 months
weekly_seasonality=True) # We have a weekly cycle
m.fit(prophet_train)
# Create a dataframe for future dates (our test period)
future = m.make_future_dataframe(periods=len(test), freq='D')
# Generate the forecast
forecast_prophet = m.predict(future)
# The forecast is in a large dataframe. Let's extract the prediction for our test period.
forecast_test = forecast_prophet.set_index('ds').loc[test.index, 'yhat']
# Plot the components
fig2 = m.plot_components(forecast_prophet)
# Calculate error
prophet_mape = mean_absolute_percentage_error(test, forecast_test) * 100
print(f"Prophet Forecast MAPE: {prophet_mape:.2f}%")
Prophet decomposes the time series similarly to the classical method but uses an additive model that can flexibly fit non-linear trends. The plot_components function shows you the trend, weekly seasonality, and any other patterns you added. It handles outliers well and gives you confidence intervals automatically.
Sometimes, traditional time series models feel limiting. You might have other relevant data: was it a weekend? Was there a marketing campaign? This is where machine learning shines. You can frame the problem as a supervised learning task: use past values and other features to predict the next value.
The key is feature engineering—creating input variables from the time series itself.
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
def create_lag_features(series, lags):
"""Create a dataframe with lagged versions of the series."""
df = pd.DataFrame(series)
for lag in lags:
df[f'lag_{lag}'] = series.shift(lag)
return df.dropna()
# Create features using lags of 1, 2, 3, 7 days (to capture weekly pattern)
lags = [1, 2, 3, 7]
featured_df = create_lag_features(sales_series, lags)
# Our target 'y' is the current value. Our features 'X' are the past values.
X = featured_df.drop(columns=['temperature']) # Using the series name from earlier
y = featured_df['temperature']
# Split temporally
split = int(len(X) * 0.8)
X_train, X_test = X.iloc[:split], X.iloc[split:]
y_train, y_test = y.iloc[:split], y.iloc[split:]
# Train a simple model
model_rf = RandomForestRegressor(n_estimators=100, random_state=42)
model_rf.fit(X_train, y_train)
# Predict
y_pred = model_rf.predict(X_test)
# Evaluate
ml_mae = mean_absolute_error(y_test, y_pred)
print(f"Machine Learning (Random Forest) Mean Absolute Error: {ml_mae:.2f}")
# Show which lags were most important
feature_importance = pd.DataFrame({
'feature': X.columns,
'importance': model_rf.feature_importances_
}).sort_values('importance', ascending=False)
print("\nFeature Importance:")
print(feature_importance)
This approach is incredibly flexible. You can add features like day-of-week, month, moving averages, or even external data like weather. The model learns the complex relationships between these inputs and the output. Random Forests are a good start because they handle non-linearity well and don't require extensive tuning.
Finally, not all analysis is about forecasting the future. Sometimes you need to monitor a system and flag when something goes wrong. Anomaly detection in time series looks for points that deviate significantly from the expected pattern. This could be a sudden spike in server errors, a drop in sales, or a sensor malfunction.
One simple statistical method is to use rolling statistics to define a "normal" band.
def find_anomalies_rolling(series, window=7, n_sigmas=2):
"""Identify points outside n standard deviations from a rolling mean."""
rolling_mean = series.rolling(window=window, center=True).mean()
rolling_std = series.rolling(window=window, center=True).std()
upper_bound = rolling_mean + (n_sigmas * rolling_std)
lower_bound = rolling_mean - (n_sigmas * rolling_std)
anomalies = (series > upper_bound) | (series < lower_bound)
return anomalies, upper_bound, lower_bound
# Apply to our sales data (let's inject a fake anomaly)
sales_with_anomaly = sales_series.copy()
sales_with_anomaly.iloc[45] = 60 # A suspiciously high sale on day 45
anomalies, upper, lower = find_anomalies_rolling(sales_with_anomaly, window=7, n_sigmas=2)
# Plot
plt.figure(figsize=(12, 5))
plt.plot(sales_with_anomaly.index, sales_with_anomaly, label='Sales with Anomaly')
plt.fill_between(sales_with_anomaly.index, lower, upper, color='lightgray', alpha=0.5, label='Normal Range')
plt.scatter(sales_with_anomaly[anomalies].index, sales_with_anomaly[anomalies], color='red', s=50, label='Detected Anomaly')
plt.legend()
plt.title('Anomaly Detection using Rolling Statistics')
plt.grid(True, alpha=0.3)
plt.show()
print(f"Number of anomalies detected: {anomalies.sum()}")
This method defines "normal" based on recent history. A point is flagged if it falls outside, say, two standard deviations from the recent average. For more complex patterns, machine learning models like Isolation Forest can be trained to recognize normal behavior and isolate points that don't fit.
These eight techniques form a practical toolkit. You start by cleaning and structuring your data. You decompose it to understand its anatomy. You check its stationarity and autocorrelation. You forecast using specialized models like Exponential Smoothing, ARIMA, or Prophet. You can also treat it as a machine learning problem. And you always keep an eye out for the unusual. Each method has its place, and often the best results come from trying a few and seeing which understands the rhythm of your specific data the best. The code here is a starting point. The real work begins when you apply it to your own sequences of days, hours, or milliseconds, and start listening to the story they tell.
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva
Top comments (0)