*! version 1.3.0 07mar2026

Title

xtcsdq -- Tests of no cross-sectional error dependence
in panel quantile regressions

Syntax

Mode 1 -- Internal estimation (runs QR internally, tests residuals):

xtcsdq depvar indepvars [if] [in],
quantiles(numlist)
[options]

Mode 2 -- User-supplied residuals (from any QR estimator):

xtcsdq,
residuals(varlist)
quantiles(numlist)
[options]

Mode 3 -- Post-estimation (reads model from last e() results):

xtcsdq,
post
quantiles(numlist)
[options]

* quantiles(numlist) list of quantiles in (0,1) at which to test

residuals(varlist) (Mode 2) pre-computed QR residuals, one variable per quantile

post (Mode 3) auto-generate residuals from last estimation command

individual (Mode 1 only) estimate QR per unit instead of pooled FE

bandwidth(#) KDE bandwidth; default = 0.35*(NT)^(-0.2)

nocorrection report only T_tau without finite-sample correction

* quantiles() is required in all modes.

Panel data must be declared using xtset before running xtcsdq.

The panel must be strongly balanced.

Description

xtcsdq tests whether the residuals of a panel quantile regression
exhibit cross-sectional dependence (CSD), based on
Demetrescu, Hosseinkouchack & Rodrigues (2023).

This is a residual-based diagnostic test.
It examines pairwise correlations among the quantile regression errors
after fitting a QR model. The variables you supply define the model
specification — they are not tested directly.

Three modes

Mode 1 (varlist): The command estimates a panel QR internally
(pooled FE with unit dummies, or individual-unit), extracts residuals,
then tests them for CSD. The previous estimation command is irrelevant
— Mode 1 always runs its own QR. Use this mode after mmqreg or when
you simply want a quick diagnostic.

Mode 2 (residuals()): You provide pre-computed QR residuals.
This is the most transparent mode: you control exactly which residuals
are tested. One variable per quantile.

Mode 3 (post): The command reads e(depvar) and
e(indepvars) from the previous estimation, re-runs per-panel QR
to generate residuals, then tests them.
Works after xtpqardl, xtcspqardl, qreg, sqreg,
bsqreg, and any command storing e(depvar).

Why residuals, not raw variables?

Testing raw y or x for CSD just asks "are the series correlated?"
— the answer is usually yes with macroeconomic data.

The paper's test asks a different question: "after controlling for x,
is there remaining dependence in the errors?" If yes, the model is
misspecified: unobserved common factors contaminate the error term.

Unlike OLS, CSD in QR errors causes coefficient bias
(not just inference distortion). This makes the CSD test essential
before interpreting panel QR results.

Decision rule:

Fail to reject H0 (large p-value): No CSD → model is adequate.

Reject H0 (small p-value): CSD → switch to factor-augmented estimator
(xtcspqardl or qregpd).

Options

Required

quantiles(numlist) specifies the quantile(s) at which to test.
Each value must be strictly between 0 and 1.
Example: quantiles(0.1 0.25 0.5 0.75 0.9).

Mode selection

residuals(varlist) activates Mode 2. Each variable contains
QR residuals at the corresponding quantile. The number of variables must
equal the number of quantiles.

post activates Mode 3. The command reads e(depvar) and
e(indepvars) or e(b) column names from the last estimation.

Important: The regressor list is saved before the internal QR loop,
so the previous estimation's e() is not overwritten.

Estimation

individual (Mode 1 only) estimates a separate QR for each
cross-sectional unit (heterogeneous slopes), corresponding to Proposition 2.
The default is pooled fixed-effects QR with unit dummies (Proposition 1).

Minimum sample requirement: Individual-unit QR requires
T >> k (number of regressors). With T=15 and k=4,
QR at extreme quantiles (0.25, 0.75) will fail. In this case,
use the default pooled mode or nocorrection.

bandwidth(#) KDE bandwidth for estimating f(q_tau).
Default: 0.35*(NT)^(-0.2). Larger values smooth more.

nocorrection suppresses the corrected statistic T~_tau
and reports only T_tau. Recommended when T < 50, because the
finite-sample correction may overcorrect in small samples.

Remarks

Test statistics

Let û denote the QR residuals. The uncorrected statistic is:

T_tau = [1/sqrt(N(N-1))] × Σ(T × ρ²_ij − 1)

where ρ_ij is the pairwise correlation between demeaned residuals of
units i and j. Under H0 (no CSD), T_tau →d N(0,1).

The corrected statistic adjusts for finite-sample bias:

T~_tau = T_tau − sqrt(N(N-1)/2T) − [τ(1−τ)/f²] × sqrt(N(N-1)/T)

When K > 1 quantiles are tested, the portmanteau statistic
M_K = (1/K) × Σ T_tau_k provides a joint test.

Pooled vs individual

Pooled FE (default): Runs qreg y x1 x2 i.id on all N×T
observations together. Imposes homogeneous slopes across units,
with unit-specific intercepts (fixed effects). Corresponds to
Proposition 1 in the paper.

Individual-unit (individual): Runs qreg y x1 x2 separately
for each unit. Allows heterogeneous slopes. Requires large T.
Corresponds to Proposition 2.

Post-estimation mode (Mode 3) uses per-panel QR, which is equivalent to
individual-unit estimation. Individual = External = Post (they all give
the same test statistic when the same regressors are used).

Sample size guidance

T range Recommendation

T ≥ 100 Use default (both T_tau and T~_tau)
50 ≤ T < 100 Default OK; T~_tau slightly conservative
15 ≤ T < 50 Use nocorrection; rely on T_tau only
T < 15 Use pooled mode only; T too small for per-panel QR

Practical workflow for researchers

Step 1: Estimate your panel QR model.

. xtpqardl y D.x1, lr(L.y x1) tau(0.25 0.50 0.75) pmg

Step 2: Test for CSD in the residuals.

. xtcsdq, post quantiles(0.25 0.50 0.75)

Step 3: Interpret the result.

• If H0 is not rejected: your model is fine.

• If H0 is rejected: switch to a CSD-robust estimator:

. xtcspqardl y D.x1, lr(L.y x1) tau(0.25 0.50 0.75) qccemg

Step 4: Verify the fix.

. xtcsdq, post quantiles(0.25 0.50 0.75)

If CSD persists after xtcspqardl, increase cross-sectional
average lags: cr_lags(5).

Estimator compatibility

Residual extraction reference for panel QR commands:

Command Syntax to get residuals Notes

qreg predict r, residuals works directly
sqreg predict r, residuals works directly
bsqreg predict r, residuals works directly
xtqreg predict r, residuals works directly
qregpd predict r, residuals works directly
rifhdreg predict r, residuals works directly
mmqreg use xtcsdq Mode 1 predict not supported
ivqreg gen r = y - xb manual computation
xtpqardl use xtcsdq, post Mode 3
xtcspqardl use xtcsdq, post Mode 3

Notes on mmqreg

mmqreg (Rios-Avila, 2020) does not have its own predict program.
Stata's default predict does not work after it — neither predict, res
nor predict, residuals. This is because mmqreg estimates a
location-scale model with multiple equation names in e(b).

Solution: Use xtcsdq in Mode 1 (the command runs its own
pooled FE QR internally):

. mmqreg y x1 x2, quantile(25 50 75) absorb(id)

. xtcsdq y x1 x2, quantiles(0.25 0.50 0.75)

Note: Mode 1 runs its own qreg y x1 x2 i.id internally —
it does not use mmqreg's results. The test statistic is
computed from the pooled FE QR residuals, not from mmqreg's
location-scale residuals.

Notes on post-estimation mode

In Mode 3, xtcsdq re-runs per-panel qreg internally using
the regressors from the previous estimation. The test result is based
on these per-panel QR residuals, not on the original estimator's residuals.

This means: if you run qreg, sqreg, or bsqreg before
xtcsdq, post with the same regressors, you get identical
test statistics — because all three produce the same per-panel QR
residuals for testing.

The results will differ between pooled FE (Mode 1 default) and
per-panel (Mode 1 individual, Mode 3, Mode 2 with per-panel
residuals) because these are different models:

Method Model

Mode 1 (default) Homogeneous slopes + FE dummies
Mode 1 (individual) Heterogeneous slopes per unit
Mode 3 (post) Per-panel QR (= heterogeneous)

This is expected behavior: different models produce different residuals,
hence different test statistics. The CSD test is about the
model specification, not the specific estimator.

Examples

Example 1: Quick diagnostic after mmqreg (Mode 1)

Estimate a location-scale QR, then run CSD test using Mode 1
(the command runs its own pooled FE QR for testing):

. webuse nlswork, clear

. drop if missing(ln_wage, age, tenure, hours)

. bysort idcode: gen nobs = _N

. su nobs, meanonly

. keep if nobs == r(max)

. drop nobs

. xtset idcode year

. mmqreg ln_wage age tenure hours, quantile(25 50 75) absorb(idcode)

. xtcsdq ln_wage age tenure hours, quantiles(0.25 0.50 0.75)

Example 2: After xtpqardl (Mode 3 post-estimation)

Panel quantile ARDL → CSD diagnostic:

. xtpqardl y D.x1, lr(L.y x1) tau(0.2 0.5 0.8) pmg

. xtcsdq, post quantiles(0.2 0.5 0.8)

Example 3: After xtcspqardl (Mode 3 — verify CSD removed)

. xtcspqardl y D.x1, lr(L.y x1) tau(0.2 0.5 0.8) qccemg

. xtcsdq, post quantiles(0.2 0.5 0.8)

If CSD persists, increase CSA lags: xtcspqardl ..., cr_lags(5).

Example 4: After qreg (Mode 3)

. qreg y x1 x2, quantile(50)

. xtcsdq, post quantiles(0.25 0.50 0.75)

Example 5: External residuals (Mode 2)

Manual residual computation for full control:

. qui qreg y x1 x2 i.id, quantile(50)

. predict r_50, residuals

. xtcsdq, residuals(r_50) quantiles(0.50)

Example 6: Direct Mode 1 with options

. xtcsdq y x1 x2, quantiles(0.25 0.50 0.75)

/* pooled FE, default */

. xtcsdq y x1 x2, quantiles(0.50) individual

/* per-unit QR */

. xtcsdq y x1 x2, quantiles(0.25 0.50 0.75) nocorrection

/* T<50: skip T~_tau */

Example 7: Full comparison across modes (simulated data)

. clear all

. set seed 12345

. set obs 1000

. gen id = ceil(_n/50)

. bysort id: gen t = _n

. xtset id t

. gen double x1 = rnormal()

. gen double x2 = rnormal()

. bysort t: gen double shock = rnormal() if _n==1

. bysort t: replace shock = shock[1]

. gen double y = 1 + x1 + 0.5*x2 + 0.3*shock + rnormal()

. * Mode 1: pooled FE

. xtcsdq y x1 x2, quantiles(0.25 0.50 0.75)

. * Mode 1: individual

. xtcsdq y x1 x2, quantiles(0.50) individual

. * Mode 3: post-estimation after qreg

. qreg y x1 x2, quantile(50)

. xtcsdq, post quantiles(0.50)

Expected: CSD is detected (DGP includes common shock 0.3*shock).
Pooled FE and individual give different T_tau because they use
different models (homogeneous vs heterogeneous slopes).

Stored results

xtcsdq stores the following in r():

Scalars

r(N) number of cross-sectional units

r(T) number of time periods

r(K) number of quantiles

r(bandwidth) KDE bandwidth used

r(M_K) portmanteau M_K (if K > 1)

r(Mtilde_K) corrected portmanteau M~_K (if K > 1)

r(pval_M) p-value of M_K (if K > 1)

r(pval_Mc) p-value of M~_K (if K > 1)

Matrices

r(T_tau) K × 1 vector of uncorrected test statistics

r(Ttilde_tau) K × 1 vector of corrected statistics

r(pval_T) K × 1 vector of p-values of T_tau

r(pval_Ttilde) K × 1 vector of p-values of T~_tau

r(fhat) K × 1 vector of KDE estimates f_hat

References

Demetrescu, M., Hosseinkouchack, M., and Rodrigues, P. M. M. (2023).
Tests of no cross-sectional error dependence in panel quantile regressions.
Ruhr Economic Papers, No. 1041.

Pesaran, M. H. (2015).
Testing weak cross-sectional dependence in large panels.
Econometric Reviews, 34(6-10), 1089-1117.

Machado, J. A. F. and Santos Silva, J. M. C. (2019).
Quantiles via moments.
Journal of Econometrics, 213(1), 145-173.

Harding, M., Lamarche, C., and Pesaran, M. H. (2020).
Common correlated effects estimation of heterogeneous dynamic panel
quantile regression models.
Journal of Applied Econometrics, 35(3), 294-314.

Rios-Avila, F. (2020).
Recentered influence functions (RIFs) in Stata.
Stata Journal, 20(1), 51-94.

Authors

Dr Merwan Roudane

Email: merwanroudane920@gmail.com

Please cite as:

Roudane, M. (2026). XTCSDQ: Stata module for testing cross-sectional
error dependence in panel quantile regressions.
Available from SSC.

Bug reports and suggestions are welcome.

Also see

Online:
xtpqardl (if installed),
xtcspqardl (if installed),
mmqreg (if installed),
xtqreg (if installed),
qregpd (if installed),
qreg,
xtset