panelbox 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panelbox/__init__.py +67 -0
- panelbox/__version__.py +14 -0
- panelbox/cli/__init__.py +0 -0
- panelbox/cli/{commands}/__init__.py +0 -0
- panelbox/core/__init__.py +0 -0
- panelbox/core/base_model.py +164 -0
- panelbox/core/formula_parser.py +318 -0
- panelbox/core/panel_data.py +387 -0
- panelbox/core/results.py +366 -0
- panelbox/datasets/__init__.py +0 -0
- panelbox/datasets/{data}/__init__.py +0 -0
- panelbox/gmm/__init__.py +65 -0
- panelbox/gmm/difference_gmm.py +645 -0
- panelbox/gmm/estimator.py +562 -0
- panelbox/gmm/instruments.py +580 -0
- panelbox/gmm/results.py +550 -0
- panelbox/gmm/system_gmm.py +621 -0
- panelbox/gmm/tests.py +535 -0
- panelbox/models/__init__.py +11 -0
- panelbox/models/dynamic/__init__.py +0 -0
- panelbox/models/iv/__init__.py +0 -0
- panelbox/models/static/__init__.py +13 -0
- panelbox/models/static/fixed_effects.py +516 -0
- panelbox/models/static/pooled_ols.py +298 -0
- panelbox/models/static/random_effects.py +512 -0
- panelbox/report/__init__.py +61 -0
- panelbox/report/asset_manager.py +410 -0
- panelbox/report/css_manager.py +472 -0
- panelbox/report/exporters/__init__.py +15 -0
- panelbox/report/exporters/html_exporter.py +440 -0
- panelbox/report/exporters/latex_exporter.py +510 -0
- panelbox/report/exporters/markdown_exporter.py +446 -0
- panelbox/report/renderers/__init__.py +11 -0
- panelbox/report/renderers/static/__init__.py +0 -0
- panelbox/report/renderers/static_validation_renderer.py +341 -0
- panelbox/report/report_manager.py +502 -0
- panelbox/report/template_manager.py +337 -0
- panelbox/report/transformers/__init__.py +0 -0
- panelbox/report/transformers/static/__init__.py +0 -0
- panelbox/report/validation_transformer.py +449 -0
- panelbox/standard_errors/__init__.py +0 -0
- panelbox/templates/__init__.py +0 -0
- panelbox/templates/assets/css/base_styles.css +382 -0
- panelbox/templates/assets/css/report_components.css +747 -0
- panelbox/templates/assets/js/tab-navigation.js +161 -0
- panelbox/templates/assets/js/utils.js +276 -0
- panelbox/templates/common/footer.html +24 -0
- panelbox/templates/common/header.html +44 -0
- panelbox/templates/common/meta.html +5 -0
- panelbox/templates/validation/interactive/index.html +272 -0
- panelbox/templates/validation/interactive/partials/charts.html +58 -0
- panelbox/templates/validation/interactive/partials/methodology.html +201 -0
- panelbox/templates/validation/interactive/partials/overview.html +146 -0
- panelbox/templates/validation/interactive/partials/recommendations.html +101 -0
- panelbox/templates/validation/interactive/partials/test_results.html +231 -0
- panelbox/utils/__init__.py +0 -0
- panelbox/utils/formatting.py +172 -0
- panelbox/utils/matrix_ops.py +233 -0
- panelbox/utils/statistical.py +173 -0
- panelbox/validation/__init__.py +58 -0
- panelbox/validation/base.py +175 -0
- panelbox/validation/cointegration/__init__.py +0 -0
- panelbox/validation/cross_sectional_dependence/__init__.py +13 -0
- panelbox/validation/cross_sectional_dependence/breusch_pagan_lm.py +222 -0
- panelbox/validation/cross_sectional_dependence/frees.py +297 -0
- panelbox/validation/cross_sectional_dependence/pesaran_cd.py +188 -0
- panelbox/validation/heteroskedasticity/__init__.py +13 -0
- panelbox/validation/heteroskedasticity/breusch_pagan.py +222 -0
- panelbox/validation/heteroskedasticity/modified_wald.py +172 -0
- panelbox/validation/heteroskedasticity/white.py +208 -0
- panelbox/validation/instruments/__init__.py +0 -0
- panelbox/validation/robustness/__init__.py +0 -0
- panelbox/validation/serial_correlation/__init__.py +13 -0
- panelbox/validation/serial_correlation/baltagi_wu.py +220 -0
- panelbox/validation/serial_correlation/breusch_godfrey.py +260 -0
- panelbox/validation/serial_correlation/wooldridge_ar.py +200 -0
- panelbox/validation/specification/__init__.py +16 -0
- panelbox/validation/specification/chow.py +273 -0
- panelbox/validation/specification/hausman.py +264 -0
- panelbox/validation/specification/mundlak.py +331 -0
- panelbox/validation/specification/reset.py +273 -0
- panelbox/validation/unit_root/__init__.py +0 -0
- panelbox/validation/validation_report.py +257 -0
- panelbox/validation/validation_suite.py +401 -0
- panelbox-0.2.0.dist-info/METADATA +337 -0
- panelbox-0.2.0.dist-info/RECORD +90 -0
- panelbox-0.2.0.dist-info/WHEEL +5 -0
- panelbox-0.2.0.dist-info/entry_points.txt +2 -0
- panelbox-0.2.0.dist-info/licenses/LICENSE +21 -0
- panelbox-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Matrix operations for panel econometrics.
|
|
3
|
+
|
|
4
|
+
This module provides optimized matrix operations commonly used in
|
|
5
|
+
panel data estimation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from typing import Tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def add_intercept(X: np.ndarray) -> np.ndarray:
|
|
14
|
+
"""
|
|
15
|
+
Add intercept column to design matrix.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
X : np.ndarray
|
|
20
|
+
Design matrix without intercept
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
np.ndarray
|
|
25
|
+
Design matrix with intercept as first column
|
|
26
|
+
"""
|
|
27
|
+
n = X.shape[0]
|
|
28
|
+
return np.column_stack([np.ones(n), X])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def demean_matrix(
|
|
32
|
+
X: np.ndarray,
|
|
33
|
+
groups: np.ndarray
|
|
34
|
+
) -> np.ndarray:
|
|
35
|
+
"""
|
|
36
|
+
Demean matrix by groups (within transformation).
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
X : np.ndarray
|
|
41
|
+
Matrix to demean (n x k)
|
|
42
|
+
groups : np.ndarray
|
|
43
|
+
Group identifiers (length n)
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
np.ndarray
|
|
48
|
+
Demeaned matrix
|
|
49
|
+
"""
|
|
50
|
+
X_demeaned = X.copy()
|
|
51
|
+
unique_groups = np.unique(groups)
|
|
52
|
+
|
|
53
|
+
for group in unique_groups:
|
|
54
|
+
mask = groups == group
|
|
55
|
+
group_mean = X[mask].mean(axis=0)
|
|
56
|
+
X_demeaned[mask] -= group_mean
|
|
57
|
+
|
|
58
|
+
return X_demeaned
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def compute_ols(
|
|
62
|
+
y: np.ndarray,
|
|
63
|
+
X: np.ndarray,
|
|
64
|
+
weights: np.ndarray = None
|
|
65
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
66
|
+
"""
|
|
67
|
+
Compute OLS estimates.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
y : np.ndarray
|
|
72
|
+
Dependent variable (n x 1 or length n)
|
|
73
|
+
X : np.ndarray
|
|
74
|
+
Design matrix (n x k)
|
|
75
|
+
weights : np.ndarray, optional
|
|
76
|
+
Observation weights
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
beta : np.ndarray
|
|
81
|
+
Coefficient estimates (k x 1)
|
|
82
|
+
resid : np.ndarray
|
|
83
|
+
Residuals (n x 1)
|
|
84
|
+
fitted : np.ndarray
|
|
85
|
+
Fitted values (n x 1)
|
|
86
|
+
"""
|
|
87
|
+
# Ensure y is column vector
|
|
88
|
+
if y.ndim == 1:
|
|
89
|
+
y = y.reshape(-1, 1)
|
|
90
|
+
|
|
91
|
+
if weights is not None:
|
|
92
|
+
# Weighted least squares
|
|
93
|
+
W = np.diag(np.sqrt(weights))
|
|
94
|
+
y_w = W @ y
|
|
95
|
+
X_w = W @ X
|
|
96
|
+
beta = np.linalg.lstsq(X_w, y_w, rcond=None)[0]
|
|
97
|
+
else:
|
|
98
|
+
# Ordinary least squares
|
|
99
|
+
beta = np.linalg.lstsq(X, y, rcond=None)[0]
|
|
100
|
+
|
|
101
|
+
# Compute fitted values and residuals
|
|
102
|
+
fitted = X @ beta
|
|
103
|
+
resid = y - fitted
|
|
104
|
+
|
|
105
|
+
return beta, resid.ravel(), fitted.ravel()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def compute_vcov_nonrobust(
|
|
109
|
+
X: np.ndarray,
|
|
110
|
+
resid: np.ndarray,
|
|
111
|
+
df_resid: int
|
|
112
|
+
) -> np.ndarray:
|
|
113
|
+
"""
|
|
114
|
+
Compute non-robust covariance matrix.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
X : np.ndarray
|
|
119
|
+
Design matrix (n x k)
|
|
120
|
+
resid : np.ndarray
|
|
121
|
+
Residuals (length n)
|
|
122
|
+
df_resid : int
|
|
123
|
+
Degrees of freedom for residuals
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
np.ndarray
|
|
128
|
+
Covariance matrix (k x k)
|
|
129
|
+
"""
|
|
130
|
+
# Estimate of error variance
|
|
131
|
+
s2 = np.sum(resid ** 2) / df_resid
|
|
132
|
+
|
|
133
|
+
# Covariance matrix: s^2 (X'X)^{-1}
|
|
134
|
+
XtX_inv = np.linalg.inv(X.T @ X)
|
|
135
|
+
vcov = s2 * XtX_inv
|
|
136
|
+
|
|
137
|
+
return vcov
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def compute_rsquared(
|
|
141
|
+
y: np.ndarray,
|
|
142
|
+
fitted: np.ndarray,
|
|
143
|
+
resid: np.ndarray,
|
|
144
|
+
has_intercept: bool = True
|
|
145
|
+
) -> Tuple[float, float]:
|
|
146
|
+
"""
|
|
147
|
+
Compute R-squared and adjusted R-squared.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
y : np.ndarray
|
|
152
|
+
Dependent variable
|
|
153
|
+
fitted : np.ndarray
|
|
154
|
+
Fitted values
|
|
155
|
+
resid : np.ndarray
|
|
156
|
+
Residuals
|
|
157
|
+
has_intercept : bool, default=True
|
|
158
|
+
Whether model includes intercept
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
rsquared : float
|
|
163
|
+
R-squared
|
|
164
|
+
rsquared_adj : float
|
|
165
|
+
Adjusted R-squared
|
|
166
|
+
"""
|
|
167
|
+
# Total sum of squares
|
|
168
|
+
if has_intercept:
|
|
169
|
+
tss = np.sum((y - y.mean()) ** 2)
|
|
170
|
+
else:
|
|
171
|
+
tss = np.sum(y ** 2)
|
|
172
|
+
|
|
173
|
+
# Residual sum of squares
|
|
174
|
+
rss = np.sum(resid ** 2)
|
|
175
|
+
|
|
176
|
+
# R-squared
|
|
177
|
+
rsquared = 1 - (rss / tss) if tss > 0 else 0.0
|
|
178
|
+
|
|
179
|
+
return rsquared
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def compute_panel_rsquared(
|
|
183
|
+
y: np.ndarray,
|
|
184
|
+
fitted: np.ndarray,
|
|
185
|
+
resid: np.ndarray,
|
|
186
|
+
groups: np.ndarray
|
|
187
|
+
) -> Tuple[float, float, float]:
|
|
188
|
+
"""
|
|
189
|
+
Compute panel-specific R-squared measures.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
y : np.ndarray
|
|
194
|
+
Dependent variable
|
|
195
|
+
fitted : np.ndarray
|
|
196
|
+
Fitted values
|
|
197
|
+
resid : np.ndarray
|
|
198
|
+
Residuals
|
|
199
|
+
groups : np.ndarray
|
|
200
|
+
Group identifiers
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
rsquared_within : float
|
|
205
|
+
Within R-squared
|
|
206
|
+
rsquared_between : float
|
|
207
|
+
Between R-squared
|
|
208
|
+
rsquared_overall : float
|
|
209
|
+
Overall R-squared
|
|
210
|
+
"""
|
|
211
|
+
# Overall R-squared
|
|
212
|
+
y_mean = y.mean()
|
|
213
|
+
tss_overall = np.sum((y - y_mean) ** 2)
|
|
214
|
+
rss = np.sum(resid ** 2)
|
|
215
|
+
rsquared_overall = 1 - (rss / tss_overall) if tss_overall > 0 else 0.0
|
|
216
|
+
|
|
217
|
+
# Within R-squared (variation within groups)
|
|
218
|
+
y_demeaned = demean_matrix(y.reshape(-1, 1), groups).ravel()
|
|
219
|
+
fitted_demeaned = demean_matrix(fitted.reshape(-1, 1), groups).ravel()
|
|
220
|
+
tss_within = np.sum(y_demeaned ** 2)
|
|
221
|
+
ess_within = np.sum(fitted_demeaned ** 2)
|
|
222
|
+
rsquared_within = ess_within / tss_within if tss_within > 0 else 0.0
|
|
223
|
+
|
|
224
|
+
# Between R-squared (variation between group means)
|
|
225
|
+
unique_groups = np.unique(groups)
|
|
226
|
+
y_means = np.array([y[groups == g].mean() for g in unique_groups])
|
|
227
|
+
fitted_means = np.array([fitted[groups == g].mean() for g in unique_groups])
|
|
228
|
+
y_grand_mean = y.mean()
|
|
229
|
+
tss_between = np.sum((y_means - y_grand_mean) ** 2)
|
|
230
|
+
ess_between = np.sum((fitted_means - y_grand_mean) ** 2)
|
|
231
|
+
rsquared_between = ess_between / tss_between if tss_between > 0 else 0.0
|
|
232
|
+
|
|
233
|
+
return rsquared_within, rsquared_between, rsquared_overall
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Statistical functions for panel econometrics.
|
|
3
|
+
|
|
4
|
+
This module provides statistical functions for hypothesis testing
|
|
5
|
+
and inference in panel models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy import stats
|
|
10
|
+
from typing import Tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def compute_tstat(
|
|
14
|
+
coef: float,
|
|
15
|
+
se: float
|
|
16
|
+
) -> float:
|
|
17
|
+
"""
|
|
18
|
+
Compute t-statistic.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
coef : float
|
|
23
|
+
Coefficient estimate
|
|
24
|
+
se : float
|
|
25
|
+
Standard error
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
float
|
|
30
|
+
t-statistic
|
|
31
|
+
"""
|
|
32
|
+
return coef / se if se > 0 else np.nan
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def compute_pvalue(
|
|
36
|
+
tstat: float,
|
|
37
|
+
df: int,
|
|
38
|
+
two_sided: bool = True
|
|
39
|
+
) -> float:
|
|
40
|
+
"""
|
|
41
|
+
Compute p-value for t-statistic.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
tstat : float
|
|
46
|
+
t-statistic
|
|
47
|
+
df : int
|
|
48
|
+
Degrees of freedom
|
|
49
|
+
two_sided : bool, default=True
|
|
50
|
+
Whether to compute two-sided p-value
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
float
|
|
55
|
+
p-value
|
|
56
|
+
"""
|
|
57
|
+
if two_sided:
|
|
58
|
+
return 2 * (1 - stats.t.cdf(np.abs(tstat), df))
|
|
59
|
+
else:
|
|
60
|
+
return 1 - stats.t.cdf(tstat, df)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def compute_fstat(
|
|
64
|
+
rss_restricted: float,
|
|
65
|
+
rss_unrestricted: float,
|
|
66
|
+
df_diff: int,
|
|
67
|
+
df_resid: int
|
|
68
|
+
) -> Tuple[float, float]:
|
|
69
|
+
"""
|
|
70
|
+
Compute F-statistic for nested model comparison.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
rss_restricted : float
|
|
75
|
+
Residual sum of squares from restricted model
|
|
76
|
+
rss_unrestricted : float
|
|
77
|
+
Residual sum of squares from unrestricted model
|
|
78
|
+
df_diff : int
|
|
79
|
+
Difference in degrees of freedom (number of restrictions)
|
|
80
|
+
df_resid : int
|
|
81
|
+
Degrees of freedom for residuals (unrestricted model)
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
fstat : float
|
|
86
|
+
F-statistic
|
|
87
|
+
pvalue : float
|
|
88
|
+
p-value
|
|
89
|
+
"""
|
|
90
|
+
numerator = (rss_restricted - rss_unrestricted) / df_diff
|
|
91
|
+
denominator = rss_unrestricted / df_resid
|
|
92
|
+
|
|
93
|
+
fstat = numerator / denominator if denominator > 0 else np.nan
|
|
94
|
+
pvalue = 1 - stats.f.cdf(fstat, df_diff, df_resid)
|
|
95
|
+
|
|
96
|
+
return fstat, pvalue
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def wald_test(
|
|
100
|
+
restrictions: np.ndarray,
|
|
101
|
+
params: np.ndarray,
|
|
102
|
+
vcov: np.ndarray,
|
|
103
|
+
q: np.ndarray = None
|
|
104
|
+
) -> Tuple[float, float, int]:
|
|
105
|
+
"""
|
|
106
|
+
Compute Wald test for linear restrictions.
|
|
107
|
+
|
|
108
|
+
Tests H0: R*beta = q
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
restrictions : np.ndarray
|
|
113
|
+
Restriction matrix R (m x k)
|
|
114
|
+
params : np.ndarray
|
|
115
|
+
Parameter estimates (k x 1)
|
|
116
|
+
vcov : np.ndarray
|
|
117
|
+
Covariance matrix (k x k)
|
|
118
|
+
q : np.ndarray, optional
|
|
119
|
+
RHS of restrictions (m x 1). Default is zeros.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
statistic : float
|
|
124
|
+
Wald chi-squared statistic
|
|
125
|
+
pvalue : float
|
|
126
|
+
p-value
|
|
127
|
+
df : int
|
|
128
|
+
Degrees of freedom
|
|
129
|
+
"""
|
|
130
|
+
if params.ndim == 1:
|
|
131
|
+
params = params.reshape(-1, 1)
|
|
132
|
+
|
|
133
|
+
if q is None:
|
|
134
|
+
q = np.zeros((restrictions.shape[0], 1))
|
|
135
|
+
elif q.ndim == 1:
|
|
136
|
+
q = q.reshape(-1, 1)
|
|
137
|
+
|
|
138
|
+
# Compute R*beta - q
|
|
139
|
+
restriction_value = restrictions @ params - q
|
|
140
|
+
|
|
141
|
+
# Compute (R * Vcov * R')^{-1}
|
|
142
|
+
middle = restrictions @ vcov @ restrictions.T
|
|
143
|
+
middle_inv = np.linalg.inv(middle)
|
|
144
|
+
|
|
145
|
+
# Wald statistic: (R*beta - q)' (R * Vcov * R')^{-1} (R*beta - q)
|
|
146
|
+
statistic = float(restriction_value.T @ middle_inv @ restriction_value)
|
|
147
|
+
|
|
148
|
+
# Degrees of freedom
|
|
149
|
+
df = restrictions.shape[0]
|
|
150
|
+
|
|
151
|
+
# p-value from chi-squared distribution
|
|
152
|
+
pvalue = 1 - stats.chi2.cdf(statistic, df)
|
|
153
|
+
|
|
154
|
+
return statistic, pvalue, df
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def compute_chi2_pvalue(statistic: float, df: int) -> float:
|
|
158
|
+
"""
|
|
159
|
+
Compute p-value for chi-squared statistic.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
statistic : float
|
|
164
|
+
Chi-squared statistic
|
|
165
|
+
df : int
|
|
166
|
+
Degrees of freedom
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
float
|
|
171
|
+
p-value
|
|
172
|
+
"""
|
|
173
|
+
return 1 - stats.chi2.cdf(statistic, df)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation tests for panel data models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from panelbox.validation.base import ValidationTest, ValidationTestResult
|
|
6
|
+
from panelbox.validation.validation_suite import ValidationSuite
|
|
7
|
+
from panelbox.validation.validation_report import ValidationReport
|
|
8
|
+
|
|
9
|
+
# Specification tests
|
|
10
|
+
from panelbox.validation.specification.hausman import HausmanTest, HausmanTestResult
|
|
11
|
+
from panelbox.validation.specification.mundlak import MundlakTest
|
|
12
|
+
from panelbox.validation.specification.reset import RESETTest
|
|
13
|
+
from panelbox.validation.specification.chow import ChowTest
|
|
14
|
+
|
|
15
|
+
# Serial correlation tests
|
|
16
|
+
from panelbox.validation.serial_correlation.wooldridge_ar import WooldridgeARTest
|
|
17
|
+
from panelbox.validation.serial_correlation.breusch_godfrey import BreuschGodfreyTest
|
|
18
|
+
from panelbox.validation.serial_correlation.baltagi_wu import BaltagiWuTest
|
|
19
|
+
|
|
20
|
+
# Heteroskedasticity tests
|
|
21
|
+
from panelbox.validation.heteroskedasticity.modified_wald import ModifiedWaldTest
|
|
22
|
+
from panelbox.validation.heteroskedasticity.breusch_pagan import BreuschPaganTest
|
|
23
|
+
from panelbox.validation.heteroskedasticity.white import WhiteTest
|
|
24
|
+
|
|
25
|
+
# Cross-sectional dependence tests
|
|
26
|
+
from panelbox.validation.cross_sectional_dependence.pesaran_cd import PesaranCDTest
|
|
27
|
+
from panelbox.validation.cross_sectional_dependence.breusch_pagan_lm import BreuschPaganLMTest
|
|
28
|
+
from panelbox.validation.cross_sectional_dependence.frees import FreesTest
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Base classes
|
|
32
|
+
'ValidationTest',
|
|
33
|
+
'ValidationTestResult',
|
|
34
|
+
'ValidationSuite',
|
|
35
|
+
'ValidationReport',
|
|
36
|
+
|
|
37
|
+
# Specification tests
|
|
38
|
+
'HausmanTest',
|
|
39
|
+
'HausmanTestResult',
|
|
40
|
+
'MundlakTest',
|
|
41
|
+
'RESETTest',
|
|
42
|
+
'ChowTest',
|
|
43
|
+
|
|
44
|
+
# Serial correlation tests
|
|
45
|
+
'WooldridgeARTest',
|
|
46
|
+
'BreuschGodfreyTest',
|
|
47
|
+
'BaltagiWuTest',
|
|
48
|
+
|
|
49
|
+
# Heteroskedasticity tests
|
|
50
|
+
'ModifiedWaldTest',
|
|
51
|
+
'BreuschPaganTest',
|
|
52
|
+
'WhiteTest',
|
|
53
|
+
|
|
54
|
+
# Cross-sectional dependence tests
|
|
55
|
+
'PesaranCDTest',
|
|
56
|
+
'BreuschPaganLMTest',
|
|
57
|
+
'FreesTest',
|
|
58
|
+
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for validation tests.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ValidationTestResult:
|
|
11
|
+
"""
|
|
12
|
+
Container for validation test results.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
test_name : str
|
|
17
|
+
Name of the test
|
|
18
|
+
statistic : float
|
|
19
|
+
Test statistic value
|
|
20
|
+
pvalue : float
|
|
21
|
+
P-value
|
|
22
|
+
df : int or tuple, optional
|
|
23
|
+
Degrees of freedom
|
|
24
|
+
alpha : float
|
|
25
|
+
Significance level used
|
|
26
|
+
conclusion : str
|
|
27
|
+
Interpretation of test result
|
|
28
|
+
null_hypothesis : str
|
|
29
|
+
Description of H0
|
|
30
|
+
alternative_hypothesis : str
|
|
31
|
+
Description of H1
|
|
32
|
+
metadata : dict
|
|
33
|
+
Additional test-specific information
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
test_name: str,
|
|
39
|
+
statistic: float,
|
|
40
|
+
pvalue: float,
|
|
41
|
+
null_hypothesis: str,
|
|
42
|
+
alternative_hypothesis: str,
|
|
43
|
+
alpha: float = 0.05,
|
|
44
|
+
df: Optional[Any] = None,
|
|
45
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
46
|
+
):
|
|
47
|
+
self.test_name = test_name
|
|
48
|
+
self.statistic = statistic
|
|
49
|
+
self.pvalue = pvalue
|
|
50
|
+
self.df = df
|
|
51
|
+
self.alpha = alpha
|
|
52
|
+
self.null_hypothesis = null_hypothesis
|
|
53
|
+
self.alternative_hypothesis = alternative_hypothesis
|
|
54
|
+
self.metadata = metadata or {}
|
|
55
|
+
# Add 'df' to metadata for convenience if provided
|
|
56
|
+
if df is not None and 'df' not in self.metadata:
|
|
57
|
+
self.metadata['df'] = df
|
|
58
|
+
|
|
59
|
+
# Determine conclusion
|
|
60
|
+
if pvalue < alpha:
|
|
61
|
+
self.reject_null = True
|
|
62
|
+
self.conclusion = (
|
|
63
|
+
f"Reject H0 at {alpha*100:.0f}% level. {alternative_hypothesis}"
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
self.reject_null = False
|
|
67
|
+
self.conclusion = (
|
|
68
|
+
f"Fail to reject H0 at {alpha*100:.0f}% level. {null_hypothesis}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def details(self):
|
|
73
|
+
"""Alias for metadata for backwards compatibility."""
|
|
74
|
+
return self.metadata
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
"""String representation."""
|
|
78
|
+
return self.summary()
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
"""Repr."""
|
|
82
|
+
return f"{self.test_name}Result(statistic={self.statistic:.3f}, pvalue={self.pvalue:.4f})"
|
|
83
|
+
|
|
84
|
+
def summary(self) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Generate formatted summary.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
str
|
|
91
|
+
Formatted test results
|
|
92
|
+
"""
|
|
93
|
+
lines = []
|
|
94
|
+
lines.append("=" * 70)
|
|
95
|
+
lines.append(self.test_name.upper())
|
|
96
|
+
lines.append("=" * 70)
|
|
97
|
+
lines.append("")
|
|
98
|
+
lines.append(f"H0: {self.null_hypothesis}")
|
|
99
|
+
lines.append(f"H1: {self.alternative_hypothesis}")
|
|
100
|
+
lines.append("")
|
|
101
|
+
lines.append("-" * 70)
|
|
102
|
+
lines.append(f"{'Test Statistic':<30} {self.statistic:>15.4f}")
|
|
103
|
+
lines.append(f"{'P-value':<30} {self.pvalue:>15.4f}")
|
|
104
|
+
|
|
105
|
+
if self.df is not None:
|
|
106
|
+
if isinstance(self.df, tuple):
|
|
107
|
+
df_str = f"({self.df[0]}, {self.df[1]})"
|
|
108
|
+
else:
|
|
109
|
+
df_str = str(self.df)
|
|
110
|
+
lines.append(f"{'Degrees of Freedom':<30} {df_str:>15}")
|
|
111
|
+
|
|
112
|
+
lines.append("-" * 70)
|
|
113
|
+
lines.append("")
|
|
114
|
+
lines.append(f"Conclusion: {self.conclusion}")
|
|
115
|
+
|
|
116
|
+
# Add metadata if present
|
|
117
|
+
if self.metadata:
|
|
118
|
+
lines.append("")
|
|
119
|
+
lines.append("Additional Information:")
|
|
120
|
+
for key, value in self.metadata.items():
|
|
121
|
+
if isinstance(value, float):
|
|
122
|
+
lines.append(f" {key}: {value:.4f}")
|
|
123
|
+
else:
|
|
124
|
+
lines.append(f" {key}: {value}")
|
|
125
|
+
|
|
126
|
+
lines.append("=" * 70)
|
|
127
|
+
lines.append("")
|
|
128
|
+
|
|
129
|
+
return "\n".join(lines)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ValidationTest(ABC):
|
|
133
|
+
"""
|
|
134
|
+
Abstract base class for validation tests.
|
|
135
|
+
|
|
136
|
+
All validation tests should inherit from this class and implement
|
|
137
|
+
the `run()` method.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, results: 'PanelResults'):
|
|
141
|
+
"""
|
|
142
|
+
Initialize validation test.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
results : PanelResults
|
|
147
|
+
Results object from panel model estimation
|
|
148
|
+
"""
|
|
149
|
+
self.results = results
|
|
150
|
+
self.resid = results.resid
|
|
151
|
+
self.fittedvalues = results.fittedvalues
|
|
152
|
+
self.params = results.params
|
|
153
|
+
self.nobs = results.nobs
|
|
154
|
+
self.n_entities = results.n_entities
|
|
155
|
+
self.n_periods = results.n_periods
|
|
156
|
+
self.model_type = results.model_type
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def run(self, alpha: float = 0.05, **kwargs) -> ValidationTestResult:
|
|
160
|
+
"""
|
|
161
|
+
Run the validation test.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
alpha : float, default=0.05
|
|
166
|
+
Significance level
|
|
167
|
+
**kwargs
|
|
168
|
+
Test-specific keyword arguments
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
ValidationTestResult
|
|
173
|
+
Test results
|
|
174
|
+
"""
|
|
175
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-sectional dependence tests for panel models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from panelbox.validation.cross_sectional_dependence.pesaran_cd import PesaranCDTest
|
|
6
|
+
from panelbox.validation.cross_sectional_dependence.breusch_pagan_lm import BreuschPaganLMTest
|
|
7
|
+
from panelbox.validation.cross_sectional_dependence.frees import FreesTest
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'PesaranCDTest',
|
|
11
|
+
'BreuschPaganLMTest',
|
|
12
|
+
'FreesTest',
|
|
13
|
+
]
|