Time Series Foundations & Exploratory Analysis
A detailed foundation for understanding, exploring, visualizing, and preparing time series data before statistical, machine learning, and deep learning forecasting.
Time Series Foundations & Exploratory Analysis
1. Learning Objectives#
By the end of this notebook, you should be able to:
- Explain what time series data is and how it differs from cross-sectional data.
- Identify trend, seasonality, cyclicity, and noise.
- Understand why time order matters.
- Work correctly with timestamps, frequencies, missing dates, and resampling.
- Perform a structured exploratory analysis of time series data.
- Understand stationarity and why it matters for many forecasting methods.
- Calculate and interpret rolling statistics.
- Understand autocorrelation and partial autocorrelation.
- Use ACF and PACF plots to investigate temporal dependence.
- Perform seasonal decomposition with STL.
- Recognize common time-series data problems and avoid future-data leakage.
- Prepare a clean dataset for the statistical and machine-learning notebooks that follow.
2. What Is Time Series Data?
A time series is a sequence of observations recorded over time, usually at regular or meaningful intervals.
Examples:
- Daily stock prices
- Hourly electricity demand
- Monthly sales
- Weekly website traffic
- Minute-level sensor measurements
- Daily temperature
- Quarterly GDP
- Number of orders per day
A simple time series can be represented as:
where each observation is associated with a timestamp:
The key characteristic is that observations are ordered in time.
This means that:
The past can contain information about the future.
That temporal relationship is what makes time series different from ordinary tabular machine learning.
3. Cross-Sectional Data vs Time Series Data
3.1 Cross-Sectional Data#
Cross-sectional data contains observations from different entities at approximately the same point in time.
Example:
| Customer | Age | Income | Purchases |
|---|---|---|---|
| A | 25 | 50000 | 4 |
| B | 41 | 80000 | 9 |
| C | 33 | 62000 | 6 |
The order of rows usually does not carry predictive meaning.
3.2 Time Series Data#
Time series data contains observations indexed by time.
| Date | Sales |
|---|---|
| 2025-01-01 | 120 |
| 2025-01-02 | 135 |
| 2025-01-03 | 128 |
| 2025-01-04 | 145 |
Here, changing the order of observations destroys important information.
For example:
may contain useful predictive information.
3.3 Why This Difference Matters#
In ordinary machine learning, randomly splitting observations into training and test sets can often be reasonable.
In time series, random splitting can allow information from the future to influence training.
For example:
Architecture & Data FlowPast ------------------------------> Future Training data Test data |----------------------|------------->
The model should learn from the past and be evaluated on data that occurs later.
This principle becomes especially important in Notebook 3 and Notebook 5.
4. Types of Time Series
Time series can be classified in several ways.
4.1 Univariate Time Series#
Only one variable is being modeled over time.
Example:
textDate Sales 2025-01-01 120 2025-01-02 135 2025-01-03 128
Mathematically:
4.2 Multivariate Time Series#
Multiple variables are observed over time.
Example:
textDate Sales Price Advertising Temperature 2025-01-01 120 10.5 500 24 2025-01-02 135 10.0 700 25
Now we may model:
Multivariate time series become particularly useful when external variables help explain the target.
4.3 Regularly Spaced Time Series#
Observations occur at a consistent frequency:
text00:00 01:00 02:00 03:00
4.4 Irregularly Spaced Time Series#
Observations occur at different times:
text09:03 09:07 09:15 09:28
Irregular data may need special preprocessing depending on the model.
5. Time Series Components
A time series is often understood through four major components:
- Trend
- Seasonality
- Cyclicity
- Noise
These components are conceptual tools. A real dataset may not cleanly separate into four independent parts.
6. Trend
Trend represents the long-term direction of a series.
A trend can be:
- Increasing
- Decreasing
- Approximately flat
- Nonlinear
Example:
Architecture & Data FlowValue ^ | * | * | * | * | * +----------------------------> Time
A company whose sales grow steadily over several years has an upward trend.
Why Trend Matters#
Many forecasting models behave differently when a strong trend exists.
For example, ARIMA models often use differencing to remove non-stationary trend behavior.
7. Seasonality
Seasonality is a pattern that repeats at a known and fixed frequency.
Examples:
- Retail sales increase every December.
- Electricity demand changes every day.
- Restaurant traffic is higher on weekends.
- Website traffic follows a weekly pattern.
Suppose daily sales have a weekly seasonal pattern:
The value seven days ago may therefore contain useful information.
Important Distinction#
Seasonality is tied to a known repeating calendar or frequency.
For example:
Mathematical FormulationDaily data: Weekly seasonality = 7 Monthly data: Yearly seasonality = 12 Hourly data: Daily seasonality = 24
The correct seasonal period depends on the sampling frequency and domain.
8. Cyclicity
Cycles are long-term rises and falls that do not necessarily repeat at a fixed frequency.
Examples:
- Economic expansion and recession
- Long-term business cycles
- Industry demand cycles
The key distinction is:
›Seasonality -> predictable fixed period Cyclicity -> variable / less regular duration
A cycle may last two years in one period and five years in another.
This makes cyclic behavior harder to model as simple seasonality.
9. Noise
Noise represents unpredictable variation that remains after systematic structure is removed.
Example:
Mathematical FormulationObserved series = Trend + Seasonality + Other structure + Noise
Noise is not necessarily "bad data."
Some variation is genuinely unpredictable.
The goal is not always to eliminate every fluctuation. The goal is to distinguish useful structure from random variation.
10. Additive vs Multiplicative Structure
A common conceptual decomposition is:
Additive#
where:
- = trend
- = seasonality
- = cyclic component
- = remainder/noise
Additive structure is useful when seasonal variation has roughly constant magnitude.
Multiplicative#
Multiplicative structure is useful when seasonal variation grows with the level of the series.
For example:
›Low sales -> seasonal change of ±10 High sales -> seasonal change of ±100
A logarithmic transformation can sometimes convert multiplicative relationships into additive ones:
This will become important in the ARIMA notebook.
11. Time Series EDA Workflow
A useful exploratory workflow is:
Architecture & Data Flow1. Load data | 2. Inspect timestamps | 3. Sort chronologically | 4. Check frequency | 5. Check missing dates | 6. Check missing values | 7. Visualize the raw series | 8. Investigate trend | 9. Investigate seasonality | 10. Examine rolling statistics | 11. Examine ACF/PACF | 12. Check stationarity | 13. Decompose the series | 14. Document findings
This workflow should become a habit.
12. Python Environment
We will primarily use:
🐍 PythonInteractive WebAssemblyimport numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import STL
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.stattools import adfuller
For reproducible notebooks:
🐍 PythonInteractive WebAssemblypd.set_option("display.max_columns", None)
pd.set_option("display.width", 120)
13. Create a Practice Time Series Dataset
To understand the concepts without depending on a specific external dataset, we can create a realistic synthetic daily series.
🐍 PythonInteractive WebAssemblyimport numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
dates = pd.date_range(
start="2022-01-01",
periods=3 * 365,
freq="D"
)
n = len(dates)
trend = np.linspace(100, 180, n)
weekly_seasonality = 12 * np.sin(
2 * np.pi * np.arange(n) / 7
)
yearly_seasonality = 8 * np.sin(
2 * np.pi * np.arange(n) / 365
)
noise = np.random.normal(
loc=0,
scale=5,
size=n
)
sales = (
trend
+ weekly_seasonality
+ yearly_seasonality
+ noise
)
df = pd.DataFrame({
"date": dates,
"sales": sales
})
df.head()
14. Inspecting the Dataset
Always inspect the structure before analyzing it.
🐍 PythonInteractive WebAssemblydf.shape
🐍 PythonInteractive WebAssemblydf.info()
🐍 PythonInteractive WebAssemblydf.head()
🐍 PythonInteractive WebAssemblydf.describe()
Questions to ask:
- Is the timestamp column correctly recognized?
- Is the data sorted?
- What is the frequency?
- Are there missing values?
- Are there duplicate timestamps?
- Are there suspicious values?
- What is the approximate range?
15. Datetime Handling
Convert timestamp columns explicitly.
🐍 PythonInteractive WebAssemblydf["date"] = pd.to_datetime(df["date"])
Sort the data:
🐍 PythonInteractive WebAssemblydf = df.sort_values("date")
Set the timestamp as the index:
🐍 PythonInteractive WebAssemblydf = df.set_index("date")
Now:
🐍 PythonInteractive WebAssemblydf.head()
The index should look like:
text2022-01-01 2022-01-02 2022-01-03 ...
A proper datetime index makes time-based operations much easier.
16. Checking the Time Frequency
Inspect the time differences:
🐍 PythonInteractive WebAssemblydf.index.to_series().diff().value_counts().head()
For daily data, you would typically see:
›1 day
You can also inspect:
🐍 PythonInteractive WebAssemblypd.infer_freq(df.index)
If the frequency cannot be inferred, investigate why.
Possible reasons:
- Missing dates
- Duplicate timestamps
- Irregular observations
- Insufficient observations
17. Missing Dates vs Missing Values
These are not the same problem.
Suppose you expect daily data:
textJan 1 Jan 2 Jan 3 Jan 4
but receive:
textJan 1 Jan 2 Jan 4
January 3 is a missing date.
A missing value is different:
›Jan 3 NaN
The timestamp exists, but the observation is missing.
Always check both.
18. Detect Missing Dates
For daily data:
🐍 PythonInteractive WebAssemblyexpected_dates = pd.date_range(
start=df.index.min(),
end=df.index.max(),
freq="D"
)
missing_dates = expected_dates.difference(df.index)
missing_dates
If the result is empty:
›No missing dates.
If dates appear, investigate them.
19. Reindexing to a Complete Frequency
If daily observations are expected:
🐍 PythonInteractive WebAssemblydf = df.asfreq("D")
Missing dates will now appear as rows with missing values.
Check:
🐍 PythonInteractive WebAssemblydf.isna().sum()
Do not automatically fill missing values.
The correct treatment depends on the meaning of the data.
Possible approaches include:
- Forward fill
- Backward fill
- Interpolation
- Domain-specific imputation
- Leaving values missing
- Removing affected observations
For example:
🐍 PythonInteractive WebAssemblydf["sales"] = df["sales"].interpolate()
Interpolation should only be used when it makes sense for the underlying process.
20. Duplicate Timestamps
Check duplicates:
🐍 PythonInteractive WebAssemblydf.index.duplicated().sum()
If duplicates exist:
🐍 PythonInteractive WebAssemblydf[df.index.duplicated(keep=False)]
Duplicate timestamps may represent:
- Multiple transactions at the same timestamp
- Data ingestion errors
- Multiple measurements
- Different entities
Do not blindly remove duplicates. First understand what they mean.
21. First Visualization
Visualization should be one of the first analytical steps.
🐍 PythonInteractive WebAssemblyplt.figure(figsize=(14, 5))
plt.plot(df.index, df["sales"])
plt.title("Daily Sales")
plt.xlabel("Date")
plt.ylabel("Sales")
plt.show()
When looking at the plot, ask:
- Is there an upward or downward trend?
- Is the variance changing?
- Is there visible seasonality?
- Are there unusual spikes?
- Are there structural breaks?
- Are there long periods of missing data?
22. Zooming Into the Data
A full three-year chart may hide short-term patterns.
Look at a smaller window:
🐍 PythonInteractive WebAssemblydf["sales"].iloc[:90].plot(figsize=(14, 5))
plt.title("First 90 Days")
plt.xlabel("Date")
plt.ylabel("Sales")
plt.show()
Then inspect other periods.
This helps distinguish:
- Short-term seasonality
- Long-term trend
- Outliers
- Changes in behavior
23. Rolling Statistics
Rolling statistics calculate statistics over a moving window.
For example, a 7-day rolling mean:
In pandas:
🐍 PythonInteractive WebAssemblydf["rolling_mean_7"] = df["sales"].rolling(7).mean()
Rolling standard deviation:
🐍 PythonInteractive WebAssemblydf["rolling_std_7"] = df["sales"].rolling(7).std()
Visualize them:
🐍 PythonInteractive WebAssemblyplt.figure(figsize=(14, 5))
plt.plot(df.index, df["sales"], label="Sales")
plt.plot(df.index, df["rolling_mean_7"], label="7-Day Rolling Mean")
plt.title("Sales and Rolling Mean")
plt.legend()
plt.show()
24. Why Rolling Statistics Matter
Rolling statistics help us investigate whether the statistical properties of a series change over time.
For example:
Architecture & Data FlowStable mean + stable variance | v Potentially stationary behavior
versus:
Architecture & Data FlowChanging mean | v Possible trend / non-stationarity
and:
Architecture & Data FlowIncreasing variance | v Possible changing volatility
Rolling statistics are exploratory tools, not definitive stationarity tests.
25. Rolling Window Size
The window controls how much history is used.
🐍 PythonInteractive WebAssemblydf["rolling_mean_7"] = df["sales"].rolling(7).mean()
df["rolling_mean_30"] = df["sales"].rolling(30).mean()
df["rolling_mean_90"] = df["sales"].rolling(90).mean()
Interpretation:
- 7-day window: short-term behavior
- 30-day window: medium-term behavior
- 90-day window: smoother long-term behavior
The appropriate window depends on the business and sampling frequency.
26. Stationarity
Stationarity is one of the most important concepts in classical time series analysis.
A weakly stationary series generally has:
- Constant mean over time.
- Constant variance over time.
- Autocovariance that depends on the lag, not the absolute time.
Informally:
The statistical behavior of the process does not systematically change over time.
A simple stationary-looking series might fluctuate around a stable level:
Architecture & Data FlowValue ^ | * * * | * * * * | * * * | * * * +------------------> Time
A trending series is generally non-stationary:
Architecture & Data FlowValue ^ | * | * | * | * | * +------------------> Time
27. Why Stationarity Matters
Several classical statistical models work best when the relevant series is stationary.
ARIMA, for example, explicitly includes differencing to transform a non-stationary series into a more stationary representation.
Stationarity also makes relationships between observations more stable over time.
However:
Not every forecasting problem requires the raw target itself to be stationary.
Modern machine-learning and deep-learning methods can work directly with non-stationary data, provided the modeling strategy handles temporal structure appropriately.
28. Trend and Stationarity
Suppose:
The mean changes as time increases.
Therefore, the series is not stationary in the usual weak-sense framework.
Differencing can remove the deterministic trend:
If:
then approximately:
which may be much closer to stationary.
29. Differencing
First-order differencing:
🐍 PythonInteractive WebAssemblydf["sales_diff"] = df["sales"].diff()
Visualize:
🐍 PythonInteractive WebAssemblydf["sales_diff"].plot(figsize=(14, 5))
plt.title("First Difference of Sales")
plt.xlabel("Date")
plt.ylabel("Differenced Sales")
plt.show()
Differencing is central to ARIMA and will be covered in depth in Notebook 2.
30. Autocorrelation
Autocorrelation measures the relationship between a time series and its previous values.
At lag :
Examples:
- Lag 1: relationship with yesterday
- Lag 7: relationship with one week ago
- Lag 30: relationship with approximately one month ago
Strong autocorrelation means past observations contain information about later observations.
31. Intuitive Example of Autocorrelation
Suppose:
Mathematical FormulationToday's temperature ≈ Yesterday's temperature
Then lag-1 autocorrelation may be high.
For daily sales with weekly seasonality:
Mathematical FormulationMonday sales ≈ Previous Monday sales
Then lag-7 autocorrelation may be strong.
This is one reason autocorrelation is useful for discovering temporal structure.
32. ACF Plot
The Autocorrelation Function (ACF) shows correlation between the series and lagged versions of itself.
🐍 PythonInteractive WebAssemblyfrom statsmodels.graphics.tsaplots import plot_acf
plot_acf(
df["sales"].dropna(),
lags=40
)
plt.show()
Interpretation:
- Large correlation at lag 1 -> strong short-term dependence.
- Large correlation at lag 7 -> possible weekly seasonality.
- Repeating peaks -> possible seasonal structure.
- Slowly declining ACF -> possible non-stationarity or persistent dependence.
ACF is diagnostic evidence, not proof of a particular model.
33. Partial Autocorrelation
PACF measures the relationship between and after accounting for the effects of intermediate lags.
For example, PACF at lag 3 asks approximately:
How much direct relationship remains between today's value and the value three periods ago after accounting for lags 1 and 2?
Plot it:
🐍 PythonInteractive WebAssemblyfrom statsmodels.graphics.tsaplots import plot_pacf
plot_pacf(
df["sales"].dropna(),
lags=40,
method="ywm"
)
plt.show()
PACF is especially useful when thinking about autoregressive model orders.
34. ACF vs PACF
A useful intuition:
| Tool | Main Question |
|---|---|
| ACF | How related is the series to its past at different lags? |
| PACF | What relationship remains at a lag after accounting for shorter lags? |
In classical ARIMA modeling, ACF and PACF can provide clues about model structure.
We will use them much more heavily in Notebook 2.
35. Seasonal Decomposition
Decomposition attempts to separate a series into interpretable components.
A classical additive representation is:
where:
- = trend
- = seasonal component
- = remainder
STL stands for:
Seasonal-Trend decomposition using Loess.
It is a flexible decomposition method that can handle changing trend and seasonal behavior better than simple classical decomposition in many situations.
36. STL Decomposition
For daily data with weekly seasonality:
🐍 PythonInteractive WebAssemblyfrom statsmodels.tsa.seasonal import STL
series = df["sales"].dropna()
stl = STL(
series,
period=7
)
result = stl.fit()
result.plot()
plt.show()
The output typically contains:
- Observed
- Trend
- Seasonal
- Residual
37. Understanding STL Output
Observed#
The original series.
Trend#
The estimated long-term movement.
Seasonal#
The repeating pattern associated with the selected period.
Residual#
What remains after estimated trend and seasonality are removed.
Conceptually:
for an additive decomposition.
The residual should not automatically be assumed to be pure white noise. It must be diagnosed.
38. Choosing the STL Period
The period parameter is extremely important.
Examples:
🐍 PythonInteractive WebAssemblySTL(series, period=7)
for weekly seasonality in daily data.
🐍 PythonInteractive WebAssemblySTL(series, period=12)
for yearly seasonality in monthly data.
🐍 PythonInteractive WebAssemblySTL(series, period=24)
for daily seasonality in hourly data.
Choosing the wrong period can produce misleading decomposition results.
Domain knowledge and exploratory analysis should guide the choice.
39. Multiple Seasonalities
Real-world data can contain multiple seasonal patterns.
For example, hourly electricity demand may have:
- Daily seasonality
- Weekly seasonality
- Annual seasonality
A single classical seasonal period may not fully represent such data.
This is an important limitation to understand before moving into more advanced forecasting methods.
Notebook 3 will discuss machine-learning approaches for engineered multiple seasonal features, while Notebook 5 will discuss more advanced forecasting architectures.
40. Resampling
Resampling changes the frequency of a time series.
Examples:
Architecture & Data FlowDaily -> Weekly Daily -> Monthly Hourly -> Daily Minute -> Hourly
For example, daily sales to monthly sales:
🐍 PythonInteractive WebAssemblymonthly_sales = df["sales"].resample("ME").sum()
For average daily values:
🐍 PythonInteractive WebAssemblymonthly_average = df["sales"].resample("ME").mean()
The aggregation function must match the meaning of the variable.
41. Choosing the Correct Aggregation
For a flow variable such as sales:
🐍 PythonInteractive WebAssemblymonthly_sales = df["sales"].resample("ME").sum()
For a measurement such as temperature:
🐍 PythonInteractive WebAssemblymonthly_temperature = df["temperature"].resample("ME").mean()
For an ending balance:
🐍 PythonInteractive WebAssemblymonthly_balance = df["balance"].resample("ME").last()
For maximum daily demand:
🐍 PythonInteractive WebAssemblymonthly_peak = df["demand"].resample("ME").max()
Never assume mean() is always correct.
42. Downsampling vs Upsampling
Downsampling#
Moving to a lower-frequency representation.
Example:
›Hourly -> Daily
This generally requires aggregation.
Upsampling#
Moving to a higher-frequency representation.
Example:
›Daily -> Hourly
This creates new timestamps.
For example:
🐍 PythonInteractive WebAssemblyhourly = df["sales"].resample("h").asfreq()
The newly created observations may be missing and require appropriate treatment.
Upsampling does not magically create information that was never measured.
43. Time-Based Features
Before moving to machine learning, it is useful to understand calendar features.
🐍 PythonInteractive WebAssemblydf["day_of_week"] = df.index.dayofweek
df["day_of_month"] = df.index.day
df["month"] = df.index.month
df["quarter"] = df.index.quarter
df["year"] = df.index.year
For example:
🐍 PythonInteractive WebAssemblydf["is_weekend"] = df.index.dayofweek >= 5
These features can be valuable in machine-learning forecasting.
However, they should be created carefully so that features available at prediction time are not accidentally based on future information.
44. Time Series Outliers
An outlier is an observation that behaves unusually relative to the surrounding data.
Examples:
- Sudden sales spike
- Sensor malfunction
- Website traffic explosion
- Market crash
- Data-entry error
Not every outlier should be removed.
An extreme observation may represent a real event.
A useful investigation asks:
- Is the observation valid?
- Is it a measurement error?
- Was there a real-world event?
- Would the same event occur again?
- Should the forecasting model learn this behavior?
45. Structural Breaks
A structural break occurs when the underlying behavior of the series changes.
Example:
textBefore event: Sales around 100–120 After event: Sales around 180–220
Possible causes:
- New product launch
- Economic shock
- Policy change
- Business expansion
- Pandemic
- Change in measurement system
A structural break can make a previously useful model perform poorly because the data-generating process has changed.
46. Missing Data Strategies
Common strategies include:
Forward Fill#
🐍 PythonInteractive WebAssemblydf["sales"].ffill()
Useful when the previous value remains valid until a new observation arrives.
Backward Fill#
🐍 PythonInteractive WebAssemblydf["sales"].bfill()
Uses the next available value.
Interpolation#
🐍 PythonInteractive WebAssemblydf["sales"].interpolate()
Can be useful for smoothly varying measurements.
Model-Based Imputation#
A separate model estimates missing values.
The correct strategy depends on the domain.
Avoid filling missing values simply because a library function makes it easy.
47. Data Leakage in Time Series
Data leakage occurs when information unavailable at prediction time is used during training.
Example:
Architecture & Data FlowPrediction date: January 10 Allowed: January 1 -> January 9 Not allowed: January 11 -> January 20
A common mistake is calculating features using the full dataset before splitting it into train and test periods.
For example, a centered rolling statistic can use future observations.
Avoid:
🐍 PythonInteractive WebAssemblydf["rolling_mean"] = df["sales"].rolling(
window=7,
center=True
).mean()
for a forecasting feature unless the future values are genuinely available at prediction time.
For forecasting, causal features generally use past information:
🐍 PythonInteractive WebAssemblydf["lag_7"] = df["sales"].shift(7)
This topic becomes critical in Notebook 3 and Notebook 5.
48. A Practical EDA Checklist
Before modeling a time series, check:
Timestamp#
- Is the timestamp parsed correctly?
- Is it sorted?
- Are there duplicates?
- What is the frequency?
Completeness#
- Are dates missing?
- Are values missing?
- Are there gaps?
Distribution#
- Minimum
- Maximum
- Mean
- Standard deviation
- Extreme values
Temporal Structure#
- Trend
- Seasonality
- Cycles
- Autocorrelation
- Structural breaks
Statistical Properties#
- Is the mean stable?
- Is variance stable?
- Does differencing help?
- What does the ACF look like?
- What does the PACF look like?
Modeling Readiness#
- What is the forecasting target?
- What information is available at prediction time?
- What forecast horizon is required?
- What baseline should be used?
49. Building a Reusable EDA Function
For practical projects, it is useful to automate basic checks.
🐍 PythonInteractive WebAssemblydef time_series_summary(df, target):
print("Shape:", df.shape)
print("\nMissing values:")
print(df[target].isna().sum())
print("\nDuplicate timestamps:")
print(df.index.duplicated().sum())
print("\nDate range:")
print(df.index.min(), "to", df.index.max())
print("\nInferred frequency:")
print(pd.infer_freq(df.index))
print("\nDescriptive statistics:")
print(df[target].describe())
Use it:
🐍 PythonInteractive WebAssemblytime_series_summary(df, "sales")
This should be considered a starting point, not a replacement for investigation.
50. Stationarity Test: Augmented Dickey-Fuller
Visual inspection is useful, but statistical tests can provide additional evidence.
The Augmented Dickey-Fuller (ADF) test evaluates a null hypothesis associated with a unit root.
In simplified terms:
- Null hypothesis: the series has a unit root / is non-stationary.
- Alternative hypothesis: the series is stationary.
Example:
🐍 PythonInteractive WebAssemblyfrom statsmodels.tsa.stattools import adfuller
result = adfuller(df["sales"].dropna())
print("ADF Statistic:", result[0])
print("p-value:", result[1])
A commonly used interpretation is:
Architecture & Data Flowp-value < 0.05 -> reject the null hypothesis -> evidence supporting stationarity p-value >= 0.05 -> insufficient evidence to reject the null -> investigate possible non-stationarity
The threshold is not a universal law, and statistical tests should be interpreted alongside plots and domain knowledge.
51. ADF Test After Differencing
🐍 PythonInteractive WebAssemblysales_diff = df["sales"].diff().dropna()
result = adfuller(sales_diff)
print("ADF Statistic:", result[0])
print("p-value:", result[1])
If differencing substantially changes the evidence for stationarity, that is useful information for later ARIMA modeling.
52. White Noise
A white-noise series contains no predictable temporal structure in its mean.
A simplified white-noise process can be written as:
where:
and observations are uncorrelated across time.
A useful intuition:
Architecture & Data FlowPast value | X | Future value
Knowing the past does not provide useful linear predictive information about the future.
For a well-fitted forecasting model, residuals should ideally behave approximately like white noise.
This will become especially important in Notebook 2.
53. What We Should Expect From Good Residuals
After fitting a model, we generally want residuals with:
- Approximately zero mean
- No obvious trend
- No obvious seasonality
- Little remaining autocorrelation
- Stable variance where appropriate
Residual diagnostics answer:
Did the model successfully capture the predictable structure?
If strong autocorrelation remains, the model may have missed information.
54. A Complete Mini EDA Pipeline
A compact workflow might look like:
🐍 PythonInteractive WebAssembly# 1. Parse timestamp
df["date"] = pd.to_datetime(df["date"])
# 2. Sort
df = df.sort_values("date")
# 3. Set index
df = df.set_index("date")
# 4. Inspect frequency
print(pd.infer_freq(df.index))
# 5. Check missing values
print(df.isna().sum())
# 6. Plot original series
df["sales"].plot(figsize=(14, 5))
plt.show()
# 7. Rolling statistics
rolling_mean = df["sales"].rolling(7).mean()
rolling_std = df["sales"].rolling(7).std()
# 8. ACF
plot_acf(df["sales"].dropna(), lags=40)
plt.show()
# 9. PACF
plot_pacf(df["sales"].dropna(), lags=40, method="ywm")
plt.show()
# 10. STL
stl = STL(df["sales"].dropna(), period=7)
result = stl.fit()
result.plot()
plt.show()
# 11. Stationarity test
adf_result = adfuller(df["sales"].dropna())
print("ADF Statistic:", adf_result[0])
print("p-value:", adf_result[1])
55. Common Beginner Mistakes
Mistake 1: Random Train/Test Splitting#
Bad for ordinary forecasting scenarios:
🐍 PythonInteractive WebAssemblytrain_test_split(X, y)
because observations from the future may enter the training set.
Use chronological splits.
Mistake 2: Ignoring Missing Dates#
A dataset can have no NaN values but still have missing timestamps.
Always check the expected frequency.
Mistake 3: Treating Every Spike as an Outlier#
A spike may represent a genuine business event.
Mistake 4: Assuming Every Time Series Is Stationary#
Many real-world series contain trends, seasonality, changing variance, or structural breaks.
Mistake 5: Blindly Differencing#
Differencing can remove useful information and can be overused.
Mistake 6: Using Future Information in Features#
Forecasting features must respect the information available at prediction time.
Mistake 7: Choosing Seasonal Period Arbitrarily#
The period should be justified by data frequency and domain knowledge.
56. Beginner Mental Model
Think about time series in this order:
Architecture & Data FlowTIME | v What happened? | v TREND | v Does something repeat? | v SEASONALITY | v Does it move in irregular long cycles? | v CYCLICITY | v What remains? | v NOISE
Then ask:
Architecture & Data FlowIs the statistical behavior stable? | Yes | v Model structure OR No | v Transform / difference / engineer features / choose an appropriate model
57. Advanced Perspective
For advanced practitioners, the important idea is that a time series is not merely a column containing dates and numbers.
It is an observed realization of an underlying stochastic process.
We can write:
where:
- is the target at time .
- Past values may influence the present.
- represents external variables.
- represents unpredictable variation.
Different forecasting families make different assumptions about this relationship.
Statistical Models#
ARIMA-style models explicitly model temporal dependence and transformations.
Machine Learning#
Tree-based models learn relationships from engineered temporal features.
Deep Learning#
RNNs, LSTMs, GRUs, and Transformers can learn complex sequential representations.
The next notebooks will build these approaches progressively.
58. What We Have Learned
In this notebook we learned:
- Time series observations are ordered in time.
- Time order creates dependencies that ordinary tabular ML does not necessarily have.
- Time series can be univariate or multivariate.
- Trend describes long-term movement.
- Seasonality describes repeating patterns at known frequencies.
- Cyclicity describes less regular long-term fluctuations.
- Noise represents unpredictable variation.
- Stationarity describes stable statistical behavior.
- Rolling statistics help investigate changing mean and variance.
- ACF measures relationships across lags.
- PACF examines lag relationships after accounting for intermediate lags.
- STL separates a series into trend, seasonal, and residual components.
- Resampling changes the temporal frequency.
- Missing timestamps and missing values are different problems.
- Data leakage is especially dangerous in forecasting.
- Residuals should be investigated rather than automatically treated as noise.
59. Exercises
Exercise 1: Basic Inspection#
Given a DataFrame containing:
›date sales
perform:
- Datetime conversion.
- Sorting.
- Indexing.
- Frequency detection.
- Missing-value detection.
- Duplicate timestamp detection.
Exercise 2: Visualization#
Create:
- Full-series plot.
- First 90-day plot.
- 7-day rolling mean.
- 30-day rolling mean.
Explain what each visualization tells you.
Exercise 3: Stationarity#
- Plot the original series.
- Run the ADF test.
- Difference the series.
- Plot the differenced series.
- Run ADF again.
- Compare the results.
Exercise 4: ACF and PACF#
Generate:
🐍 PythonInteractive WebAssemblyplot_acf(...) plot_pacf(...)
for:
- Original series
- Differenced series
Explain how the patterns changed.
Exercise 5: STL#
Perform STL decomposition using the appropriate seasonal period.
Answer:
- Is there a visible trend?
- Is seasonality strong?
- Is the seasonal pattern stable?
- Does the residual contain obvious structure?
60. Mini Project: Time Series EDA Report
Choose a real-world time series dataset such as:
- Retail sales
- Electricity demand
- Temperature
- Website traffic
- Stock prices
- Transportation demand
Your report should contain:
Section A: Data Understanding#
- Dataset description
- Time range
- Frequency
- Target variable
- Number of observations
Section B: Data Quality#
- Missing timestamps
- Missing values
- Duplicate timestamps
- Outliers
- Irregular intervals
Section C: Visualization#
- Original series
- Rolling mean
- Rolling standard deviation
Section D: Temporal Structure#
- Trend
- Seasonality
- Cyclic behavior
- ACF
- PACF
Section E: Decomposition#
Perform STL and interpret:
- Trend
- Seasonal
- Residual
Section F: Stationarity#
- ADF test
- Differenced series if appropriate
- Interpretation
Section G: Modeling Recommendation#
Based on your EDA, explain whether you would initially consider:
- ARIMA/SARIMA
- Tree-based ML
- Deep learning
- A combination of approaches
Do not train the final forecasting model yet. The purpose of this project is to develop the analytical reasoning required before modeling.
61. Preparation for Notebook 2
The next notebook will move from exploration to statistical forecasting.
We will build the concepts in this order:
Architecture & Data FlowTime Series EDA | v Stationarity | v Differencing | v White Noise | v AR | v MA | v ARMA | v ARIMA | v SARIMA | v Residual Diagnostics | v Forecasting
The goal is not simply to memorize ARIMA syntax.
The goal is to understand:
Why a particular statistical model is appropriate for a particular temporal structure.
62. Final Takeaway
Time series forecasting starts long before model training.
A strong practitioner first asks:
- What exactly is being measured?
- How is time represented?
- Is the frequency regular?
- Are there missing dates?
- Is there a trend?
- Is there seasonality?
- Is there autocorrelation?
- Is the series stationary?
- Are there structural breaks or unusual events?
- What information will actually be available at prediction time?
Only after answering these questions should we choose a forecasting model.
The central principle of this course is:
textUnderstand the temporal structure ↓ Prepare the data correctly ↓ Choose the appropriate model ↓ Validate chronologically ↓ Diagnose errors ↓ Deploy and monitor
This foundation will support every forecasting approach covered in the remaining notebooks.
Time Series Stationarity & EDA Checkpoint
Finished studying this notebook?
Mark this guide as completed to update your course progress roadmap.