README.md

# 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`:

```elixir
def deps do
  [
    {:ljung_box, "~> 0.1.0"}
  ]
end
```

## Usage

### Full test

```elixir
# 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.")
end
```

### Q-statistic only

```elixir
LjungBox.statistic([1, 2, 3, 4], 2)
# => 1.58
```

### Autocorrelations

```elixir
# 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.