Model card — Panel unit-root tests¶
Family: panel_unit_root (Levin-Lin-Chu, Im-Pesaran-Shin, Fisher/Maddala-Wu-Choi)
One entry point, three classic tests of the joint null "every cross-section
unit has a unit root." Stacking many short series into a panel buys the power
a single 60-quarter history cannot: where one ADF test on one country barely
distinguishes a persistent stationary series from a random walk, pooling or
averaging the evidence across N units sharpens the verdict enormously. All
three tests share the same front half — the already-validated per-unit augmented
Dickey-Fuller regression (adf, matched to statsmodels at
1e-8) — and differ only in how they combine the per-unit results.
test |
Combines | Null / alternative | Panel shape |
|---|---|---|---|
"llc" |
pooled ADF, common ρ | all units I(1) / all units share one stationary root | balanced (common T) |
"ips" (default) |
average of per-unit t-statistics | all units I(1) / some units stationary | unbalanced OK |
"fisher" |
combines per-unit p-values | all units I(1) / some units stationary | unbalanced OK |
What it estimates¶
panel_unit_root(data, test="llc")— Levin-Lin-Chu (2002): a pooled ADF with a single common autoregressive root δ across units. It removes per-unit means/trends, scales each unit by its own long-run-variance ratio, runs one pooled OLS for δ, and applies the tabulatedmu*/sigma*bias adjustment (LLC Table 2) to givet*_delta ~ N(0,1)in the left tail. The homogeneous-alternative test: it asks whether the common root is below one.panel_unit_root(data, test="ips")— Im-Pesaran-Shin (2003): fit a separate ADF to each unit, average the t-statistics intot_bar, then standardize toW_tbar ~ N(0,1)with the tabulated mean/variance of the individual ADF t-statistic (IPS Table 3). The heterogeneous-alternative test: it allows each unit its own root and rejects when enough units are stationary.panel_unit_root(data, test="fisher")— the Fisher-type combination (Maddala-Wu 1999; Choi 2001): from the per-unit ADF p-valuesp_i, form Maddala-Wu'sP = -2 Σ ln p_i ~ χ²(2N)(right tail) and Choi's inverse-normalZ = N^{-1/2} Σ Φ⁻¹(p_i) ~ N(0,1)(left tail). A deterministic, exact-arithmetic function of the per-unit p-values — it inherits the ADF's validated accuracy directly and is the crate's strongest-anchored panel statistic.
Assumptions¶
- Cross-sectional independence — the defining caveat of the whole family.
All three are first-generation tests: their asymptotic null distributions
are derived assuming the units' errors are independent across
i. When a common factor (a global business cycle, a common shock) correlates the units at each date — the rule, not the exception, in macro panels — the true variance of the pooled/averaged statistic is wrong, and the tests suffer size distortion (typically over-rejection). This is a genuine limitation, not a small-sample nuisance; the second-generation remedies (Pesaran's 2007 CIPS, Bai-Ng PANIC, Moon-Perron) are on the roadmap. Before trusting a rejection, ask whether a common factor is plausible (Pesaran's CD test is the standard pre-check), and treat a first-generation "stationary" verdict on a strongly co-moving panel with suspicion. - LLC imposes a common root. Its alternative is that every unit is stationary with the same δ. If the panel mixes stationary and unit-root units, or the stationary units have very different roots, LLC is misspecified — IPS or Fisher, whose alternative is heterogeneous, are the honest choice.
- The deterministic specification matters.
regression="c"(constant) vs"ct"(constant + trend) changes the ADF null distribution and the tabulated moments; choose it to match the data's trend behavior, exactly as for a single-series ADF."n"(no deterministic term) is invalid for IPS (the IPS moment tables are tabulated only for thec/ctcases). - Lag augmentation per unit. Each unit's ADF needs enough lagged
differences to whiten its residuals; too few and the size is wrong, too many
and power bleeds away. The default per-unit auto-AIC handles heterogeneous
serial correlation, but cap
max_lagson shortTso a unit cannot spend its degrees of freedom on spurious lags.
When to use¶
"ips"— the default, and the right first move for most macro panels: heterogeneous alternative, unbalanced panels allowed, and a transparent average of per-unit evidence. Reach for it when you believe some units may be stationary and others not."fisher"— when you want the most robust p-value combination, unbalanced panels, and a statistic whose accuracy is inherited directly from the validated single-series ADF. Choi's Z is the better-sized variant for largeN; Maddala-Wu's χ² is the classic."llc"— when theory or design genuinely implies a common root across units (e.g. the same law of motion imposed on every unit), and the panel is balanced. Its pooling buys power, but only if the homogeneity is real.- Not a substitute for the ADF+KPSS quadrant per unit. If you care about
which units are nonstationary, run the single-series workflow
(
check_stationarity) unit by unit; the panel test answers the joint question only.
Key arguments and defaults¶
| Argument | Default | Notes |
|---|---|---|
data |
— | balanced N × T array (rows = units) or a list of 1-D per-unit series (unbalanced OK for ips/fisher; llc needs a common length) |
test |
"ips" |
"ips", "llc", or "fisher" |
regression |
"c" |
"c" constant, "ct" constant + trend, "n" none ("n" invalid for ips) |
lags |
None |
None = per-unit auto-AIC; an int = a fixed common lag; or "aic"/"bic"/"t-stat" |
max_lags |
None |
cap on the per-unit auto lag search |
lrv_kernel / lrv_bandwidth |
"bartlett" / None |
the long-run-variance kernel LLC uses to scale each unit; ignored by IPS/Fisher |
How to read the output¶
Every call returns statistic, p_value, the per-unit vectors
per_unit_tstat / per_unit_pvalue / per_unit_lags / per_unit_nobs,
n_units, and the echoed regression, plus test-specific extras:
"ips"→t_bar(the raw average per-unit t-statistic, before standardization);statisticis the standardizedW_tbar ~ N(0,1)."llc"→delta_hat(the estimated common root adjustment),t_delta(the raw pooled t before the bias adjustment),s_n(the average long-run to short-run standard-deviation ratio), andt_bar_periods;statisticis the bias-adjustedt*_delta ~ N(0,1)."fisher"→maddala_wu(theχ²(2N)statistic, equal tostatistic),choi_z(the inverse-normal statistic), andchoi_z_pvalue. The Maddala-Wup_valueis the right-tail χ²; Choi's is the left-tail normal.
A small p_value rejects the joint unit-root null in favor of (at least
some) stationarity. Inspect per_unit_pvalue to see which units drive the
verdict, and per_unit_lags / per_unit_nobs to check the augmentation and
effective sample were sane per unit.
Failure modes¶
- Reading a rejection as "the panel is stationary" under cross-sectional dependence. A common factor inflates the test's size; the rejection may be the factor talking, not stationarity. This is the family's headline weakness — check for cross-sectional dependence first, and prefer the second-generation tests (roadmap) when it is present.
- LLC on a heterogeneous panel. Its common-root alternative is misspecified when units have different dynamics; the pooled δ is a hard-to-interpret average and the verdict can mislead. Use IPS/Fisher.
regression="n"with IPS. Raises — the IPS moment tables exist only forc/ct. This is a correct refusal, not a bug.- Unbalanced input to LLC. LLC needs a common
T; hand it ragged per-unit series and it raises. Balance the panel or switch to IPS/Fisher. - Over-long auto lags on short
T. Uncapped AIC can pick a large lag on a short unit, distorting its ADF; setmax_lags.
Validated against¶
Golden fixtures reproduce R plm::purtest's Wtbar (IPS), levinlin
(LLC), and madwu / invnormal (Fisher) statistics to floating-point
precision, and — for Fisher — an independent statsmodels/SciPy reference on the
p-value combination. The per-unit ADF is reused verbatim from tsecon-diag
(matched to statsmodels adfuller at 1e-8); only the combination layer and
the transcribed IPS-2003 Table 3 and LLC-2002 Table 2 moment families are new.
Fixture: tsecon-panelroot.json;
tests: golden.rs,
validation.rs,
properties.rs. See the
validation matrix.
References¶
- Levin, A., Lin, C.-F. & Chu, C.-S. J. (2002). "Unit root tests in panel data: asymptotic and finite-sample properties." J. Econometrics 108.
- Im, K. S., Pesaran, M. H. & Shin, Y. (2003). "Testing for unit roots in heterogeneous panels." J. Econometrics 115.
- Maddala, G. S. & Wu, S. (1999). "A comparative study of unit root tests with panel data and a new simple test." Oxford Bull. Econ. Stat. 61.
- Choi, I. (2001). "Unit root tests for panel data." J. Int. Money Finance 20.
- Pesaran, M. H. (2007). "A simple panel unit root test in the presence of cross-section dependence." J. Applied Econometrics 22. (The second-generation CIPS answer to the independence caveat — roadmap.)
See the guide: Panel Time Series.
Runnable example¶
import numpy as np
import tsecon
rng = np.random.default_rng(31)
N, T = 12, 80
# ---- a STATIONARY panel: each unit an AR(1) with its own |rho| < 1 ----
stat = np.empty((N, T))
for i in range(N):
rho = rng.uniform(0.5, 0.85)
u = np.empty(T); u[0] = rng.standard_normal()
for t in range(1, T):
u[t] = rho * u[t - 1] + rng.standard_normal()
stat[i] = u
# ---- a UNIT-ROOT panel: each unit its own independent random walk ----
rw = np.cumsum(rng.standard_normal((N, T)), axis=1)
print("STATIONARY panel (each row an AR(1), |rho|<1) -- should reject:")
for test in ["ips", "llc", "fisher"]:
r = tsecon.panel_unit_root(stat, test=test, regression="c", max_lags=4)
print(f" {test:6s} stat={r['statistic']:+9.4f} p={r['p_value']:.3g}")
print("UNIT-ROOT panel (each row a random walk) -- should NOT reject:")
for test in ["ips", "llc", "fisher"]:
r = tsecon.panel_unit_root(rw, test=test, regression="c", max_lags=4)
print(f" {test:6s} stat={r['statistic']:+9.4f} p={r['p_value']:.3g}")
# test-specific extras (each test carries its own named quantities)
ips = tsecon.panel_unit_root(stat, test="ips", regression="c", max_lags=4)
print("IPS t_bar:", round(ips["t_bar"], 4), " n_units:", ips["n_units"])
fis = tsecon.panel_unit_root(stat, test="fisher", regression="c", max_lags=4)
print("Fisher Maddala-Wu:", round(fis["maddala_wu"], 3),
" Choi Z:", round(fis["choi_z"], 4), " Choi p:", round(fis["choi_z_pvalue"], 3))
llc = tsecon.panel_unit_root(stat, test="llc", regression="c", max_lags=4)
print("LLC delta_hat:", round(llc["delta_hat"], 4), " t_delta:", round(llc["t_delta"], 4))
# unbalanced list input for IPS/Fisher (LLC needs a common T)
unb = [rw[i, : T - int(rng.integers(0, 12))] for i in range(N)]
ru = tsecon.panel_unit_root(unb, test="ips", regression="c", max_lags=4)
print("unbalanced IPS p:", round(ru["p_value"], 3),
" nobs range:", int(np.min(ru["per_unit_nobs"])), "-", int(np.max(ru["per_unit_nobs"])))
Expected output:
STATIONARY panel (each row an AR(1), |rho|<1) -- should reject:
ips stat= -7.0591 p=8.38e-13
llc stat= -5.9169 p=1.64e-09
fisher stat=+105.9066 p=2.9e-12
UNIT-ROOT panel (each row a random walk) -- should NOT reject:
ips stat= -0.0954 p=0.462
llc stat= -0.2452 p=0.403
fisher stat= +22.0614 p=0.576
IPS t_bar: -3.2816 n_units: 12
Fisher Maddala-Wu: 105.907 Choi Z: -7.3878 Choi p: 0.0
LLC delta_hat: -0.2375 t_delta: -10.8384
unbalanced IPS p: 0.288 nobs range: 68 - 79
All three tests strongly reject the joint unit-root null on the stationary panel
(p ≈ 1e-12) and comfortably fail to reject it on the independent random walks
(p ≈ 0.4-0.6) — the power the panel buys, and the size it keeps when the units
are independent. The last two calls show the unbalanced-list input path
(different nobs per unit) that IPS and Fisher accept but LLC does not.