LjungBox
An Elixir implementation of the Ljung-Box test for autocorrelation in time series residuals.
Overview
The Ljung-Box test checks whether the residuals of a time series model (or any sequence of values) are independently distributed — i.e., whether they exhibit no autocorrelation up to a chosen number of lags.
Null hypothesis H₀: The data are independently distributed (white noise).
Alternative H₁: The data exhibit serial correlation at one or more lags.
Method
The Q-statistic is:
$$Q = n(n+2)\sum_{k=1}^{h}\frac{\hat{\rho}_k^2}{n-k}$$
| Symbol | Meaning |
|---|---|
| $n$ | Number of observations |
| $h$ | Number of lags tested |
| $\hat{\rho}_k$ | Sample autocorrelation at lag $k$ |
Under H₀, $Q$ follows a chi-squared distribution with $h$ degrees of freedom. When testing ARMA(p, q) residuals, use $h - p - q$ degrees of freedom instead and adjust the lags count accordingly.
The p-value is computed using the regularised lower incomplete gamma function (Lanczos + Lentz continued-fraction method, pure Elixir, no FFI).
Interpretation of results
| p-value | Conclusion |
|---|---|
| < 0.05 | Reject H₀ — significant autocorrelation is present |
| ≥ 0.05 | Fail to reject H₀ — series is consistent with white noise |
Practical tips:
- For ARIMA residuals the conventional lag choices are $h = \lfloor\ln n\rfloor$ or $h = 10$ (for non-seasonal data).
- Use a higher lag count to detect slowly-decaying autocorrelation patterns.
- The test has low power for very short series (n < 30); interpret with caution.
- A series with structural breaks or heteroscedasticity can produce misleading results; combine with other diagnostics when in doubt.
Installation
Add ljung_box to your list of dependencies in mix.exs:
def deps do
[
{:ljung_box, "~> 0.1.0"}
]
endUsage
Full test
# residuals from an ARIMA model
residuals = [0.12, -0.05, 0.03, 0.21, -0.18, 0.07, -0.02, 0.11, ...]
result = LjungBox.test(residuals, 10)
# %{statistic: 8.43, p_value: 0.587, lags: 10, n: 200}
if result.p_value < 0.05 do
IO.puts("Residuals show significant autocorrelation — revisit the model.")
else
IO.puts("Residuals are consistent with white noise.")
endQ-statistic only
LjungBox.statistic([1, 2, 3, 4], 2)
# => 1.58Autocorrelations
# Individual autocorrelation at a specific lag
LjungBox.autocorrelation([1, 2, 3, 4], 1)
# => 0.25
# All autocorrelations up to max_lag
LjungBox.autocorrelations([1, 2, 3, 4, 5, 6], 4)
# => [rho_1, rho_2, rho_3, rho_4]API
| Function | Description |
|---|---|
LjungBox.test(series, lags \\ 10) |
Full test — returns %{statistic, p_value, lags, n} |
LjungBox.statistic(series, lags) | Q-statistic only |
LjungBox.autocorrelation(series, lag) | Sample autocorrelation at a single lag |
LjungBox.autocorrelations(series, max_lag) | Sample autocorrelations at lags 1..max_lag |
References
- Ljung, G. M. & Box, G. E. P. (1978). On a measure of lack of fit in time series models. Biometrika, 65(2), 297–303.
- Box, G. E. P. & Pierce, D. A. (1970). Distribution of residual autocorrelations in autoregressive-integrated moving average time series models. Journal of the American Statistical Association, 65(332), 1509–1526.