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.
Files changed (90) hide show
  1. panelbox/__init__.py +67 -0
  2. panelbox/__version__.py +14 -0
  3. panelbox/cli/__init__.py +0 -0
  4. panelbox/cli/{commands}/__init__.py +0 -0
  5. panelbox/core/__init__.py +0 -0
  6. panelbox/core/base_model.py +164 -0
  7. panelbox/core/formula_parser.py +318 -0
  8. panelbox/core/panel_data.py +387 -0
  9. panelbox/core/results.py +366 -0
  10. panelbox/datasets/__init__.py +0 -0
  11. panelbox/datasets/{data}/__init__.py +0 -0
  12. panelbox/gmm/__init__.py +65 -0
  13. panelbox/gmm/difference_gmm.py +645 -0
  14. panelbox/gmm/estimator.py +562 -0
  15. panelbox/gmm/instruments.py +580 -0
  16. panelbox/gmm/results.py +550 -0
  17. panelbox/gmm/system_gmm.py +621 -0
  18. panelbox/gmm/tests.py +535 -0
  19. panelbox/models/__init__.py +11 -0
  20. panelbox/models/dynamic/__init__.py +0 -0
  21. panelbox/models/iv/__init__.py +0 -0
  22. panelbox/models/static/__init__.py +13 -0
  23. panelbox/models/static/fixed_effects.py +516 -0
  24. panelbox/models/static/pooled_ols.py +298 -0
  25. panelbox/models/static/random_effects.py +512 -0
  26. panelbox/report/__init__.py +61 -0
  27. panelbox/report/asset_manager.py +410 -0
  28. panelbox/report/css_manager.py +472 -0
  29. panelbox/report/exporters/__init__.py +15 -0
  30. panelbox/report/exporters/html_exporter.py +440 -0
  31. panelbox/report/exporters/latex_exporter.py +510 -0
  32. panelbox/report/exporters/markdown_exporter.py +446 -0
  33. panelbox/report/renderers/__init__.py +11 -0
  34. panelbox/report/renderers/static/__init__.py +0 -0
  35. panelbox/report/renderers/static_validation_renderer.py +341 -0
  36. panelbox/report/report_manager.py +502 -0
  37. panelbox/report/template_manager.py +337 -0
  38. panelbox/report/transformers/__init__.py +0 -0
  39. panelbox/report/transformers/static/__init__.py +0 -0
  40. panelbox/report/validation_transformer.py +449 -0
  41. panelbox/standard_errors/__init__.py +0 -0
  42. panelbox/templates/__init__.py +0 -0
  43. panelbox/templates/assets/css/base_styles.css +382 -0
  44. panelbox/templates/assets/css/report_components.css +747 -0
  45. panelbox/templates/assets/js/tab-navigation.js +161 -0
  46. panelbox/templates/assets/js/utils.js +276 -0
  47. panelbox/templates/common/footer.html +24 -0
  48. panelbox/templates/common/header.html +44 -0
  49. panelbox/templates/common/meta.html +5 -0
  50. panelbox/templates/validation/interactive/index.html +272 -0
  51. panelbox/templates/validation/interactive/partials/charts.html +58 -0
  52. panelbox/templates/validation/interactive/partials/methodology.html +201 -0
  53. panelbox/templates/validation/interactive/partials/overview.html +146 -0
  54. panelbox/templates/validation/interactive/partials/recommendations.html +101 -0
  55. panelbox/templates/validation/interactive/partials/test_results.html +231 -0
  56. panelbox/utils/__init__.py +0 -0
  57. panelbox/utils/formatting.py +172 -0
  58. panelbox/utils/matrix_ops.py +233 -0
  59. panelbox/utils/statistical.py +173 -0
  60. panelbox/validation/__init__.py +58 -0
  61. panelbox/validation/base.py +175 -0
  62. panelbox/validation/cointegration/__init__.py +0 -0
  63. panelbox/validation/cross_sectional_dependence/__init__.py +13 -0
  64. panelbox/validation/cross_sectional_dependence/breusch_pagan_lm.py +222 -0
  65. panelbox/validation/cross_sectional_dependence/frees.py +297 -0
  66. panelbox/validation/cross_sectional_dependence/pesaran_cd.py +188 -0
  67. panelbox/validation/heteroskedasticity/__init__.py +13 -0
  68. panelbox/validation/heteroskedasticity/breusch_pagan.py +222 -0
  69. panelbox/validation/heteroskedasticity/modified_wald.py +172 -0
  70. panelbox/validation/heteroskedasticity/white.py +208 -0
  71. panelbox/validation/instruments/__init__.py +0 -0
  72. panelbox/validation/robustness/__init__.py +0 -0
  73. panelbox/validation/serial_correlation/__init__.py +13 -0
  74. panelbox/validation/serial_correlation/baltagi_wu.py +220 -0
  75. panelbox/validation/serial_correlation/breusch_godfrey.py +260 -0
  76. panelbox/validation/serial_correlation/wooldridge_ar.py +200 -0
  77. panelbox/validation/specification/__init__.py +16 -0
  78. panelbox/validation/specification/chow.py +273 -0
  79. panelbox/validation/specification/hausman.py +264 -0
  80. panelbox/validation/specification/mundlak.py +331 -0
  81. panelbox/validation/specification/reset.py +273 -0
  82. panelbox/validation/unit_root/__init__.py +0 -0
  83. panelbox/validation/validation_report.py +257 -0
  84. panelbox/validation/validation_suite.py +401 -0
  85. panelbox-0.2.0.dist-info/METADATA +337 -0
  86. panelbox-0.2.0.dist-info/RECORD +90 -0
  87. panelbox-0.2.0.dist-info/WHEEL +5 -0
  88. panelbox-0.2.0.dist-info/entry_points.txt +2 -0
  89. panelbox-0.2.0.dist-info/licenses/LICENSE +21 -0
  90. 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
+ ]