Skip to content

Commit 5c2b663

Browse files
authored
Merge pull request #340 from bashtage/system-lr
ENH: Add likelihood ratio test for SUR
2 parents 2d81262 + 8788a1b commit 5c2b663

File tree

9 files changed

+164
-14
lines changed

9 files changed

+164
-14
lines changed

doc/source/changes.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
Change Log
22
==========
3+
Version 4.24
4+
------------
5+
* Added :func:`~linearmodels.system.results.SystemResults.breusch_pagan` and
6+
:func:`~linearmodels.system.results.SystemResults.likelihood_ratio` to test
7+
whether the shock covariance is diagonal.
38

49
Version 4.21
510
------------
@@ -8,7 +13,7 @@ Version 4.21
813
inference.
914
* Added ``rank_check`` argument to panel-data models that allows the rank
1015
check to be skipped. Estimating a model that is rank deficient may result
11-
in unreliable estiamtes and so caution is needed if using this option.
16+
in unreliable estimates and so caution is needed if using this option.
1217
* Changed the rank check to use :func:`numpy.linalg.lstsq` which is better
1318
aligned with parameter estimation than the :func:`numpy.linalg.svd`-based
1419
:func:`numpy.linalg.matrix_rank`.

doc/source/names_wordlist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ rubin
4040
basmann
4141
sargan
4242
wooldridge
43+
Breusch
44+
Pagan
45+
breusch
46+
pagan

doc/source/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,4 @@ numpy
203203
str
204204
debias
205205
pyhdfe
206+
absorber

doc/source/system/convert-lyx.cmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"C:\Program Files (x86)\LyX 2.3\bin\lyx" --force-overwrite --export latex mathematical-detail.lyx
1+
"c:\Program Files\LyX 2.3\bin \lyx" --force-overwrite --export latex mathematical-detail.lyx
22
pandoc -s mathematical-detail.tex -o mathematical-detail.rst
33
copy /Y mathematical-detail.rst mathematical-detail-pre.txt
44
del mathematical-detail.rst

doc/source/system/mathematical-detail.lyx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,40 @@ ic weighting formula immediately above.
10681068

10691069
\end_layout
10701070

1071+
\begin_layout Subsection*
1072+
Testing Covariance and Correlations
1073+
\end_layout
1074+
1075+
\begin_layout Standard
1076+
Two tests are available to test whether the residual covariance is diagonal.
1077+
These are useful diagnostics when considering GLS estimation.
1078+
If the tests reject the null, then the data suggest that GLS estimation
1079+
should improve efficiency as long as the regressors are not all common.
1080+
If the null is not rejected, then the covariance is not statistically different
1081+
from a diagonal covariance and there are unlikely to be gains to using
1082+
GLS.
1083+
The Breusch-Pagan test directly examines the correlations of the residuals,
1084+
and is defined as
1085+
\begin_inset Formula
1086+
\[
1087+
N\left(\sum_{i=1}^{K}\sum_{j=i+1}^{K}\hat{\rho}\right)\sim\chi_{K\left(K-1\right)/2}^{2}.
1088+
\]
1089+
1090+
\end_inset
1091+
1092+
The likelihood ratio is defined as the difference between the log determinants
1093+
of a diagonal covariance matrix and the full unrestricted covariance matrix,
1094+
\begin_inset Formula
1095+
\[
1096+
N\left(\sum_{i=1}^{K}\ln\hat{\sigma}_{i}^{2}-\ln\left|\hat{\Sigma}\right|\right)=N\left(\sum_{i=1}^{K}\ln\left|\hat{\Sigma}\odot I_{K}\right|-\ln\left|\hat{\Sigma}\right|\right)\sim\chi_{K\left(K-1\right)/2}^{2}.
1097+
\]
1098+
1099+
\end_inset
1100+
1101+
The asymptotic distribution of the likelihood ratio test requires homoskedastici
1102+
ty.
1103+
\end_layout
1104+
10711105
\begin_layout Subsection*
10721106
System Measures of Fit (
10731107
\begin_inset Formula $R^{2}$

doc/source/system/mathematical-detail.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,29 @@ parameters will simplify to
371371

372372
.. math:: \widehat{Var\left(\hat{\beta}\right)}=N^{-1}\left(\frac{X^{\prime}Z}{N}\hat{W}^{-1}\frac{Z^{\prime}X}{N}\right)^{-1}.
373373

374+
Testing Covariance and Correlations
375+
-----------------------------------
376+
377+
Two tests are available to test whether the residual covariance is
378+
diagonal. These are useful diagnostics when considering GLS estimation.
379+
If the tests reject the null, then the data suggest that GLS estimation
380+
should improve efficiency as long as the regressors are not all common.
381+
If the null is not rejected, then the covariance is not statistically
382+
different from a diagonal covariance and there are unlikely to be gains
383+
to using GLS. The Breusch-Pagan test directly examines the correlations
384+
of the residuals, and is defined as
385+
386+
.. math:: N\left(\sum_{i=1}^{K}\sum_{j=i+1}^{K}\hat{\rho}\right)\sim\chi_{K\left(K-1\right)/2}^{2}.
387+
388+
The likelihood ratio is defined as the difference between the log
389+
determinants of a diagonal covariance matrix and the full unrestricted
390+
covariance matrix,
391+
392+
.. math:: N\left(\sum_{i=1}^{K}\ln\hat{\sigma}_{i}^{2}-\ln\left|\hat{\Sigma}\right|\right)=N\left(\sum_{i=1}^{K}\ln\left|\hat{\Sigma}\odot I_{K}\right|-\ln\left|\hat{\Sigma}\right|\right)\sim\chi_{K\left(K-1\right)/2}^{2}.
393+
394+
The asymptotic distribution of the likelihood ratio test requires
395+
homoskedasticity.
396+
374397
System Measures of Fit (:math:`R^{2}`)
375398
--------------------------------------
376399

linearmodels/panel/model.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ class _PanelModelBase(object):
263263
Flag indicating whether to perform a rank check on the exogenous
264264
variables to ensure that the model is identified. Skipping this
265265
check can reduce the time required to validate a model specification.
266-
Results may be numerically instable if this check is skipped and
266+
Results may be numerically unstable if this check is skipped and
267267
the matrix is not full rank.
268268
"""
269269

@@ -402,7 +402,7 @@ def _check_exog_rank(self) -> int:
402402
raise ValueError(
403403
"exog does not have full column rank. If you wish to proceed with "
404404
"model estimation irrespective of the numerical accuracy of "
405-
"coefficient estiamtes, you can set rank_check=False."
405+
"coefficient estimates, you can set rank_check=False."
406406
)
407407
return rank_of_x
408408

@@ -804,7 +804,7 @@ class PooledOLS(_PanelModelBase):
804804
Flag indicating whether to perform a rank check on the exogenous
805805
variables to ensure that the model is identified. Skipping this
806806
check can reduce the time required to validate a model specification.
807-
Results may be numerically instable if this check is skipped and
807+
Results may be numerically unstable if this check is skipped and
808808
the matrix is not full rank.
809809
810810
Notes
@@ -854,7 +854,7 @@ def from_formula(
854854
Flag indicating whether to perform a rank check on the exogenous
855855
variables to ensure that the model is identified. Skipping this
856856
check can reduce the time required to validate a model
857-
specification. Results may be numerically instable if this check
857+
specification. Results may be numerically unstable if this check
858858
is skipped and the matrix is not full rank.
859859
860860
Returns
@@ -1102,7 +1102,7 @@ class PanelOLS(_PanelModelBase):
11021102
Flag indicating whether to perform a rank check on the exogenous
11031103
variables to ensure that the model is identified. Skipping this
11041104
check can reduce the time required to validate a model specification.
1105-
Results may be numerically instable if this check is skipped and
1105+
Results may be numerically unstable if this check is skipped and
11061106
the matrix is not full rank.
11071107
11081108
Notes
@@ -1331,7 +1331,7 @@ def from_formula(
13311331
Flag indicating whether to perform a rank check on the exogenous
13321332
variables to ensure that the model is identified. Skipping this
13331333
check can reduce the time required to validate a model
1334-
specification. Results may be numerically instable if this check
1334+
specification. Results may be numerically unstable if this check
13351335
is skipped and the matrix is not full rank.
13361336
13371337
Returns
@@ -2165,7 +2165,7 @@ def from_formula(
21652165
Flag indicating whether to perform a rank check on the exogenous
21662166
variables to ensure that the model is identified. Skipping this
21672167
check can reduce the time required to validate a model
2168-
specification. Results may be numerically instable if this check
2168+
specification. Results may be numerically unstable if this check
21692169
is skipped and the matrix is not full rank.
21702170
21712171
Returns
@@ -2463,7 +2463,7 @@ def from_formula(
24632463
Flag indicating whether to perform a rank check on the exogenous
24642464
variables to ensure that the model is identified. Skipping this
24652465
check can reduce the time required to validate a model
2466-
specification. Results may be numerically instable if this check
2466+
specification. Results may be numerically unstable if this check
24672467
is skipped and the matrix is not full rank.
24682468
24692469
Returns
@@ -2556,7 +2556,7 @@ def from_formula(
25562556
Flag indicating whether to perform a rank check on the exogenous
25572557
variables to ensure that the model is identified. Skipping this
25582558
check can reduce the time required to validate a model
2559-
specification. Results may be numerically instable if this check
2559+
specification. Results may be numerically unstable if this check
25602560
is skipped and the matrix is not full rank.
25612561
25622562
Returns
@@ -3028,7 +3028,7 @@ def from_formula(
30283028
Flag indicating whether to perform a rank check on the exogenous
30293029
variables to ensure that the model is identified. Skipping this
30303030
check can reduce the time required to validate a model
3031-
specification. Results may be numerically instable if this check
3031+
specification. Results may be numerically unstable if this check
30323032
is skipped and the matrix is not full rank.
30333033
30343034
Returns

linearmodels/system/results.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ def breusch_pagan(self) -> Union[WaldTestStatistic, InvalidTestStatistic]:
523523
Returns
524524
-------
525525
WaldTestStatistic
526-
Test statistic for null all correlationsare zero.
526+
Test statistic for null all correlations are zero.
527527
528528
Notes
529529
-----
@@ -540,7 +540,11 @@ def breusch_pagan(self) -> Union[WaldTestStatistic, InvalidTestStatistic]:
540540
541541
where :math:`\hat{\rho}_{ij}` is the sample residual correlation
542542
between series i and j. n is the sample size. It has an asymptotic
543-
:math:`\chi^2_{k(k-1)/2}` distribution.
543+
:math:`\chi^2_{k(k-1)/2}` distribution. See [1]_ for details.
544+
545+
References
546+
----------
547+
.. [1] Greene, William H. Econometric analysis. Pearson Education, 2003.
544548
"""
545549
name = "Breusch-Pagan LM Test"
546550
resids = self.resids
@@ -561,6 +565,60 @@ def breusch_pagan(self) -> Union[WaldTestStatistic, InvalidTestStatistic]:
561565
name=name,
562566
)
563567

568+
def likelihood_ratio(self) -> Union[WaldTestStatistic, InvalidTestStatistic]:
569+
r"""
570+
Likelihood ratio test of no cross-correlation
571+
572+
Returns
573+
-------
574+
WaldTestStatistic
575+
Test statistic that the covariance is diagonal.
576+
577+
Notes
578+
-----
579+
The null hypothesis is that the shock covariance matrix is diagonal,
580+
and so all correlations are 0. In this case, there are no gains to
581+
using GLS estimation in the system estimator.
582+
583+
When the null is rejected, there should be efficiency gains to using
584+
GLS as long the regressors are not common to all models.
585+
586+
The LR test statistic is defined as
587+
588+
.. math::
589+
590+
LR=n\left[\sum_{i=1}^{k}\log\hat{\sigma}_i^2
591+
-\log\left|\hat{\Sigma}\right|\right]
592+
593+
where :math:`\hat{\sigma}_i^2` is the sample residual variance for
594+
series i and :math:`\hat{\Sigma}` is the residual covariance.
595+
n is the sample size. It has an asymptotic :math:`\chi^2_{k(k-1)/2}`
596+
distribution. The asymptotic distribution of the likelihood ratio
597+
test requires homoskedasticity. See [1]_ for details.
598+
599+
References
600+
----------
601+
.. [1] Greene, William H. Econometric analysis. Pearson Education, 2003.
602+
"""
603+
name = "Likelihood Ratio Test for Diagonal Covariance"
604+
resids = np.asarray(self.resids)
605+
if resids.shape[1] == 1:
606+
return InvalidTestStatistic(
607+
"Cannot test covariance structure when the system contains a single "
608+
"dependent variable.",
609+
name=name,
610+
)
611+
sigma = resids.T @ resids / resids.shape[0]
612+
nobs, k = resids.shape
613+
_, logdet = np.linalg.slogdet(sigma)
614+
stat = nobs * (np.log(np.diag(sigma)).sum() - logdet)
615+
return WaldTestStatistic(
616+
stat,
617+
"Covariance is diagonal",
618+
k * (k - 1) // 2,
619+
name=name,
620+
)
621+
564622

565623
class SystemEquationResult(_CommonResults):
566624
"""

linearmodels/tests/system/test_sur.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,3 +878,28 @@ def test_brequsch_pagan(k):
878878
assert_allclose(stat.pval, 1.0 - scipy.stats.chi2(3).cdf(direct))
879879
assert "Residuals are uncorrelated" in stat.null
880880
assert "Breusch-Pagan" in str(stat)
881+
882+
883+
@pytest.mark.parametrize("k", [1, 3])
884+
def test_likelihood_ratio(k):
885+
eqns = generate_data(k=k)
886+
mod = SUR(eqns)
887+
res = mod.fit()
888+
stat = res.likelihood_ratio()
889+
if k == 1:
890+
assert isinstance(stat, InvalidTestStatistic)
891+
assert "Likelihood Ratio Test" in str(stat)
892+
assert np.isnan(stat.stat)
893+
return
894+
eps = np.asarray(res.resids)
895+
sigma = eps.T @ eps / eps.shape[0]
896+
nobs = res.resids.shape[0]
897+
direct = np.linalg.slogdet(sigma * np.eye(k))[1]
898+
direct -= np.linalg.slogdet(sigma)[1]
899+
direct *= nobs
900+
assert isinstance(stat, WaldTestStatistic)
901+
assert_allclose(stat.stat, direct)
902+
assert stat.df == 3
903+
assert_allclose(stat.pval, 1.0 - scipy.stats.chi2(3).cdf(direct))
904+
assert "Covariance is diagonal" in stat.null
905+
assert "Likelihood Ratio Test" in str(stat)

0 commit comments

Comments
 (0)