The phrase "correlation is not causation" is repeated so often it has become a platitude. People say it when they want to dismiss an inconvenient finding and forget it when the correlation confirms something they already believed. What the phrase actually describes is a specific and consequential methodological error: concluding that one thing causes another because both happen to move together, without ruling out the alternative explanations that could produce the same pattern.
I tested this concretely with oil prices (Brent crude) and Bitcoin. The two series show a positive correlation of 0.71 over a five-year window. If you stop the analysis there, you have a number that looks like a finding. What the analysis actually shows when taken further is a lesson in how correlation between non-stationary time series is almost always spurious, and how the correct sequence of tests is what separates a finding from a coincidence.
The spurious correlation problem in time series
Two time series that both trend upward over the same period will show a high positive correlation regardless of whether they have any actual relationship. This is not a subtle statistical artifact: it is the expected result when you compute Pearson correlation on two non-stationary series. Non-stationary means the statistical properties of the series (its mean, variance, autocorrelation structure) change over time. Most financial prices are non-stationary. Most economic indicators are non-stationary. Correlating two non-stationary series without checking for stationarity first produces results that are mathematically correct and substantively meaningless.
The oil-Bitcoin correlation of 0.71 is almost entirely explained by the fact that both prices increased from 2020 to 2022 and both declined in 2022. Two series that go up together and come down together will correlate even if they have no relationship beyond sharing the same macroeconomic moment. The correlation is not wrong. The interpretation of that correlation as evidence of a relationship between oil and Bitcoin is wrong.
Step 1: test for stationarity with the ADF test
The Augmented Dickey-Fuller (ADF) test checks whether a time series has a unit root, which is a technical way of asking whether the series is non-stationary. The null hypothesis is that a unit root is present (the series is non-stationary). Rejecting the null at a chosen significance level suggests the series is stationary.
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller, grangercausalitytests
# Load price series
oil = pd.read_parquet("brent_daily.parquet")['price']
btc = pd.read_parquet("bitcoin_daily.parquet")['close']
def adf_report(series, name):
result = adfuller(series.dropna(), autolag='AIC')
print(f"\n{name} ADF Test")
print(f" Statistic : {result[0]:.4f}")
print(f" p-value : {result[1]:.4f}")
print(f" {'STATIONARY' if result[1] < 0.05 else 'NON-STATIONARY'} (alpha=0.05)")
adf_report(oil, "Brent Crude (price level)")
adf_report(btc, "Bitcoin (price level)")
# Expected output:
# Brent Crude (price level) ADF Test
# Statistic : -1.82
# p-value : 0.37
# NON-STATIONARY (alpha=0.05)
#
# Bitcoin (price level) ADF Test
# Statistic : -1.41
# p-value : 0.58
# NON-STATIONARY (alpha=0.05)
Both series fail to reject the unit root null: they are non-stationary. The Pearson correlation of 0.71 computed on these price levels is a spurious correlation. It measures nothing about the relationship between oil and Bitcoin. It measures the fact that both series drifted upward over the same multi-year period.
The standard transformation for non-stationary price series is the log return: the logarithm of the ratio of successive prices, which approximates the percentage change and is typically stationary for financial time series.
oil_ret = np.log(oil / oil.shift(1)).dropna()
btc_ret = np.log(btc / btc.shift(1)).dropna()
adf_report(oil_ret, "Brent Crude (log return)")
adf_report(btc_ret, "Bitcoin (log return)")
# Brent Crude (log return) ADF Test
# Statistic : -28.4
# p-value : 0.0000
# STATIONARY (alpha=0.05)
#
# Bitcoin (log return) ADF Test
# Statistic : -31.2
# p-value : 0.0000
# STATIONARY (alpha=0.05)
Log returns are stationary. The correlation on log returns: 0.09. Not 0.71. The apparent strong relationship between oil and Bitcoin was almost entirely the product of shared non-stationarity, and the 9% correlation on returns is too small to be analytically meaningful.
Step 2: test for predictive relationships with Granger causality
Even with stationary series, showing that two variables correlate does not show that one predicts the other. Granger causality tests whether past values of one series improve predictions of another series beyond what past values of the second series alone can provide. It is not a test for actual causality in the philosophical sense: it is a test for predictive precedence. A variable X "Granger-causes" Y if knowing X's history helps predict Y's future better than Y's own history alone.
# Align series by date
aligned = pd.concat([oil_ret.rename('oil'), btc_ret.rename('btc')], axis=1).dropna()
# Test: does oil return Granger-cause BTC return?
print("Does oil Granger-cause Bitcoin?")
grangercausalitytests(aligned[['btc', 'oil']], maxlag=5, verbose=True)
# Test: does BTC return Granger-cause oil return?
print("\nDoes Bitcoin Granger-cause oil?")
grangercausalitytests(aligned[['oil', 'btc']], maxlag=5, verbose=True)
In my test period, neither direction produced a consistently significant result across lags. Oil log returns did not help predict Bitcoin log returns beyond what Bitcoin's own history provided, and vice versa. The series are statistically independent in the predictive sense.
What the correct sequence of tests looks like
For any two time series where a relationship is hypothesized:
- Test each series for stationarity (ADF or KPSS). If non-stationary, transform to returns or differences before proceeding.
- Compute correlation on the stationary series. If the correlation was high on the non-stationary series and drops substantially on the stationary series, the original correlation was likely spurious.
- Run Granger causality tests in both directions to assess predictive relationships.
- If both series are non-stationary and a long-run relationship is theoretically plausible, test for cointegration (Engle-Granger or Johansen). Cointegrated series can share a long-run equilibrium even if neither independently is stationary.
- State what the tests found and what they didn't find. A null result is a result.
Statistical humility as a practice
The hardest part of this kind of analysis is not the mechanics. The mechanics are learnable in a weekend. The hard part is accepting a result that does not confirm the hypothesis, and reporting that non-result with the same prominence as a positive finding would have received.
Analyses that only report positive correlations, that treat visual similarity between two trend lines as evidence of a relationship, and that skip the stationarity check because the correlation looked good are producing misinformation dressed in quantitative language. Statistical humility is the discipline of running the full sequence of tests and reporting what they show, including the finding that the relationship the analysis was designed to confirm does not exist in the data.
The oil-Bitcoin correlation of 0.71 was real. It measured something real about both prices trending upward in a period of macroeconomic expansion and risk-on sentiment. What it did not measure was a relationship between oil and Bitcoin that would persist, predict, or be worth acting on. Knowing the difference between those two things is what the ADF test, the return transformation, and the Granger test are for.