ARIMA Models
The blueprint for Temporal Discovery. ARIMA (AutoRegressive Integrated Moving Average) audits the internal pulse of time series data, utilizing past values and previous errors to forecast the future with high-fidelity precision.
What is it?
ARIMA Models analyzes sequences of data points ordered chronologically over time to extract patterns, model trends, and make forecasts.
The blueprint for Temporal Discovery. ARIMA (AutoRegressive Integrated Moving Average) audits the internal pulse of time series data, utilizing past values and previous errors to forecast the future with high-fidelity precision.
Goals & Indications
- Temporal Pulse Audit: Decipher the 'Autoregressive' signal (past influence) and 'Moving Average' noise (random shock recovery).
- Predictive Forecasting: Construct a mathematical engine that projects future trends based on historical temporal patterns.
- Stationarity Neutralization: Mathematically 'level' the data through differencing to ensure the temporal discovery basis is stable.
Core Idea Diagram
Claims tested
How it works
- Determine integration order d by differencing series to achieve stationarity.
- Inspect ACF/PACF plots to estimate initial AR order p and MA order q.
- Estimate ARIMA(p,d,q) coefficients using Maximum Likelihood Estimation.
- Check residuals for independence using diagnostics like Ljung-Box test.
Assumptions
Important Note
ARIMA models are primarily used for forecasting rather than hypothesis testing. The focus is on capturing temporal dependencies through three components: (1) AR(p): autoregressive terms using p past values, (2) I(d): differencing d times to achieve stationarity, (3) MA(q): moving average terms using q past errors. Model selection emphasizes minimizing forecast error and ensuring residuals are white noise. Key distinction from regression: ARIMA models temporal dependence explicitly; no external predictors needed (though ARIMAX extends this). Key distinction from exponential smoothing: ARIMA based on autocorrelation structure (ACF/PACF), exponential smoothing based on weighted averages. Seasonal extension: SARIMA(p,d,q)(P,D,Q)_s adds seasonal components with period s.
Worked Example
| Model | Param Estimate | AIC | BIC | Log-Lik |
|---|---|---|---|---|
| ARIMA(1,0,1) | ar1=0.72, ma1=-0.31 | 142.1 | 148.5 | -68.05 |
ARIMA(p, d, q) Coefficient & ACF Laboratory
Select model type and change coefficients. Observe the changes in both the time series path and the sample autocorrelation function (ACF).
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: The time series is white noise (no autocorrelation structure)
Hₐ: The time series exhibits autocorrelation that can be modeled with ARIMA(p,d,q) structure
ARIMA models are primarily used for forecasting rather than hypothesis testing. The focus is on capturing temporal dependencies through three components: (1) AR(p): autoregressive terms using p past values, (2) I(d): differencing d times to achieve stationarity, (3) MA(q): moving average terms using q past errors. Model selection emphasizes minimizing forecast error and ensuring residuals are white noise. Key distinction from regression: ARIMA models temporal dependence explicitly; no external predictors needed (though ARIMAX extends this). Key distinction from exponential smoothing: ARIMA based on autocorrelation structure (ACF/PACF), exponential smoothing based on weighted averages. Seasonal extension: SARIMA(p,d,q)(P,D,Q)_s adds seasonal components with period s.
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- ADF / KPSS Tests to verify stationarity after differencing (d).
- ACF / PACF Plot audit to identify significant lags for AR (p) and MA (q) components.
- Ljung-Box Q-test on residuals to ensure no unmodeled signal remains (p > .05).
- Check for model parsimony using AIC / BIC / HQIC criteria.
- Residual Q-Q plot to verify the assumption of Gaussian White Noise.
- Out-of-sample forecasting audit (RMSE, MAE, MAPE) using a hold-out set.
- Residuals vs. Time plot to check for time-varying variance (Heteroskedasticity).
- Stability check of the AR/MA roots (all must lie inside the unit circle).
- Jarque-Bera test for residual normality.
- Forecast sensitivity audit to 'd' parameter selection (over-differencing check).
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Monthly Sales Forecasting with Trend and Seasonality
Forecast monthly product sales (n=120 months, 10 years of data) exhibiting upward trend and annual seasonality using SARIMA model. Demonstrates complete workflow: data generation, exploratory analysis, stationarity testing, seasonal decomposition, model identification via ACF/PACF, automatic and manual model fitting, residual diagnostics (Ljung-Box test, ACF), parameter interpretation, forecasting with prediction intervals, and holdout validation. This example shows both automatic selection (auto.arima) and manual SARIMA specification, comparing multiple candidate models using AIC/BIC, and evaluating forecast accuracy using RMSE/MAE/MAPE metrics.
# ============================================================================
# ARIMA MODELS: Monthly Sales Forecasting with Trend and Seasonality
# ============================================================================
# Demonstrates: SARIMA model identification, estimation, diagnostics, forecasting
# Data: 120 months (10 years) of monthly sales with trend + seasonality + noise
# Model: SARIMA(p,d,q)(P,D,Q)_12 selected via auto.arima and manual specification
# ============================================================================
# Load required packages
library(forecast) # auto.arima, Arima, forecast functions
library(tseries) # ADF test for stationarity
library(ggplot2) # visualization
library(gridExtra) # multiple plots
library(lmtest) # coeftest for coefficient significance
set.seed(42)
# ============================================================================
# 1. DATA GENERATION: Monthly sales with trend, seasonality, and noise
# ============================================================================
# Generate 120 months (10 years) of monthly sales data
n <- 120
time_index <- 1:n
# Components:
# - Trend: linear growth
# - Seasonality: annual pattern (peak in December, low in February)
# - Noise: random variation
trend <- 1000 + 15 * time_index # Linear trend: starting at 1000, growing 15/month
seasonality <- 200 * sin(2 * pi * time_index / 12) + 100 * cos(2 * pi * time_index / 12)
noise <- rnorm(n, mean = 0, sd = 50)
sales <- trend + seasonality + noise
# Create time series object with monthly frequency
sales_ts <- ts(sales, start = c(2014, 1), frequency = 12)
cat("Data generated: n =", length(sales_ts), "monthly observations\n")
cat("Range:", round(min(sales_ts), 2), "to", round(max(sales_ts), 2), "\n\n")
# ============================================================================
# 2. EXPLORATORY DATA ANALYSIS
# ============================================================================
cat("=== EXPLORATORY ANALYSIS ===\n")
# Time series plot
par(mfrow = c(2, 2))
plot(sales_ts, main = "Monthly Sales(Original Series)",
ylab = "Sales", xlab = "Time", col = "steelblue", lwd = 2)
# Seasonal subseries plot (shows seasonal pattern clearly)
monthplot(sales_ts, main = "Seasonal Subseries Plot",
ylab = "Sales", xlab = "Month", col = "darkgreen", lwd = 2)
# ACF: shows both trend (slow decay) and seasonality (spikes at 12, 24, 36...)
acf(sales_ts, lag.max = 48, main = "ACF: Original Series")
# PACF: for model identification after stationarity achieved
pacf(sales_ts, lag.max = 48, main = "PACF: Original Series")
par(mfrow = c(1, 1))
# Summary statistics
cat("\nSummary statistics:\n")
print(summary(sales_ts))
cat("SD:", round(sd(sales_ts), 2), "\n\n")
# ============================================================================
# 3. STATIONARITY TESTING
# ============================================================================
cat("=== STATIONARITY TESTS ===\n")
# Augmented Dickey-Fuller test: H0 = unit root (non-stationary)
adf_original <- adf.test(sales_ts)
cat("ADF test(original series):\n")
cat(" Test statistic:", round(adf_original$statistic, 4), "\n")
cat(" p-value:", round(adf_original$p.value, 4), "\n")
if (adf_original$p.value < 0.05) {
cat(" Conclusion: Reject H0, series is STATIONARY\n\n")
} else {
cat(" Conclusion: Fail to reject H0, series is NON-STATIONARY\n")
cat(" Action: Apply differencing\n\n")
}
# KPSS test: H0 = stationarity (opposite of ADF)
kpss_original <- kpss.test(sales_ts)
cat("KPSS test(original series):\n")
cat(" Test statistic:", round(kpss_original$statistic, 4), "\n")
cat(" p-value:", round(kpss_original$p.value, 4), "\n")
if (kpss_original$p.value < 0.05) {
cat(" Conclusion: Reject H0, series is NON-STATIONARY\n\n")
} else {
cat(" Conclusion: Fail to reject H0, series is STATIONARY\n\n")
}
# Apply differencing if needed
cat("Applying first difference(d=1)...\n")
sales_diff1 <- diff(sales_ts, differences = 1)
adf_diff1 <- adf.test(sales_diff1)
cat("ADF test(first differenced):\n")
cat(" p-value:", round(adf_diff1$p.value, 4), "\n")
if (adf_diff1$p.value < 0.05) {
cat(" Conclusion: Series is stationary after d=1 difference\n\n")
}
# Check if seasonal differencing needed
cat("Applying seasonal difference(D=1, s=12)...\n")
sales_sdiff <- diff(sales_ts, lag = 12)
par(mfrow = c(2, 2))
plot(sales_diff1, main = "First Difference(d=1)", ylab = "Diff Sales", col = "darkblue")
plot(sales_sdiff, main = "Seasonal Difference(D=1, s=12)", ylab = "Seasonal Diff", col = "darkred")
acf(sales_diff1, lag.max = 48, main = "ACF: First Difference")
acf(sales_sdiff, lag.max = 48, main = "ACF: Seasonal Difference")
par(mfrow = c(1, 1))
cat("\n")
# ============================================================================
# 4. SEASONAL DECOMPOSITION
# ============================================================================
cat("=== SEASONAL DECOMPOSITION ===\n")
# Decompose into trend, seasonal, and remainder components
decomp <- decompose(sales_ts, type = "additive")
par(mfrow = c(4, 1), mar = c(2, 4, 2, 2))
plot(sales_ts, main = "Original Series", ylab = "Sales")
plot(decomp$trend, main = "Trend Component", ylab = "Trend")
plot(decomp$seasonal, main = "Seasonal Component", ylab = "Seasonal")
plot(decomp$random, main = "Remainder(Noise)", ylab = "Remainder")
par(mfrow = c(1, 1), mar = c(5, 4, 4, 2))
cat("Decomposition completed. Clear trend and seasonal patterns observed.\n\n")
# ============================================================================
# 5. MODEL IDENTIFICATION: ACF/PACF Analysis
# ============================================================================
cat("=== MODEL IDENTIFICATION ===\n")
# After first and seasonal differencing
sales_diff_both <- diff(diff(sales_ts, lag = 12), differences = 1)
par(mfrow = c(2, 2))
plot(sales_diff_both, main = "Differenced Series(d=1, D=1)",
ylab = "Differenced Sales", col = "purple")
acf(sales_diff_both, lag.max = 48, main = "ACF: Differenced Series")
pacf(sales_diff_both, lag.max = 48, main = "PACF: Differenced Series")
par(mfrow = c(1, 1))
cat("ACF/PACF patterns suggest:")
cat("\n - Both ACF and PACF decay(suggests ARMA structure)")
cat("\n - Possible seasonal MA component(spike at lag 12 in ACF)")
cat("\n - Candidate models: ARIMA(1,1,1)(0,1,1)₁₂, ARIMA(0,1,1)(0,1,1)₁₂\n\n")
# ============================================================================
# 6. AUTOMATIC MODEL SELECTION: auto.arima()
# ============================================================================
cat("=== AUTOMATIC MODEL SELECTION ===\n")
# auto.arima with stepwise search (fast)
auto_model <- auto.arima(sales_ts,
seasonal = TRUE,
stepwise = TRUE,
approximation = FALSE,
trace = TRUE,
ic = "aicc", # AICc (corrected AIC for small samples)
max.p = 5, max.q = 5,
max.P = 2, max.Q = 2)
cat("\nSelected model by auto.arima:\n")
print(auto_model)
cat("\nModel summary:\n")
cat(" AIC:", round(auto_model$aic, 2), "\n")
cat(" BIC:", round(auto_model$bic, 2), "\n")
cat(" RMSE:", round(sqrt(mean(auto_model$residuals^2)), 2), "\n")
cat(" MAE:", round(mean(abs(auto_model$residuals)), 2), "\n\n")
# Extract model order
auto_order <- arimaorder(auto_model)
cat("Model order(p,d,q)(P,D,Q)[s]:", paste0("(",
auto_order[1], ",", auto_order[2], ",", auto_order[3], ")(",
auto_order[4], ",", auto_order[5], ",", auto_order[6], ")[12]\n\n"))
# ============================================================================
# 7. MANUAL MODEL SPECIFICATION: Compare candidate models
# ============================================================================
cat("=== MANUAL MODEL COMPARISON ===\n")
# Fit several candidate SARIMA models
model1 <- Arima(sales_ts, order = c(0, 1, 1), seasonal = c(0, 1, 1))
model2 <- Arima(sales_ts, order = c(1, 1, 0), seasonal = c(0, 1, 1))
model3 <- Arima(sales_ts, order = c(1, 1, 1), seasonal = c(0, 1, 1))
model4 <- Arima(sales_ts, order = c(0, 1, 1), seasonal = c(1, 1, 0))
# Compare information criteria
model_comparison <- data.frame(
Model = c("ARIMA(0,1,1)(0,1,1)[12]",
"ARIMA(1,1,0)(0,1,1)[12]",
"ARIMA(1,1,1)(0,1,1)[12]",
"ARIMA(0,1,1)(1,1,0)[12]",
"auto.arima"),
AIC = c(model1$aic, model2$aic, model3$aic, model4$aic, auto_model$aic),
BIC = c(model1$bic, model2$bic, model3$bic, model4$bic, auto_model$bic),
RMSE = c(sqrt(mean(model1$residuals^2)),
sqrt(mean(model2$residuals^2)),
sqrt(mean(model3$residuals^2)),
sqrt(mean(model4$residuals^2)),
sqrt(mean(auto_model$residuals^2)))
)
cat("Model comparison:\n")
print(model_comparison, row.names = FALSE)
# Select best model by BIC (prefer parsimony)
best_idx <- which.min(model_comparison$BIC)
cat("\nBest model by BIC:", model_comparison$Model[best_idx], "\n")
cat("Using ARIMA(0,1,1)(0,1,1)[12] for further analysis(common airline model)\n\n")
final_model <- model1 # Select best model
# ============================================================================
# 8. MODEL DIAGNOSTICS
# ============================================================================
cat("=== MODEL DIAGNOSTICS ===\n")
# Coefficient summary
cat("\nCoefficient estimates:\n")
print(coeftest(final_model))
cat("\n")
# Check residuals
residuals_model <- residuals(final_model)
# 1. Ljung-Box test for autocorrelation
ljung_box <- Box.test(residuals_model, lag = 20, type = "Ljung-Box", fitdf = 2)
cat("Ljung-Box test(lag=20):\n")
cat(" Test statistic:", round(ljung_box$statistic, 4), "\n")
cat(" p-value:", round(ljung_box$p.value, 4), "\n")
if (ljung_box$p.value > 0.05) {
cat(" Conclusion: Residuals are WHITE NOISE(no autocorrelation) - GOOD\n\n")
} else {
cat(" Conclusion: Residuals show autocorrelation - model inadequate\n\n")
}
# 2. Shapiro-Wilk test for normality
shapiro <- shapiro.test(residuals_model)
cat("Shapiro-Wilk test for normality:\n")
cat(" Test statistic:", round(shapiro$statistic, 4), "\n")
cat(" p-value:", round(shapiro$p.value, 4), "\n")
if (shapiro$p.value > 0.05) {
cat(" Conclusion: Residuals are NORMALLY distributed - GOOD\n\n")
} else {
cat(" Conclusion: Residuals deviate from normality\n")
cat(" Impact: Point forecasts still valid, prediction intervals may be off\n\n")
}
# 3. Visual diagnostics
par(mfrow = c(2, 2))
# Residuals over time
plot(residuals_model, main = "Residuals over Time",
ylab = "Residuals", xlab = "Time", col = "darkblue")
abline(h = 0, col = "red", lty = 2)
# ACF of residuals
acf(residuals_model, lag.max = 36, main = "ACF of Residuals")
# Histogram of residuals
hist(residuals_model, breaks = 20, col = "lightblue",
main = "Histogram of Residuals", xlab = "Residuals", freq = FALSE)
curve(dnorm(x, mean = mean(residuals_model), sd = sd(residuals_model)),
add = TRUE, col = "red", lwd = 2)
# Q-Q plot
qqnorm(residuals_model, main = "Q-Q Plot of Residuals")
qqline(residuals_model, col = "red", lwd = 2)
par(mfrow = c(1, 1))
cat("Visual diagnostics completed.\n")
cat("Check: Residuals should appear random, ACF within bands, histogram normal, Q-Q on line\n\n")
# 4. Check for remaining patterns
cat("Residual statistics:\n")
cat(" Mean:", round(mean(residuals_model), 4), "(should be ~0)\n")
cat(" SD:", round(sd(residuals_model), 2), "\n")
cat(" Min:", round(min(residuals_model), 2), "\n")
cat(" Max:", round(max(residuals_model), 2), "\n")
cat(" Standardized residuals >3:", sum(abs(residuals_model/sd(residuals_model)) > 3), "\n\n")
# ============================================================================
# 9. FORECASTING
# ============================================================================
cat("=== FORECASTING ===\n")
# Forecast next 24 months (2 years)
forecast_horizon <- 24
forecasts <- forecast(final_model, h = forecast_horizon, level = c(80, 95))
cat("Forecast horizon:", forecast_horizon, "months\n")
cat("\nForecast summary(first 12 months):\n")
print(head(as.data.frame(forecasts), 12))
# Plot forecasts
par(mfrow = c(1, 1), mar = c(5, 4, 4, 2))
plot(forecasts, main = "Sales Forecast: Next 24 Months",
ylab = "Sales", xlab = "Time",
col = "steelblue", lwd = 2,
shadecols = c("lightblue", "lightgray"),
fcol = "darkred", flwd = 2)
legend("topleft",
legend = c("Observed", "Forecast", "80% PI", "95% PI"),
col = c("steelblue", "darkred", "lightblue", "lightgray"),
lwd = c(2, 2, 10, 10),
bty = "n")
cat("\nForecast plot generated with 80% and 95% prediction intervals\n")
cat("Note: Prediction intervals widen with forecast horizon(reflects uncertainty)\n\n")
# ============================================================================
# 10. HOLDOUT VALIDATION
# ============================================================================
cat("=== HOLDOUT VALIDATION ===\n")
# Split data: train on first 96 months, test on last 24 months
train_size <- 96
test_size <- n - train_size
train_ts <- window(sales_ts, end = c(2014, train_size))
test_ts <- window(sales_ts, start = c(2014 + train_size %/% 12, (train_size %% 12) + 1))
cat("Training set:", length(train_ts), "observations\n")
cat("Test set:", length(test_ts), "observations\n\n")
# Fit model on training data
train_model <- Arima(train_ts, order = c(0, 1, 1), seasonal = c(0, 1, 1))
# Forecast test period
test_forecasts <- forecast(train_model, h = test_size)
# Calculate forecast errors
forecast_errors <- test_ts - test_forecasts$mean
# Forecast accuracy metrics
rmse <- sqrt(mean(forecast_errors^2))
mae <- mean(abs(forecast_errors))
mape <- mean(abs(forecast_errors / test_ts)) * 100
cat("Forecast accuracy on holdout set:\n")
cat(" RMSE:", round(rmse, 2), "\n")
cat(" MAE:", round(mae, 2), "\n")
cat(" MAPE:", round(mape, 2), "%\n\n")
# Plot actual vs forecast
par(mfrow = c(1, 1))
plot(test_ts, main = "Holdout Validation: Actual vs Forecast",
ylab = "Sales", xlab = "Time",
col = "black", lwd = 2, ylim = range(c(test_ts, test_forecasts$mean)))
lines(test_forecasts$mean, col = "red", lwd = 2, lty = 2)
legend("topleft",
legend = c("Actual", "Forecast"),
col = c("black", "red"),
lwd = 2, lty = c(1, 2),
bty = "n")
cat("Holdout validation plot generated.\n")
cat("Model demonstrates good forecast accuracy on unseen data.\n\n")
# ============================================================================
# 11. FINAL SUMMARY
# ============================================================================
cat("=== FINAL SUMMARY ===\n\n")
cat("Data: 120 monthly observations with trend and seasonality\n")
cat("Final model: ARIMA(0,1,1)(0,1,1)[12] (Airline model)\n")
cat(" - (0,1,1): Non-seasonal component(d=1 differencing, MA(1))\n")
cat(" - (0,1,1)[12]: Seasonal component(D=1 seasonal differencing, SMA(1))\n\n")
cat("Model diagnostics:\n")
cat(" - Ljung-Box p-value:", round(ljung_box$p.value, 4), "(>0.05 = white noise residuals)\n")
cat(" - Shapiro-Wilk p-value:", round(shapiro$p.value, 4), "(>0.05 = normal residuals)\n")
cat(" - AIC:", round(final_model$aic, 2), "\n")
cat(" - BIC:", round(final_model$bic, 2), "\n\n")
cat("Forecast accuracy(24-month holdout):\n")
cat(" - RMSE:", round(rmse, 2), "\n")
cat(" - MAE:", round(mae, 2), "\n")
cat(" - MAPE:", round(mape, 2), "%\n\n")
cat("Interpretation:\n")
cat(" - Model successfully captures trend and seasonality\n")
cat(" - Residuals are white noise(no remaining autocorrelation)\n")
cat(" - Forecasts include prediction intervals reflecting uncertainty\n")
cat(" - Forecast accuracy acceptable for business planning\n\n")
cat("ARIMA analysis complete.\n")
# ============================================================================
# END OF ARIMA EXAMPLE
# ============================================================================Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Differencing Strike (d) — Mathematically 'level' the series until the unit root is neutralized.
- ADF / KPSS Audit — Mandatory checks to verify stationarity after every transformation.
- ARCH / GARCH Pivot — Model the 'Width' of future uncertainty if variance is time-varying.
- Generalized Additive Models (GAMs) — Apply smoothing splines to the temporal predictor.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
- Vary p, d, q orders systematically
- Compare with seasonal ARIMA if periodicity present
- Ljung-Box test on residuals for remaining autocorrelation
- Check residual normality for prediction intervals
- Rolling window re-estimation for stability
ARIMA models univariate time series. Post-hoc involves model diagnostics and comparison.
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
The 'Temporal History' Minimum: A minimum of 50-100 consecutive timepoints is essential. ARIMA math (Box-Jenkins) requires enough 'Pulse' to distinguish autoregressive signals from random noise.
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | Low Signal (r=.10) | n ≈ 500 timepoints |
| Medium Effect | Moderate Signal (r=.30) | n ≈ 100 timepoints |
| Large Effect | Strong Signal (r=.50) | n ≈ 50 timepoints |
The 'Stationarity Tax': If your data requires multiple levels of differencing (d > 1), you are effectively 'Losing' timepoints. Increase your temporal depth by 20% to compensate for information loss during the INTEGRATED phase.
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Term | Estimate | SE | z | p | Arima Component |
|---|---|---|---|---|---|
| AR.L1 | 0.75 | 0.08 | 9.38 | < .001 | Autoregressive (Lag 1) |
| MA.L1 | -0.42 | 0.12 | -3.50 | < .001 | Moving Average (Lag 1) |
| Constant | 0.12 | 0.05 | 2.40 | .016 | Drift |
The 'Memory' Component. Measures how strongly yesterday's value predicts today's.
The 'Shock' Component. Measures the impact of random errors from the previous period on current values.
Model Parsimony. Penalizes complexity. Lower values indicate a better balance between fit and simplicity.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Auto-select Best ARIMA Order
model <- forecast::auto.arima(ts_data)
# 2. Generate Forecast
fc <- forecast::forecast(model, h = 12)
plot(fc)
# 3. Residual Check (Independence Audit)
checkresiduals(model)If your residuals show a pattern, your model is 'blind' to a signal. Always use the Ljung-Box test to ensure residuals are White Noise.
# Execute Ljung-Box Audit
Box.test(residuals(model), type = 'Ljung-Box')Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.