Atlas
statminds
Time Series (Diagnostic Model)The underlying model family class (e.g. GLM, linear model, categorical matrix, log-linear).Parametric ReferenceStatistical methods that assume a specific probability distribution family (typically normal).12-stage workflow

Ljung-Box Test

The engine for Residual Integrity. This 'Portmanteau' test audits the entire cluster of autocorrelations in your residuals, revealing if your model has successfully extracted all the 'Signal' or if patterns remain hidden in the noise.

Model familyTime Series (Diagnostic Model)
Hypothesisdiagnostic
AliasesBox-Ljung Q-Test · Portmanteau Test · Residual White-Noise Audit
G1
Residual Signal Audit
Determine if the 'Errors' of your model are truly random (White Noise) or contain unmodeled information.
G2
Autocorrelation Shield
Protect your p-values and forecasts by ensuring no serial correlation persists across a vector of lags.
G3
Forensic Model Validation
Quantify the 'Goodness-of-Fit' for ARIMA and VAR models by proving the absence of temporal patterning.
Visual Overview Dashboard
1

What is it?

Ljung-Box Test analyzes sequences of data points ordered chronologically over time to extract patterns, model trends, and make forecasts.

The engine for Residual Integrity. This 'Portmanteau' test audits the entire cluster of autocorrelations in your residuals, revealing if your model has successfully extracted all the 'Signal' or if patterns remain hidden in the noise.

2

Goals & Indications

  • Residual Signal Audit: Determine if the 'Errors' of your model are truly random (White Noise) or contain unmodeled information.
  • Autocorrelation Shield: Protect your p-values and forecasts by ensuring no serial correlation persists across a vector of lags.
  • Forensic Model Validation: Quantify the 'Goodness-of-Fit' for ARIMA and VAR models by proving the absence of temporal patterning.
3

Core Idea Diagram

+1.96/√N-1.96/√NWhite Noise Residuals (Q < Chi-Sq Crit)
4

Claims tested

H₀: H₀: No serial correlation in residuals up to lag h (residuals are white noise)
Hₐ: Hₐ: Serial correlation present at one or more lags up to h (model inadequate)
5

How it works

  1. Select maximum lag parameter h for autocorrelation check.
  2. Compute sample autocorrelations up to lag h for the residual series.
  3. Compute Ljung-Box Q statistic weighting higher lags appropriately.
  4. Evaluate Q statistic against Chi-square distribution with h degrees of freedom.
6

Assumptions

Large sample size: Need sufficient observations for asymptotic χ² approximation
Testing residuals from correctly specified model: Model structure must be appropriate for degrees of freedom adjustment
Appropriate lag length selection: Choose h to balance power and degrees of freedom
7

Important Note

CRITICAL INTERPRETATION: p > 0.05 is GOOD (white noise, model adequate). p < 0.05 is BAD (autocorrelation remains, need better model). This is opposite of typical hypothesis tests! The Ljung-Box test is primarily a diagnostic tool for model validation, not a standalone analysis. It tests whether residuals from a fitted time series model exhibit autocorrelation. If p>0.05, we fail to reject H₀ (residuals are white noise, model captured all temporal structure). If p<0.05, we reject H₀ (residuals autocorrelated, model inadequate). Key distinction from Durbin-Watson: Ljung-Box tests autocorrelation at multiple lags jointly (omnibus test), DW tests only first-order. Key distinction from Breusch-Godfrey: Ljung-Box uses autocorrelations directly, BG uses regression framework (more flexible for regression models). The test statistic follows χ² distribution with degrees of freedom adjusted for model parameters (h for raw series, h-p-q for ARIMA(p,d,q) residuals).

8

Worked Example

Lag (h)Q-StatdfCrit Valuep-value
Lag 108.421018.310.587
Interactive Sandbox

Ljung-Box Serial Correlation Test

Test for independence in a time series. Increase ρ to introduce serial dependence. Observe how the Ljung-Box Q statistic jumps above critical thresholds.

Autocorrelation (AR ρ)0.40
ρ = 0.00 represents pure white noise residuals.
Maximum Lags (h)10
Sample Size (N)100
Test Outputs
Ljung-Box Q-statistic: 60.188
Degrees of Freedom (h): 10
p-value: 0.00000Significant (Correlation)
Sample Autocorrelation Function (ACF) with 95% Confidence Bounds
+0.20-0.20L1L2L3L4L5L6L7L8L9L10
The 12-Stage Precision Workflow
01White Noise Null
Hypotheses
We test the null of 'Randomness' (all autocorrelations up to lag m = 0) against the discovery of a systematic temporal ghost.
02Residual Continuity
Assumptions
The only requirement: the residuals must come from a fitted temporal model—the bedrock of portmanteau authority.
03Lag-m Selection
Diagnostics
Determining the 'Depth of the Audit' (m)—ideally choosing ln(N) or similar benchmarks to ensure the test has sufficient reach.
04focus
Testing the residuals of a FlowMotion download forecast—if Ljung-Box fails, the forecast is biased and the model requires more lags.
05Durbin-Watson Pivot
Alternatives
Knowing when to use Durbin-Watson if you only care about the very first lag (AR1), or Breusch-Godfrey for higher-order regression residuals.
06The Q-Strike
Significance
Calculating the weighted sum of squared autocorrelations—the definitive Q-statistic strike on residual integrity.
07Persistence Magnitude
Effect Size
Interpreting the Q-statistic directly: a massive Q relative to its degrees of freedom indicates a model that is 'Blind' to the data's rhythm.
08Temporal History
Sample Size
Ensuring a minimum of 50 residuals to provide enough variance for the Q-math to distinguish signal from chance alignment.
09The Integrity Statement
Reporting
Reporting the Q-stat, df, and p-value: 'Residuals were white noise (Q = X.XX, p = .XXX), confirming model adequacy.'
10Box.test Logic
Software
Executing 'Box.test(type = "Ljung-Box")'—ensuring the 'fitdf' parameter accounts for the (p+q) parameters used in the model.
11focus
The fatal error of ignoring the model's degrees of freedom—which dangerously overestimates the randomness of residuals.
12focus
Tracing the model back to Ljung and Box (1978) and the foundational refinement of the original Box-Pierce Q-test.
01Hypothesis test logic

Hypotheses

Pragmatic null and alternative hypotheses defined in mathematical notation.

A hypothesis is a question sharpened to a point. Ambiguity is the enemy of inference.
Logic Core
Null · H₀

H₀: No serial correlation in residuals up to lag h (residuals are white noise)

Alternative · Hₐ

Hₐ: Serial correlation present at one or more lags up to h (model inadequate)

Why it matters diagnostic

CRITICAL INTERPRETATION: p > 0.05 is GOOD (white noise, model adequate). p < 0.05 is BAD (autocorrelation remains, need better model). This is opposite of typical hypothesis tests! The Ljung-Box test is primarily a diagnostic tool for model validation, not a standalone analysis. It tests whether residuals from a fitted time series model exhibit autocorrelation. If p>0.05, we fail to reject H₀ (residuals are white noise, model captured all temporal structure). If p<0.05, we reject H₀ (residuals autocorrelated, model inadequate). Key distinction from Durbin-Watson: Ljung-Box tests autocorrelation at multiple lags jointly (omnibus test), DW tests only first-order. Key distinction from Breusch-Godfrey: Ljung-Box uses autocorrelations directly, BG uses regression framework (more flexible for regression models). The test statistic follows χ² distribution with degrees of freedom adjusted for model parameters (h for raw series, h-p-q for ARIMA(p,d,q) residuals).

02Model diagnostics

Assumptions

The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.

Stationarity is the prerequisite of prediction. You cannot forecast a system that is fundamentally changing its rules.
Integrity Shield
6
Assumptions
3
Critical / High Severity
How to check
Quick
Check sample size n. For raw series: n≥50 minimum, n≥100 preferred. For model residuals: n should be at least 10 times the total number of parameters plus lag length (n ≥ 10×(p+q+h)). Small samples: test statistic distribution deviates from χ², p-values unreliable
Rigorous
Calculate effective sample size: for raw series n_eff = n. For ARIMA residuals: n_eff = n - d (lose d observations to differencing). For regression residuals: n_eff = n - k where k=predictors. Verify n_eff > 50 minimum. For reliable inference: n_eff ≥ 100. Check ratio n/h: should be at least 5-10. Small sample correction: use finite-sample adjusted critical values or bootstrap test
If violated
If n<50: (1) Collect more data if possible. (2) Use fewer lags (reduce h) to increase power. (3) Use exact finite-sample distribution (if available) instead of asymptotic χ². (4) Bootstrap the test: resample residuals, compute Q statistic, build empirical null distribution. (5) Use simpler visual diagnostics (ACF plot alone). (6) Report results with caution, acknowledge limitation. For n<30: Ljung-Box unreliable, rely primarily on ACF plot visual inspection
runs test
How to check
Quick
If testing ARIMA residuals: verify you used correct (p,d,q) orders when computing degrees of freedom. df = h - p - q for ARIMA(p,d,q), not just h. If testing regression residuals: no adjustment needed (use df = h). If testing raw series: use df = h. Common error: using df=h for ARIMA residuals (too many df, test too conservative)
Rigorous
For ARIMA(p,d,q) residuals: degrees of freedom = h - p - q. For SARIMA(p,d,q)(P,D,Q)_s: df = h - p - q - P - Q (adjust for all AR/MA terms). For regression residuals (OLS): df = h (no adjustment). For VAR(p) residuals: df = h - p. Verify model was fitted: don't test raw series with ARIMA degrees of freedom. If model selection was data-driven (auto.arima): consider additional df penalty, though standard practice doesn't adjust further. Check fitdf parameter in Box.test() matches model parameters
If violated
If wrong degrees of freedom: (1) Recompute test with correct df adjustment. Too many df (didn't adjust for p,q): test too liberal (rejects H₀ too often), underestimates p-value. Too few df (over-adjusted): test too conservative, overestimates p-value. (2) For ARIMA: use fitdf = p+q in Box.test(). (3) For SARIMA: use fitdf = p+q+P+Q. (4) If testing raw series: use fitdf = 0. (5) Document which model residuals are being tested. If model misspecified: Ljung-Box will reject even with correct df (this is desired behavior indicating inadequate model)
How to check
Quick
Common rules: (1) For raw series: h = min(20, n/5). (2) For ARIMA residuals: h = 10-20 typically. (3) For seasonal data: include seasonal lags (h ≥ 2s where s=season length). Check h is not too large (h < n/4) or too small (h < 10). If h too small: may miss autocorrelation at higher lags. If h too large: loss of power, df issues
Rigorous
Theory suggests h = ln(n) for model residuals, but simulation studies recommend h ≈ 10-20 for practical use. For raw series: h = min(20, floor(n/5)) balances power and finite-sample performance. For model residuals: test at multiple lag values (e.g., h=10, 15, 20) to check robustness. For seasonal series: h should be ≥ 2s to capture seasonal autocorrelation (e.g., h≥24 for monthly data). Maximum h: never exceed h = n/4 (loses too many df). Verify df = h - p - q > 0 (must have positive degrees of freedom). Plot p-values across range of h to assess sensitivity
If violated
If h too small (h<10): (1) Increase h to capture more autocorrelation structure. May miss autocorrelation at higher lags. Risk: Type II error (failing to detect autocorrelation). (2) Test at h=10, 15, 20 minimum. If h too large (h>n/4): (1) Reduce h to improve power. Too many lags relative to sample size reduces test power. (2) Use h = min(20, n/5) as guideline. (3) Verify sufficient df remain. If seasonal and h<2s: (1) Increase h to include seasonal lags (h≥24 for monthly). Missing seasonal lags can fail to detect seasonal autocorrelation. Recommendation: Test at multiple lag values for robustness, report range of p-values
How to check
Quick
Verify time series has no duplicate time stamps, no missing time periods (gaps), and observations are equally spaced. Check index: should be sequential (t=1,2,3,...,n) with no repeats. If duplicate time points exist (multiple observations at same time): violates independence, distorts autocorrelation. Gaps in time series: may need different approach or interpolation
Rigorous
Examine time series index structure: verify monotonic increasing time stamps with regular frequency. Check for duplicates: any(duplicated(time_index)). Check for gaps: verify diff(time_index) is constant. If irregular spacing: Ljung-Box assumes regular spacing; use methods for irregular time series (spectral analysis, state space models). If duplicates from aggregation (multiple observations same period): aggregate properly (mean, sum) before testing. If panel data: don't pool series, test each unit separately
If violated
If duplicate time points: (1) Aggregate duplicates: take mean, sum, or median within each time period. (2) Remove duplicates if data errors. (3) Restructure as panel data if multiple series. If gaps in time series: (1) Interpolate missing values (linear, spline, LOCF) if gaps small. (2) Use methods robust to irregular spacing (Lomb-Scargle periodogram). (3) Model gaps explicitly (state space models with missing data). (4) Acknowledge limitation if gaps substantial. If irregular spacing: (1) Resample to regular frequency if appropriate. (2) Use irregular time series methods. (3) Don't apply Ljung-Box (assumes equally spaced). If panel data: Test each series separately, don't pool
How to check
Quick
Calculate degrees of freedom: df = h - p - q for ARIMA(p,d,q). Must have df > 0, preferably df ≥ 5. If df ≤ 0: test invalid (χ² distribution not defined with df≤0). If df very small (1-2): test has low power, unreliable. Check: for ARIMA(2,1,2) with h=10: df = 10-2-2 = 6 (acceptable). For ARIMA(3,1,3) with h=10: df = 10-3-3 = 4 (low but usable)
Rigorous
Degrees of freedom calculation: For ARIMA(p,d,q): df = h - p - q. For SARIMA(p,d,q)(P,D,Q)_s: df = h - p - q - P - Q (adjust for all AR/MA terms, not seasonal differencing D). For regression: df = h. For VAR(p): df = h - p. Requirement: df > 0 strictly. Recommended: df ≥ 5 for reliable inference. If complex model (large p+q): either increase h to maintain adequate df, or reduce lag count. Calculate critical value: χ²_α,df where α=significance level (0.05). Compare Q statistic to critical value
If violated
If df ≤ 0: (1) Increase h (test more lags) to ensure df > 0. For ARIMA(3,1,3): need h ≥ 7 minimum. (2) Reduce model complexity if possible (lower p,q). (3) Test cannot be performed if df≤0, report NA. If df very small (1-3): (1) Increase h to boost degrees of freedom (h=20-30). (2) Use visual diagnostics instead (ACF plot). (3) Report with caution (low power). (4) Bootstrap test for better finite-sample properties. If model too complex for data: (1) Simplify model (reduce p,q) before testing. (2) Collect more data. (3) Acknowledge that complex model limits diagnostic capability
How to check
Quick
Plot residuals over time: look for changes in mean, variance, or autocorrelation pattern. Stable residuals: roughly constant variance across time, no clustering of positive/negative residuals. Structural break signs: sudden shift in residual mean or variance at specific point. Plot rolling ACF (sliding window): check if autocorrelation pattern changes over time
Rigorous
Formal structural break tests: (1) Chow test: split data at suspected break point, test if parameters differ. (2) CUSUM test (cumulative sum): detects parameter instability over time. (3) Bai-Perron test: identifies multiple unknown break points. (4) Subsample stability: split series into early/late periods, run Ljung-Box separately, compare p-values. Large difference suggests instability. (5) Rolling Ljung-Box: compute test in sliding windows, plot p-values over time. If autocorrelation structure varies: Ljung-Box on full sample is questionable (averages over regimes)
If violated
If structural break detected: (1) Model break explicitly: add intervention variables (dummy, pulse, step) at break point. (2) Fit separate models before/after break: test residuals separately for each regime. (3) Use time-varying parameter models: state space models, regime-switching ARIMA. (4) Remove break observations if data error or outlier. (5) If break recent: limit analysis to post-break period for stable structure. If gradual change (time-varying): (1) Use rolling window analysis. (2) Adaptive/robust methods (rolling regression, time-varying VAR). (3) Acknowledge limitation: Ljung-Box assumes stable structure. If multiple regimes: Model separately or use Markov-switching ARIMA
03Residual Forensics

Diagnostics

Checking residual plots and indices to examine model deviations and ensure standard error integrity.

White noise is the goal. If your residuals have a pattern, you have left information on the table.
System Health
Essential checks
  1. ACF plot of residuals to visually identify significant spikes at specific lags.
  2. Selection of maximum lag (m) based on ln(N) or 2*sqrt(N) benchmarks.
  3. Q-statistic vs. Critical Chi-Square value comparison.
  4. Audit of degrees of freedom (must be m minus the number of fitted parameters).
  5. P-value threshold check (p > .05 confirms model adequacy).
Recommended checks
  1. Box-Pierce statistic comparison for small-sample robustness.
  2. Sensitivity audit across multiple lag windows (e.g., m=5, 10, 20).
  3. Cross-correlation function (CCF) audit if residuals come from multivariate systems.
  4. Cumulative periodogram check for periodic/cyclic rhythmic ghosts.
  5. Runs test on residual signs to detect non-linear temporal clusters.
04Live Instances

Applied Minds

Review concrete study examples, data layout guidelines, and copy executable syntax scripts.

Theory is the map. Practice is the terrain. Simulation bridges the gap.
Applied Wisdom
Example 01

ARIMA Residual Diagnostics for Monthly Sales

Demonstrates Ljung-Box test for ARIMA residual diagnostics on monthly sales data (n=120 months, 10 years). Shows complete workflow: fit adequate ARIMA model and verify residuals are white noise (Ljung-Box p>0.05, GOOD), then fit inadequate model and detect autocorrelation (Ljung-Box p<0.05, BAD). Includes testing at multiple lags (h=10,15,20), ACF/PACF visualization of residuals, interpretation of test results, and guidance on when to increase model order. Illustrates critical interpretation: p>0.05 means model is adequate (we WANT to fail to reject H₀). Also shows testing raw series for randomness before modeling.

# ============================================================================
# LJUNG-BOX TEST: ARIMA Residual Diagnostics
# ============================================================================
# Demonstrates: Testing residuals for white noise after ARIMA fitting
# Data: 120 months of sales with trend + seasonality
# Goal: Verify model residuals have no autocorrelation (white noise)
# Critical: p > 0.05 is GOOD (model adequate), p < 0.05 is BAD (inadequate)
# ============================================================================

# Load required packages
library(forecast)      # ARIMA modeling
library(tseries)       # Additional tests
library(ggplot2)       # Visualization
library(gridExtra)     # Multiple plots

set.seed(123)

# ============================================================================
# 1. DATA GENERATION
# ============================================================================

cat("=== GENERATING TIME SERIES DATA ===\n\n")

# Generate 120 months of sales data with trend and seasonality
n <- 120
time_idx <- 1:n

# True model: trend + seasonality + noise
trend <- 1000 + 12 * time_idx
seasonality <- 150 * sin(2 * pi * time_idx / 12) + 80 * cos(2 * pi * time_idx / 12)
noise <- rnorm(n, mean = 0, sd = 40)

sales <- trend + seasonality + noise
sales_ts <- ts(sales, start = c(2014, 1), frequency = 12)

cat("Generated", n, "monthly observations\n")
cat("Range:", round(min(sales_ts), 1), "to", round(max(sales_ts), 1), "\n\n")

# Plot original series
par(mfrow = c(1, 1))
plot(sales_ts, main = "Monthly Sales(Original Series)",
     ylab = "Sales", xlab = "Time", col = "steelblue", lwd = 2)

# ============================================================================
# 2. TEST RAW SERIES FOR RANDOMNESS (before modeling)
# ============================================================================

cat("=== LJUNG-BOX TEST ON RAW SERIES ===\n")
cat("Testing if raw series is white noise(random)...\n\n")

# Test raw series at h=20 lags
# For raw series: df = h (no model parameters to adjust for)
lb_raw <- Box.test(sales_ts, lag = 20, type = "Ljung-Box", fitdf = 0)

cat("Ljung-Box test(raw series, h=20):\n")
cat("  Q statistic:", round(lb_raw$statistic, 4), "\n")
cat("  Degrees of freedom:", lb_raw$parameter, "\n")
cat("  p-value:", round(lb_raw$p.value, 6), "\n\n")

if (lb_raw$p.value < 0.05) {
  cat("  Conclusion: p < 0.05, REJECT H₀\n")
  cat("  Interpretation: Raw series shows autocorrelation(NOT white noise)\n")
  cat("  This is EXPECTED for series with trend/seasonality\n")
  cat("  Action: Model the autocorrelation structure with ARIMA\n\n")
} else {
  cat("  Conclusion: p > 0.05, fail to reject H₀\n")
  cat("  Interpretation: Series appears to be white noise(random)\n")
  cat("  No ARIMA modeling needed\n\n")
}

# ACF of raw series (should show strong autocorrelation)
par(mfrow = c(1, 2))
acf(sales_ts, lag.max = 36, main = "ACF: Raw Series\n(Strong autocorrelation expected)")
pacf(sales_ts, lag.max = 36, main = "PACF: Raw Series")
par(mfrow = c(1, 1))

cat("ACF shows slow decay(trend) and seasonal spikes(seasonality)\n")
cat("This confirms autocorrelation detected by Ljung-Box\n\n")

# ============================================================================
# 3. FIT ADEQUATE ARIMA MODEL
# ============================================================================

cat("=== FITTING ADEQUATE ARIMA MODEL ===\n\n")

# Use auto.arima to select appropriate model
adequate_model <- auto.arima(sales_ts, seasonal = TRUE,
                             stepwise = TRUE, approximation = FALSE,
                             ic = "bic")

cat("Selected model:\n")
print(adequate_model)

model_order <- arimaorder(adequate_model)
cat("\nModel order(p,d,q)(P,D,Q)[s]:",
    sprintf("(%d,%d,%d)(%d,%d,%d)[12]\n",
            model_order[1], model_order[2], model_order[3],
            model_order[4], model_order[5], model_order[6]))

cat("AIC:", round(adequate_model$aic, 2), "\n")
cat("BIC:", round(adequate_model$bic, 2), "\n\n")

# Extract residuals from adequate model
resid_adequate <- residuals(adequate_model)

# ============================================================================
# 4. LJUNG-BOX TEST ON ADEQUATE MODEL RESIDUALS
# ============================================================================

cat("=== LJUNG-BOX TEST ON ADEQUATE MODEL RESIDUALS ===\n\n")

# Degrees of freedom adjustment for ARIMA(p,d,q)(P,D,Q)
# df = h - p - q - P - Q (don't adjust for d or D, only AR/MA terms)
p_order <- model_order[1]
q_order <- model_order[3]
P_order <- model_order[4]
Q_order <- model_order[6]
fitdf_adequate <- p_order + q_order + P_order + Q_order

cat("Model has p=", p_order, ", q=", q_order,
    ", P=", P_order, ", Q=", Q_order, "\n")
cat("fitdf(total AR+MA parameters) =", fitdf_adequate, "\n\n")

# Test at multiple lag values for robustness
lag_values <- c(10, 15, 20)

cat("Testing at multiple lags for robustness:\n\n")

for (h in lag_values) {
  lb_test <- Box.test(resid_adequate, lag = h, type = "Ljung-Box",
                      fitdf = fitdf_adequate)
  
  df_test <- h - fitdf_adequate
  
  cat("Lag h =", h, ":\n")
  cat("  Q statistic:", round(lb_test$statistic, 4), "\n")
  cat("  df = h - fitdf =", df_test, "\n")
  cat("  p-value:", round(lb_test$p.value, 4), "\n")
  
  if (lb_test$p.value > 0.05) {
    cat("  Result: p > 0.05 - GOOD! (Residuals are white noise)\n")
  } else {
    cat("  Result: p < 0.05 - BAD! (Autocorrelation remains)\n")
  }
  cat("\n")
}

cat("Interpretation: If all p-values > 0.05, model is ADEQUATE\n")
cat("The model has captured all autocorrelation structure\n")
cat("We WANT to fail to reject H₀ (opposite of typical tests!)\n\n")

# ============================================================================
# 5. VISUAL DIAGNOSTICS FOR ADEQUATE MODEL
# ============================================================================

cat("=== VISUAL DIAGNOSTICS(ADEQUATE MODEL) ===\n\n")

par(mfrow = c(2, 2))

# 1. Residuals over time (should look random)
plot(resid_adequate, main = "Residuals Over Time(Adequate Model)",
     ylab = "Residuals", xlab = "Time", col = "darkblue")
abline(h = 0, col = "red", lty = 2)

# 2. ACF of residuals (should be within bands)
acf(resid_adequate, lag.max = 36,
    main = "ACF: Residuals(Adequate Model)\n(All lags should be in bands)")

# 3. PACF of residuals
pacf(resid_adequate, lag.max = 36,
     main = "PACF: Residuals(Adequate Model)")

# 4. Histogram
hist(resid_adequate, breaks = 20, col = "lightblue", freq = FALSE,
     main = "Histogram: Residuals", xlab = "Residuals")
curve(dnorm(x, mean = mean(resid_adequate), sd = sd(resid_adequate)),
      add = TRUE, col = "red", lwd = 2)

par(mfrow = c(1, 1))

cat("Visual checks:\n")
cat("  - Residuals appear random over time(no patterns)\n")
cat("  - ACF/PACF within confidence bands(no significant spikes)\n")
cat("  - Histogram approximately normal\n")
cat("  All diagnostics support that residuals are white noise\n\n")

# ============================================================================
# 6. FIT INADEQUATE MODEL (for comparison)
# ============================================================================

cat("=== FITTING INADEQUATE MODEL(for comparison) ===\n\n")

# Deliberately fit wrong model: ARIMA(0,1,0) - just random walk
# This will be inadequate for data with seasonality
inadequate_model <- Arima(sales_ts, order = c(0, 1, 0),
                          seasonal = list(order = c(0, 0, 0)))

cat("Inadequate model: ARIMA(0,1,0) - random walk only\n")
cat("This ignores seasonality in the data\n")
cat("AIC:", round(inadequate_model$aic, 2), "\n")
cat("BIC:", round(inadequate_model$bic, 2), "\n\n")

resid_inadequate <- residuals(inadequate_model)

# ============================================================================
# 7. LJUNG-BOX TEST ON INADEQUATE MODEL RESIDUALS
# ============================================================================

cat("=== LJUNG-BOX TEST ON INADEQUATE MODEL RESIDUALS ===\n\n")

# For ARIMA(0,1,0): p=0, q=0, so fitdf=0
fitdf_inadequate <- 0

cat("Testing at multiple lags:\n\n")

for (h in lag_values) {
  lb_test <- Box.test(resid_inadequate, lag = h, type = "Ljung-Box",
                      fitdf = fitdf_inadequate)
  
  cat("Lag h =", h, ":\n")
  cat("  Q statistic:", round(lb_test$statistic, 4), "\n")
  cat("  df =", h, "\n")
  cat("  p-value:", format.pval(lb_test$p.value, digits = 4), "\n")
  
  if (lb_test$p.value > 0.05) {
    cat("  Result: p > 0.05 - residuals appear white noise\n")
  } else {
    cat("  Result: p < 0.05 - BAD! Autocorrelation detected\n")
    cat("  Action needed: Improve model(add AR/MA/seasonal terms)\n")
  }
  cat("\n")
}

cat("Interpretation: If p < 0.05, model is INADEQUATE\n")
cat("Residuals still contain autocorrelation\n")
cat("Model hasn't captured all structure in data\n\n")

# ============================================================================
# 8. VISUAL DIAGNOSTICS FOR INADEQUATE MODEL
# ============================================================================

cat("=== VISUAL DIAGNOSTICS(INADEQUATE MODEL) ===\n\n")

par(mfrow = c(2, 2))

# 1. Residuals over time
plot(resid_inadequate, main = "Residuals Over Time(Inadequate Model)",
     ylab = "Residuals", xlab = "Time", col = "darkred")
abline(h = 0, col = "blue", lty = 2)

# 2. ACF of residuals (expect spikes outside bands)
acf(resid_inadequate, lag.max = 36,
    main = "ACF: Residuals(Inadequate Model)\n(Expect spikes at seasonal lags)")

# 3. PACF of residuals
pacf(resid_inadequate, lag.max = 36,
     main = "PACF: Residuals(Inadequate Model)")

# 4. Histogram
hist(resid_inadequate, breaks = 20, col = "lightcoral", freq = FALSE,
     main = "Histogram: Residuals", xlab = "Residuals")
curve(dnorm(x, mean = mean(resid_inadequate), sd = sd(resid_inadequate)),
      add = TRUE, col = "blue", lwd = 2)

par(mfrow = c(1, 1))

cat("Visual checks reveal problems:\n")
cat("  - ACF shows significant spikes at seasonal lags(12, 24, 36)\n")
cat("  - Patterns indicate remaining autocorrelation\n")
cat("  - Confirms Ljung-Box test result(p < 0.05)\n")
cat("  - Need to add seasonal terms to model\n\n")

# ============================================================================
# 9. COMPARE MODELS
# ============================================================================

cat("=== MODEL COMPARISON ===\n\n")

# Compare at h=20
lb_adeq_20 <- Box.test(resid_adequate, lag = 20, type = "Ljung-Box",
                       fitdf = fitdf_adequate)
lb_inadeq_20 <- Box.test(resid_inadequate, lag = 20, type = "Ljung-Box",
                          fitdf = fitdf_inadequate)

comparison <- data.frame(
  Model = c("Adequate(auto.arima)", "Inadequate(random walk)"),
  AIC = c(adequate_model$aic, inadequate_model$aic),
  BIC = c(adequate_model$bic, inadequate_model$bic),
  LB_Q = c(lb_adeq_20$statistic, lb_inadeq_20$statistic),
  LB_p = c(lb_adeq_20$p.value, lb_inadeq_20$p.value),
  Decision = c(
    ifelse(lb_adeq_20$p.value > 0.05, "ADEQUATE", "INADEQUATE"),
    ifelse(lb_inadeq_20$p.value > 0.05, "ADEQUATE", "INADEQUATE")
  )
)

cat("Model Comparison(Ljung-Box at h=20):\n")
print(comparison, row.names = FALSE)

cat("\nInterpretation:\n")
cat("  - Lower AIC/BIC indicates better fit\n")
cat("  - Ljung-Box p > 0.05 indicates adequate model(GOOD)\n")
cat("  - Ljung-Box p < 0.05 indicates inadequate model(BAD)\n")
cat("  - Adequate model has both better fit AND white noise residuals\n\n")

# ============================================================================
# 10. P-VALUE ACROSS MULTIPLE LAGS (sensitivity analysis)
# ============================================================================

cat("=== SENSITIVITY ANALYSIS: P-VALUES ACROSS LAGS ===\n\n")

# Compute Ljung-Box at many lag values
lag_range <- 1:30
p_values_adeq <- numeric(length(lag_range))
p_values_inadeq <- numeric(length(lag_range))

for (i in seq_along(lag_range)) {
  h <- lag_range[i]
  
  # Skip if df would be <= 0
  if (h > fitdf_adequate) {
    lb_a <- Box.test(resid_adequate, lag = h, type = "Ljung-Box",
                     fitdf = fitdf_adequate)
    p_values_adeq[i] <- lb_a$p.value
  } else {
    p_values_adeq[i] <- NA
  }
  
  if (h > fitdf_inadequate) {
    lb_i <- Box.test(resid_inadequate, lag = h, type = "Ljung-Box",
                     fitdf = fitdf_inadequate)
    p_values_inadeq[i] <- lb_i$p.value
  } else {
    p_values_inadeq[i] <- NA
  }
}

# Plot p-values across lags
par(mfrow = c(1, 1))
plot(lag_range, p_values_adeq, type = "b", col = "darkgreen", pch = 16,
     ylim = c(0, 1), xlab = "Lag(h)", ylab = "p-value",
     main = "Ljung-Box p-values Across Lags")
lines(lag_range, p_values_inadeq, type = "b", col = "darkred", pch = 17)
abline(h = 0.05, col = "blue", lty = 2, lwd = 2)
legend("topright",
       legend = c("Adequate model(p > 0.05 = GOOD)",
                  "Inadequate model(p < 0.05 = BAD)",
                  "Significance threshold(0.05)"),
       col = c("darkgreen", "darkred", "blue"),
       lty = c(1, 1, 2), pch = c(16, 17, NA), lwd = c(1, 1, 2),
       bty = "n")

cat("Plot interpretation:\n")
cat("  - Adequate model: p-values consistently > 0.05 (above threshold)\n")
cat("  - Inadequate model: p-values < 0.05 (below threshold)\n")
cat("  - Results are robust across different lag choices\n\n")

# ============================================================================
# 11. FINAL SUMMARY
# ============================================================================

cat("=== FINAL SUMMARY ===\n\n")

cat("LJUNG-BOX TEST INTERPRETATION(Critical!):\n")
cat("  p > 0.05: GOOD - Residuals are white noise, model adequate\n")
cat("  p < 0.05: BAD - Autocorrelation remains, model inadequate\n")
cat("  (This is OPPOSITE of typical hypothesis tests!)\n\n")

cat("ADEQUATE MODEL RESULTS:\n")
cat("  Model:", paste(capture.output(adequate_model)[1]), "\n")
cat("  Ljung-Box p-value(h=20):", round(lb_adeq_20$p.value, 4), "\n")
cat("  Decision: p > 0.05, residuals are white noise\n")
cat("  Conclusion: Model has captured all autocorrelation\n")
cat("  Action: ACCEPT model, proceed to forecasting\n\n")

cat("INADEQUATE MODEL RESULTS:\n")
cat("  Model: ARIMA(0,1,0) - random walk only\n")
cat("  Ljung-Box p-value(h=20):", format.pval(lb_inadeq_20$p.value), "\n")
cat("  Decision: p < 0.05, autocorrelation detected\n")
cat("  Conclusion: Model inadequate, seasonality not captured\n")
cat("  Action: REJECT model, add seasonal terms\n\n")

cat("BEST PRACTICES:\n")
cat("  1. Always test residuals after fitting time series model\n")
cat("  2. Test at multiple lags(h=10,15,20) for robustness\n")
cat("  3. Combine Ljung-Box with ACF/PACF visual inspection\n")
cat("  4. If p < 0.05: increase model order or add seasonal terms\n")
cat("  5. Never accept a model with autocorrelated residuals\n")
cat("  6. Remember: p > 0.05 is GOOD for residual diagnostics!\n\n")

cat("Ljung-Box Test demonstration complete.\n")

# ============================================================================
# END OF LJUNG-BOX TEST EXAMPLE
# ============================================================================
05Tactical Pivots

Alternatives

Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.

When the path is blocked, pivot. Rigor is not rigidity; it is the intelligent adaptation to reality.
Adaptive Strategy
Measurement Precision Ladder Ideal · Continuous Residual Series
Ratio
Maintain Ljung-Box logic. The definitive audit for residual white-noise in high-fidelity markers.
Peak Signal
Interval
Ideal for Diagnostic Checks. Ensure the temporal depth (N) is sufficient to observe autocorrelations.
Standard Precision
Nominal / Ordinal
Abandon Q-Test. Use Runs tests or Join-Count statistics to model categorical randomness.
Model Collapse
Temporal Trajectory Audit Static White-Noise Audit
Residual Pulse
Portmanteau audit.
Stay with Ljung-Box. Verify if the model successfully extracted all the information.
Raw Pulse
Stationarity check.
Pivot to ADF or KPSS to see if the serial correlation is due to an underlying unit root.
Adaptive Technical Safeguards · adaptive safeguards
low order autocorrelation
  • Durbin-Watson Strike — Use if you specifically care only about the first temporal lag (AR1).
  • Breusch-Godfrey Test — The required alternative for residuals from multivariable regression models.
non linear dependence
  • McLeod-Li Test — Audit for 'ARCH Effects' by applying the Ljung-Box strike to the squared residuals.
  • BDS Test — The high-fidelity alternative for detecting complex non-linear temporal ghosts.
too few residuals
  • Box-Pierce Strike — Utilize the simpler (but less biased) version of the Q-statistic for very small samples.
06Adjusted Comparisons

Post-hoc

Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.

The omnibus test opens the door; post-hoc analysis explores the room.
Forensic Detail
Adjusted Comparisons
  • Vary number of lags tested (common: 10, 20, or sqrt(n))
  • Compare with Box-Pierce test (asymptotically equivalent)
  • Examine ACF/PACF plots alongside test results
  • Apply to squared residuals for ARCH effects
  • Compare with Breusch-Godfrey LM test for regression residuals
Interpretation Guidelines

Ljung-Box tests for autocorrelation in residuals. Post-hoc tests are not applicable.

07Standardized scale impact

Effect Size

Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.

Significance is noise. Magnitude is the signal. Measure the impact, not just the probability.
Impact Magnitude
N/A
Recommended Measure
08Statistical Power

Sample Size

Guidelines for minimum sample requirements and power analysis parameters.

An underpowered study is an ethical failure. Respect the data by collecting enough of it.
Power Protocol
Floor Requirements

The 'History Minimum': A minimum of 50 residuals is required. The Q-statistic loses its Chi-Square approximation accuracy if the temporal depth is too shallow to calculate a cluster of autocorrelations.

Effect SizeParametersRequired n
Small EffectLow Signal (r=.10)n ≈ 800
Medium EffectModerate Signal (r=.30)n ≈ 90
Large EffectStrong Signal (r=.50)n ≈ 35
Key considerations

The 'p+q' Penalty: If you are testing residuals from a fitted model, you MUST reduce the degrees of freedom by the number of parameters estimated. This reduces power, requiring a 15% N-buffer to maintain the same sensitivity.

G*Power StrategyBenchmark: χ² tests → Portmanteau Q (Ljung-Box). Parameters: Number of lags (m), α = .05, Power = .80. Note: Power is defined as the 'Detection of Residual Signal' (Failure of model adequacy).
09APA narrative blueprint

Reporting

How to compile statistical results into publication prose matching APA and journal style guides.

Data does not speak for itself. It requires a translator. Be clear, be precise, be honest.
Narrative Arc
10Exhibit Builder

Manuscript Lab

Copy standard summary tables and forensic reporting grids to outline analysis details.

Table 1: Ljung-Box Portmanteau Test for Residual Independence
VariableQ-Statisticdfp-valueStatus
ARIMA(1,1,1) Residuals8.4210.582SUCCESS (White Noise)
Raw Data Series145.210< .001AUTOCORRELATED
Note. Null Hypothesis: Residuals are independent (White Noise). Lag = 10.
p = .582Confirms 'Golden Integrity'. The model has successfully squeezed all signals out of the data; the remaining errors are pure random noise.
Header glossary

The 'Pattern' Total. Sums the autocorrelations across all lags up to the specified limit. Higher Q = more evidence of hidden patterns.

The Randomness Probability. If p > .05, we conclude the residuals are random 'White Noise' and the model has captured all available information.

11Algorithmic Logic

Command Center

Syntax libraries and function parameters for executing calculations in stats packages.

Code is the modern laboratory. Clean execution ensures reproducible discovery.
Execution Engine
# 1. Execute Ljung-Box Test
Box.test(residuals(model), lag = 10, type = 'Ljung-Box')

# 2. Automated Diagnostic Plot
forecast::checkresiduals(model)
Library stack
R
statsforecast
Python
statsmodels.tsa.stattools
Elite Forensic Strike

Ljung-Box is not just for residuals. Use it on raw data to prove that a time series is NOT a 'Random Walk' before you start building complex models.

# Audit for Non-randomness in Raw Series
Box.test(ts_data, type = 'Ljung-Box')
12The Over-adjustment Trap

Common Mistakes

Analytical caveats and corrections to maintain modeling integrity.

Wisdom is learning from the failures of others. Anticipate the error before it occurs.
Defensive Logic
Why it's wrong
For Ljung-Box residual diagnostics, p < 0.05 is BAD (autocorrelation detected, model inadequate). This is opposite of typical hypothesis tests where p < 0.05 means reject H₀ and claim effect. Here we WANT to fail to reject H₀ (p > 0.05) because H₀ is 'no autocorrelation' (white noise). Confusing this leads to accepting inadequate models
The correction
ALWAYS remember for Ljung-Box residual diagnostics: p > 0.05 = GOOD (residuals are white noise, model adequate). p < 0.05 = BAD (autocorrelation remains, model needs improvement). When p < 0.05, increase model order (p or q), add seasonal terms, or check for structural breaks. Never accept a model with Ljung-Box p < 0.05. Test is diagnostic, not inferential
Why it's wrong
Degrees of freedom must be df = h - p - q for ARIMA(p,d,q) residuals, NOT df = h. Failing to adjust overestimates df, making test too liberal (rejects H₀ too often), underestimates p-value. Results in false positives (incorrectly concluding autocorrelation when model is adequate). Note: adjust for AR/MA terms (p,q) but NOT differencing (d)
The correction
For ARIMA(p,d,q): df = h - p - q. For SARIMA(p,d,q)(P,D,Q)_s: df = h - p - q - P - Q (adjust for all AR/MA terms). For regression residuals (OLS): df = h (no adjustment). For VAR(p): df = h - p. In R: use fitdf parameter in Box.test(). In Python: use model_df parameter in acorr_ljungbox(). Always verify df > 0. Example: ARIMA(2,1,2) with h=10 has df=10-2-2=6
Why it's wrong
If h too small (h < 10), test has low power to detect autocorrelation at higher lags. May miss important structure, particularly seasonality (e.g., h=5 won't detect autocorrelation at lag 12 for monthly data). Leads to Type II error: failing to detect autocorrelation when it exists. Gives false confidence in inadequate model
The correction
Use adequate lag length: (1) For raw series: h = min(20, n/5). (2) For model residuals: h = 10-20 standard. (3) For seasonal data: h ≥ 2s where s=seasonal period (h≥24 for monthly, h≥8 for quarterly). Test at multiple lags (h=10,15,20) for robustness. If results inconsistent across lags, investigate ACF plot. Never use h < 10 unless sample size very small
Why it's wrong
If h too large (h > n/4), test loses power because too many degrees of freedom consumed. Sample autocorrelations at high lags have large variance, add noise to test. Reduces ability to detect autocorrelation. For small samples, may have df ≤ 0 (test undefined). Also, high-lag autocorrelations less relevant for typical time series diagnostics
The correction
Don't exceed h = n/4. Use h = min(20, n/5) for raw series as guideline. For n=100: max h=25. For n=50: max h=12. Verify sufficient degrees of freedom: df = h - p - q > 0, preferably df ≥ 5. If sample small, reduce h accordingly. Focus on meaningful lags: for non-seasonal data, h=15-20 is sufficient. For seasonal data, include 1-2 seasonal cycles but not excessive lags
Why it's wrong
Ljung-Box is omnibus test (joint test across all lags), cannot identify WHICH lags have autocorrelation. If test rejects (p < 0.05), you know autocorrelation exists but not where. ACF plot shows specific lags with spikes, guiding model improvement (increase AR if PACF spikes, increase MA if ACF spikes, add seasonal if spikes at s, 2s, etc.)
The correction
ALWAYS plot ACF and PACF of residuals alongside Ljung-Box test. Visual inspection complements formal test. Ljung-Box answers 'is there autocorrelation?', ACF/PACF answers 'at which lags?'. If Ljung-Box p < 0.05: examine ACF to identify problematic lags. Spike at lag 1 → increase p or q. Spikes at lags 12,24,36 → add seasonal terms. Combined approach (formal test + visual) most reliable for model diagnostics
Why it's wrong
Box-Pierce test: Q_BP = n Σ ρ̂²_k. Ljung-Box: Q_LB = n(n+2) Σ [ρ̂²_k/(n-k)]. Ljung-Box includes finite-sample correction, performs better in small/moderate samples (higher power, better size properties). Box-Pierce is asymptotically equivalent but inferior in practice. Using Box-Pierce loses power, especially for n < 200
The correction
ALWAYS use Ljung-Box, not Box-Pierce. In R: Box.test(..., type='Ljung-Box') (default). In Python: acorr_ljungbox() uses Ljung-Box. Box-Pierce is historical; Ljung-Box strictly dominates in finite samples. Only exception: if comparing to very old papers using Box-Pierce. For all modern applications: Ljung-Box is the standard, recommended test for serial correlation
Why it's wrong
Ljung-Box most commonly used for testing MODEL RESIDUALS (are they white noise?). Testing raw series for autocorrelation is valid but has different interpretation. Raw series with trend/seasonality SHOULD have autocorrelation. Finding p < 0.05 for raw series isn't a 'problem', it just indicates structure to model. Confusing these contexts leads to misinterpretation
The correction
Clarify what you're testing: (1) Raw series: Testing if series is random (white noise). Expect p < 0.05 if trend/seasonality present (this is EXPECTED, not a problem). Use as exploratory step before modeling. (2) Model residuals: Testing if model captured all structure. Want p > 0.05 (residuals white noise, model adequate). If p < 0.05, model needs improvement. Use correct degrees of freedom: df=h for raw series, df=h-p-q for ARIMA residuals
Why it's wrong
Ljung-Box tests H₀: ρ₁=ρ₂=...=ρ_h=0 jointly. Rejection means at least one ρ_k ≠ 0, but doesn't tell you which. Could be lag 1, lag 12, or multiple lags. Without ACF plot, you can't diagnose problem or decide how to improve model. Leads to trial-and-error model building instead of targeted improvement
The correction
Recognize Ljung-Box as omnibus test for presence of autocorrelation, not diagnostic of specific lags. After rejection (p < 0.05): (1) Plot ACF and PACF of residuals. (2) Identify which lags have significant spikes (outside ±1.96/√n bands). (3) Spike at lag 1-2: increase AR or MA order. (4) Spikes at seasonal lags (12,24,... for monthly): add seasonal terms. (5) Use ACF/PACF pattern to guide model modification (AR vs MA vs seasonal)
Why it's wrong
Testing at single lag (e.g., only h=10) may miss autocorrelation at higher lags or give unstable result. Power of test varies with h. Results should be robust to reasonable choices of h. If p-value changes dramatically from h=10 to h=20, suggests instability or specific lag-dependent autocorrelation pattern. Single lag test less reliable
The correction
Test at multiple lags: h=10, 15, 20 minimum. For seasonal data: also test h=24, 30. Report range of p-values or worst-case (minimum p-value). Consistent results across lags (all p > 0.05 or all p < 0.05) more convincing. If results differ: plot p-values across h=1,...,30 to visualize pattern. Investigate ACF at lags where test result changes. Robustness check: if model adequate, should pass test at all reasonable lag choices
13Academic Lineage

References

Scholarly lineage and citation keys grounding the statistical framework.

We stand on the shoulders of giants. Honor the source of the method.
Academic Lineage
[1]
[2]
[3]
[4]
[5]
[6]
If your residuals still have a rhythm, your model hasn't finished its work. Use Ljung-Box to ensure the noise is silent before you claim discovery.
The Interpretive Rigor Directive
statminds · Ljung-BoxMind reference · v2.2 · updated 2026-01-1715 of 15 sections