What is stationarity in a time series, and how do you test for it?
A stationary series has a time-invariant mean, variance, and autocovariance by lag. The ADF test uses a unit-root null: a small p-value supports stationarity, while a large p-value only means failure to reject non-stationarity; I would confirm the result with plots, a complementary test such as KPSS, and the correct trend or seasonal specification.
How to think about it
Stationarity means that the statistical behaviour of a time series does not change when the clock moves: its mean and variance stay stable, and the relationship between two observations depends on the gap between them, not their calendar dates. I would usually test for a unit root with the Augmented Dickey-Fuller test, but I would not treat one p-value as proof; the trend specification, structural breaks, seasonality, and volatility still matter.
Why stationarity matters
Imagine recording a stock price every trading day. If the price is around $100 in one year and around $250 in another, a model trained on the first period is learning from a different environment than the one it must predict. Its estimated averages and relationships may no longer apply.
A weakly, or covariance, stationary series satisfies three conditions:
- Its expected value, or mean, is constant:
E[Y_t] = μ. - Its variance is constant:
Var(Y_t) = σ². - Its autocovariance, meaning how two observations move together, depends only on their lag:
Cov(Y_t, Y_{t+k}) = γ(k), not on the timet.
The last condition is easy to underestimate. If observations ten days apart are correlated in January but unrelated in July, the series is not stationary even if its overall mean looks flat.
Stationarity does not mean that the line on a chart is flat. A stationary series can jump sharply, oscillate, and have strong autocorrelation. It means that the same probabilistic rules continue to generate those movements.
Strict stationarity is the stronger definition: the entire joint distribution is unchanged by shifting time. Interview questions about ARIMA and ADF normally mean weak stationarity, because those models work with means, variances, and autocovariances.
The reason classical time-series models care is simple. An ARMA model estimates relationships such as “today is partly related to yesterday.” Those relationships are useful only if they remain reasonably stable. ARIMA handles a non-stationary level by differencing it first; its ARMA component is then fitted to the transformed, ideally stationary series. A standard stable VAR also generally assumes stationary variables, while cointegrated variables may call for a VECM instead.
The mechanism: what a unit root does
Start with an autoregressive model:
Y_t = c + ρY_{t-1} + ε_t
Here, ε_t is a new shock at time t. When the absolute value of ρ is below one, the effect of an old shock fades. For example, with ρ = 0.8, a one-dollar shock has an expected effect of about 11 cents after ten periods because 0.8^10 is about 0.107.
When ρ = 1, the model has a unit root, meaning the previous value is carried forward with no decay:
Y_t = c + Y_{t-1} + ε_t
That is a random walk, possibly with drift. A shock does not fade. It permanently shifts the future path. If the daily shock has a standard deviation of $1, the standard deviation of the accumulated random-walk noise after 400 independent days is about $20, not $1. The variance grows with time, which violates stationarity.
The ADF test rewrites this model in differences:
ΔY_t = α + βt + γY_{t-1} + δ_1ΔY_{t-1} + ... + δ_pΔY_{t-p} + ε_t
The important relationship is γ = ρ - 1.
- The null hypothesis is
γ = 0: the series has a unit root. - The alternative is
γ < 0: the series is stationary around the deterministic terms included in the regression. - The extra lagged differences are why the test is called augmented. They help account for autocorrelation in the errors, rather than pretending each residual is independent.
The ADF statistic does not use the ordinary Student t distribution. It is compared with Dickey-Fuller critical values, which is why the testing library calculates the appropriate p-value.
A concrete example
Suppose I have about 500 daily closing prices for Northstar, a fictional stock. The price begins at $100, reaches $124 by the end of the sample, and repeatedly wanders rather than returning to one stable level. That pattern is compatible with a random walk, although a chart alone cannot prove it.
I would test both the log price and the daily log return. A log return is log(P_t) - log(P_{t-1}); it is approximately the percentage change for small movements and turns multiplication across days into addition.
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller
df = pd.read_csv("northstar.csv", parse_dates=["date"])
price = (
df.sort_values("date")
.set_index("date")["close"]
.dropna()
)
log_price = np.log(price)
log_return = log_price.diff().dropna()
for label, series in [
("log price", log_price),
("daily log return", log_return),
]:
statistic, p_value, used_lag, nobs, critical_values, _ = adfuller(
series,
regression="c",
autolag="AIC",
)
print(
f"{label}: ADF={statistic:.3f}, "
f"p={p_value:.4f}, lags={used_lag}"
)
regression="c" includes a constant but no deterministic time trend. autolag="AIC" chooses the number of lagged differences using the Akaike information criterion.
Suppose, purely as an example, the log-price test returns p = 0.42 and the return test returns p = 0.01. At a five-percent threshold, I would fail to reject the unit-root null for the log price but reject it for the return. That supports treating returns as stationary in the mean, not declaring the stock permanently solved.
Common trap: a large ADF p-value does not prove stationarity, and it does not prove anything in the opposite direction with certainty. It means the data did not provide enough evidence to reject the unit-root null. Conversely, a small p-value is evidence against a unit root under the chosen model specification; it is not a guarantee that variance, seasonality, and every other feature are stable.
Choosing the test specification
The deterministic terms must match the question:
ctests for a unit root with a constant.ctadds a deterministic linear trend.nomits both.
If a series is stationary around a linear trend, the ct version may reject the unit root. That does not mean the raw series has a constant mean; it means the deviations around its deterministic trend are stable. I would not run all three specifications and select the nicest p-value. I would choose based on the data-generating story and then report that choice.
Before testing, I would inspect the level series, rolling mean, rolling variance, and the autocorrelation function, or ACF, which measures correlation with the series’ own past at each lag. A test is evidence, not a substitute for understanding the data.
What I would do if the series is not stationary
The transformation depends on the reason for non-stationarity.
| Observed problem | Plausible response | Important caution |
|---|---|---|
| Random-walk level | First difference, Y_t - Y_{t-1} | Removes level information |
| Positive series with percentage growth | Log transform, then difference | Does not automatically fix changing volatility |
| Weekly or annual seasonality | Seasonal difference, such as Y_t - Y_{t-7} for daily weekly data | Seasonal terms or indicators may be better |
| Deterministic trend | Model or remove the trend; sometimes difference | Differencing can change the error structure |
| Structural break | Model the break, split regimes, or use a break-aware method | Blind differencing can hide the real problem |
I use the smallest transformation that makes the process suitable for the model. Differencing a stationary series is over-differencing. It can create strong negative lag-one autocorrelation, make forecasts unnecessarily noisy, and discard useful level information. A second difference is not the automatic sequel to a failed first difference; I would first ask whether the problem is seasonality, a break, or a poor trend specification.
There is also a distinction between mean stationarity and volatility behaviour. A return series may pass ADF because its mean is stable while still showing volatility clustering. That series may need a volatility model such as GARCH, or at least volatility-aware intervals. ADF does not test constant conditional variance.
Finally, do not independently difference related non-stationary series without checking cointegration. Two prices can each wander, while their spread remains stable because of a long-run relationship. Differencing both may produce a stationary model but throw away that relationship; a VECM can represent both short-run changes and long-run equilibrium.
What they’ll ask next
“Does an ADF p-value above 0.05 prove the series is non-stationary?”
No. It means I failed to reject the unit-root null. The test can have low power when the sample is short or the process is close to a unit root. I would inspect the data, check the specification, and use a complementary test.
“What is the complementary test?”
The KPSS test reverses the setup: its null is stationarity, either around a level or around a trend depending on the version. ADF failing to reject a unit root together with KPSS rejecting stationarity is stronger evidence of non-stationarity. Mixed results mean the evidence is inconclusive, not that one test gets a ceremonial victory.
“Would you difference until ADF passes?”
No. I would identify the source first, apply the minimum ordinary or seasonal differencing required, and check residual diagnostics and forecast performance. I would also preserve cointegration when multiple series share a long-run relationship.
Say this in the interview
“Stationarity means constant mean, variance, and lag-based dependence; ADF tests the unit-root null, so a small p-value supports stationarity, while a large p-value only means I could not reject non-stationarity, and I would confirm the result with diagnostics and an appropriate transformation.”