Skip to content

Diagnostics API Reference

Module

Import: from panelbox.diagnostics.<submodule> import ... Source: panelbox/diagnostics/

Overview

The diagnostics module provides function-based diagnostic tests for panel data, organized into four categories. Many of these tests complement the class-based tests in panelbox.validation.

Category Functions Purpose
Unit Root hadri_test, breitung_test, panel_unit_root_test Test stationarity
Cointegration kao_test, pedroni_test, westerlund_test Long-run relationships
Specification j_test, cox_test, wald_encompassing_test, likelihood_ratio_test Non-nested model comparison
Spatial MoranIPanelTest, LocalMoranI, lm_lag_test, lm_error_test Spatial dependence
Quantile QuantileRegressionDiagnostics Quantile regression goodness-of-fit

Diagnostics vs. Validation

Some tests exist in both modules with different interfaces:

  • panelbox.diagnosticsfunction-based, takes raw data (DataFrames)
  • panelbox.validationclass-based, takes fitted model results

Use whichever interface fits your workflow.


Unit Root Tests

hadri_test()

Hadri (2000) LM test for stationarity. Unlike most unit root tests, the null hypothesis is stationarity.

def hadri_test(
    data: pd.DataFrame,
    variable: str,
    entity_col: str = "entity",
    time_col: str = "time",
    trend: str = "c",        # "c" (constant) or "ct" (constant + trend)
    robust: bool = True,
    alpha: float = 0.05,
) -> HadriResult

Returns: HadriResult

Attribute Type Description
statistic float Standardized LM statistic
pvalue float p-value (one-sided)
reject bool Whether to reject H0
lm_statistic float Raw LM statistic
individual_lm np.ndarray Individual LM statistics per entity
n_entities int Number of entities
n_time int Number of time periods
trend str Trend specification used
robust bool Whether robust variant was used
  • H0: All panels are stationary
  • H1: Some panels contain unit roots

breitung_test()

Breitung (2000) unit root test, robust to heterogeneity in intercepts and trends.

def breitung_test(
    data: pd.DataFrame,
    variable: str,
    entity_col: str = "entity",
    time_col: str = "time",
    trend: str = "ct",
    alpha: float = 0.05,
) -> BreitungResult

Returns: BreitungResult

Attribute Type Description
statistic float Standardized test statistic
pvalue float p-value
reject bool Whether to reject H0
raw_statistic float Raw (unstandardized) statistic
n_entities int Number of entities
n_time int Number of time periods
trend str Trend specification
  • H0: All panels contain unit roots
  • H1: All panels are stationary

panel_unit_root_test()

Run multiple unit root tests at once and compare results.

def panel_unit_root_test(
    data: pd.DataFrame,
    variable: str,
    entity_col: str = "entity",
    time_col: str = "time",
    test: str | list = "all",
    trend: str = "c",
    alpha: float = 0.05,
    **kwargs,
) -> PanelUnitRootResult

Parameters:

Parameter Type Default Description
test str \| list "all" Test(s) to run: "hadri", "breitung", "llc", "ips", "fisher", "all"

Returns: PanelUnitRootResult with results dict, variable, n_entities, n_time, tests_run

Example:

from panelbox.diagnostics.unit_root import panel_unit_root_test, hadri_test

# Run all unit root tests
result = panel_unit_root_test(data, variable="gdp", entity_col="country", time_col="year")
for test_name, test_result in result.results.items():
    print(f"{test_name}: stat={test_result.statistic:.4f}, p={test_result.pvalue:.4f}")

# Run individual test
hadri = hadri_test(data, variable="gdp", entity_col="country", time_col="year", robust=True)
print(hadri)

Interpreting Unit Root Test Results

Unit Root Tests (LLC, IPS, etc.) Hadri Test Conclusion
Reject H0 Don't reject H0 Stationary
Don't reject H0 Reject H0 Unit Root
Mixed results Mixed results Inconclusive — try different trend specifications

Cointegration Tests

kao_test()

Kao (1999) residual-based cointegration test. Assumes homogeneous cointegrating vectors across entities.

def kao_test(
    data: pd.DataFrame,
    entity_col: str,
    time_col: str,
    y_var: str,
    x_vars: str | list[str],
    method: str = "adf",       # "df", "adf", "all"
    trend: str = "c",
    lags: int = 1,
) -> KaoResult

Returns: KaoResult

Attribute Type Description
statistic dict[str, float] Test statistics by method
pvalue dict[str, float] p-values by method
critical_values dict Critical values at 1%, 5%, 10%
method str Method used
trend str Trend specification
lags int Number of lags
n_entities int Number of entities
n_time int Number of time periods
  • H0: No cointegration
  • H1: Cointegration exists (homogeneous vector)

pedroni_test()

Pedroni (1999, 2004) cointegration test with 7 statistics. Allows heterogeneous cointegrating vectors across entities.

def pedroni_test(
    data: pd.DataFrame,
    entity_col: str,
    time_col: str,
    y_var: str,
    x_vars: str | list[str],
    method: str = "all",
    trend: str = "c",
    lags: int = 4,
) -> PedroniResult

Returns: PedroniResult

Attribute Type Description
statistic dict[str, float] All 7 test statistics
pvalue dict[str, float] p-values for each statistic
critical_values dict Critical values

Test Statistics:

Category Statistic Description
Within-dimension (Panel) panel_v Variance ratio
panel_rho Phillips-Perron rho
panel_pp Phillips-Perron t
panel_adf Augmented Dickey-Fuller
Between-dimension (Group) group_rho Group Phillips-Perron rho
group_pp Group Phillips-Perron t
group_adf Group ADF

Warning

The panel_v statistic may over-reject in finite samples (T < 50). Rely more on PP and ADF statistics.

westerlund_test()

Westerlund (2007) ECM-based cointegration test with bootstrap p-values.

def westerlund_test(
    data: pd.DataFrame,
    entity_col: str,
    time_col: str,
    y_var: str,
    x_vars: str | list[str],
    method: str = "all",           # "Gt", "Ga", "Pt", "Pa", "all"
    trend: str = "c",              # "n", "c", "ct"
    lags: int | str = "auto",
    max_lags: int = 4,
    lag_criterion: str = "aic",    # "aic" or "bic"
    n_bootstrap: int = 1000,
    random_state: int | None = None,
    use_bootstrap: bool = True,
) -> WesterlundResult

Returns: WesterlundResult

Attribute Type Description
statistic dict[str, float] Test statistics (Gt, Ga, Pt, Pa)
pvalue dict[str, float] p-values (bootstrap or asymptotic)
n_bootstrap int Number of bootstrap replications

Example:

from panelbox.diagnostics.cointegration import kao_test, pedroni_test, westerlund_test

# Step 1: Test for cointegration using multiple tests
kao = kao_test(data, "country", "year", y_var="gdp", x_vars=["investment", "labor"])
pedroni = pedroni_test(data, "country", "year", y_var="gdp", x_vars=["investment", "labor"])
westerlund = westerlund_test(data, "country", "year", y_var="gdp", x_vars=["investment", "labor"])

# Step 2: Count rejections (majority rule)
print(f"Kao: {kao.pvalue}")
print(f"Pedroni: {pedroni.pvalue}")
print(f"Westerlund: {westerlund.pvalue}")
  1. Pre-test: Confirm variables are I(1) using unit root tests
  2. Run multiple tests: Kao, Pedroni, and Westerlund
  3. Majority rule: Count rejections across test families
  4. If cointegrated: Estimate Panel VECM or DOLS

Specification Tests

j_test()

Davidson-MacKinnon J-test for comparing non-nested models.

def j_test(
    result1,
    result2,
    direction: str = "both",    # "forward", "reverse", "both"
    model1_name: str | None = None,
    model2_name: str | None = None,
) -> JTestResult

Returns: JTestResult

Attribute Type Description
forward dict \| None Test of model 1 against model 2
reverse dict \| None Test of model 2 against model 1
model1_name str Name of first model
model2_name str Name of second model

Interpretation:

Forward (H0: M1) Reverse (H0: M2) Conclusion
Don't reject Reject Prefer Model 1
Reject Don't reject Prefer Model 2
Don't reject Don't reject Cannot distinguish
Reject Reject Neither model adequate

cox_test()

Cox (1961, 1962) test for non-nested model comparison.

def cox_test(
    result1,
    result2,
    model1_name: str | None = None,
    model2_name: str | None = None,
) -> EncompassingResult

wald_encompassing_test()

Wald encompassing test for nested or non-nested models.

def wald_encompassing_test(
    result_restricted,
    result_unrestricted,
    model_restricted_name: str | None = None,
    model_unrestricted_name: str | None = None,
) -> EncompassingResult

likelihood_ratio_test()

Likelihood ratio test for nested models.

def likelihood_ratio_test(
    result_restricted,
    result_unrestricted,
    model_restricted_name: str | None = None,
    model_unrestricted_name: str | None = None,
) -> EncompassingResult

EncompassingResult

@dataclass
class EncompassingResult:
    test_name: str
    statistic: float
    pvalue: float
    df: float | None
    null_hypothesis: str
    alternative: str
    model1_name: str
    model2_name: str
    additional_info: dict[str, Any]

Spatial Tests

MoranIPanelTest

Moran's I test for spatial autocorrelation in panel residuals.

class MoranIPanelTest(
    residuals: np.ndarray,
    W: np.ndarray,
    entity_ids,
    time_ids,
)

Methods:

Method Returns Description
run(method="pooled") MoranIResult \| dict Run Moran's I test

MoranIResult

@dataclass
class MoranIResult:
    statistic: float
    expected_value: float
    variance: float
    z_score: float
    pvalue: float
    conclusion: str
    additional_info: dict

LocalMoranI

Local Indicators of Spatial Association (LISA).

class LocalMoranI(
    values: np.ndarray,
    W: np.ndarray,
    entity_ids,
)

Methods:

Method Returns Description
run(permutations=999) LISAResult Run local Moran's I

LISAResult

@dataclass
class LISAResult:
    local_i: np.ndarray        # Local Moran's I for each observation
    pvalues: np.ndarray        # Pseudo p-values
    z_values: np.ndarray       # z-values
    Wz_values: np.ndarray      # Spatially lagged z-values
    entity_ids: np.ndarray     # Entity identifiers

LM Tests for Spatial Dependence

Lagrange Multiplier tests to choose between spatial lag and spatial error models.

def lm_lag_test(residuals, X, W, **kwargs) -> LMTestResult
def lm_error_test(residuals, X, W, **kwargs) -> LMTestResult
def robust_lm_lag_test(residuals, X, W, **kwargs) -> LMTestResult
def robust_lm_error_test(residuals, X, W, **kwargs) -> LMTestResult
def run_lm_tests(model_result, W, alpha=0.05) -> dict

LMTestResult

@dataclass
class LMTestResult:
    test_name: str
    statistic: float
    pvalue: float
    df: int
    conclusion: str

Example:

from panelbox.diagnostics import run_lm_tests

# Run all spatial LM tests
lm_results = run_lm_tests(ols_result, W, alpha=0.05)
for name, result in lm_results.items():
    if isinstance(result, LMTestResult):
        print(f"{result.test_name}: stat={result.statistic:.4f}, p={result.pvalue:.4f}")

Decision Rule:

LM Lag LM Error Robust LM Lag Robust LM Error Conclusion
Significant Not sig. Spatial Lag model
Not sig. Significant Spatial Error model
Significant Significant Significant Not sig. Spatial Lag model
Significant Significant Not sig. Significant Spatial Error model

Quantile Regression Diagnostics

QuantileRegressionDiagnostics

Diagnostic tests for quantile regression models.

class QuantileRegressionDiagnostics(
    model,
    params: np.ndarray,
    tau: float = 0.5,
)

Methods:

Method Returns Description
pseudo_r2() float Koenker-Machado pseudo-R²
goodness_of_fit(n_bins=10) dict Goodness-of-fit measures
symmetry_test() tuple[float, float] Test for conditional symmetry
goodness_of_fit_test(n_bins=10) tuple[float, float] Formal GoF test (stat, p-value)

See Also