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,512 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Random Effects (GLS) estimator for panel data.
|
|
3
|
+
|
|
4
|
+
This module provides the Random Effects estimator which uses GLS (Generalized Least Squares)
|
|
5
|
+
to account for the variance component structure in panel data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from panelbox.core.base_model import PanelModel
|
|
13
|
+
from panelbox.core.results import PanelResults
|
|
14
|
+
from panelbox.utils.matrix_ops import (
|
|
15
|
+
compute_ols,
|
|
16
|
+
compute_panel_rsquared
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RandomEffects(PanelModel):
|
|
21
|
+
"""
|
|
22
|
+
Random Effects (GLS) estimator for panel data.
|
|
23
|
+
|
|
24
|
+
This estimator assumes that entity-specific effects are uncorrelated with
|
|
25
|
+
the regressors and uses Generalized Least Squares to efficiently estimate
|
|
26
|
+
the model accounting for the variance component structure.
|
|
27
|
+
|
|
28
|
+
The key assumption is E[u_i | X_it] = 0, where u_i is the entity-specific effect.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
formula : str
|
|
33
|
+
Model formula in R-style syntax (e.g., "y ~ x1 + x2")
|
|
34
|
+
data : pd.DataFrame
|
|
35
|
+
Panel data in long format
|
|
36
|
+
entity_col : str
|
|
37
|
+
Name of the column identifying entities
|
|
38
|
+
time_col : str
|
|
39
|
+
Name of the column identifying time periods
|
|
40
|
+
variance_estimator : str, default='swamy-arora'
|
|
41
|
+
Method for estimating variance components:
|
|
42
|
+
- 'swamy-arora': Swamy-Arora estimator (most common)
|
|
43
|
+
- 'walhus': Wallace-Hussain estimator
|
|
44
|
+
- 'amemiya': Amemiya estimator
|
|
45
|
+
- 'nerlove': Nerlove estimator
|
|
46
|
+
weights : np.ndarray, optional
|
|
47
|
+
Observation weights
|
|
48
|
+
|
|
49
|
+
Attributes
|
|
50
|
+
----------
|
|
51
|
+
variance_estimator : str
|
|
52
|
+
Variance estimation method
|
|
53
|
+
sigma2_u : float
|
|
54
|
+
Estimated variance of entity-specific effects (after fitting)
|
|
55
|
+
sigma2_e : float
|
|
56
|
+
Estimated variance of idiosyncratic errors (after fitting)
|
|
57
|
+
theta : float
|
|
58
|
+
GLS transformation parameter (after fitting)
|
|
59
|
+
|
|
60
|
+
Examples
|
|
61
|
+
--------
|
|
62
|
+
>>> import panelbox as pb
|
|
63
|
+
>>> import pandas as pd
|
|
64
|
+
>>>
|
|
65
|
+
>>> # Load data
|
|
66
|
+
>>> data = pd.read_csv('panel_data.csv')
|
|
67
|
+
>>>
|
|
68
|
+
>>> # Estimate Random Effects
|
|
69
|
+
>>> model = pb.RandomEffects("y ~ x1 + x2", data, "firm", "year")
|
|
70
|
+
>>> results = model.fit()
|
|
71
|
+
>>> print(results.summary())
|
|
72
|
+
>>>
|
|
73
|
+
>>> # Access variance components
|
|
74
|
+
>>> print(f"sigma2_u: {model.sigma2_u:.4f}")
|
|
75
|
+
>>> print(f"sigma2_e: {model.sigma2_e:.4f}")
|
|
76
|
+
>>> print(f"theta: {model.theta:.4f}")
|
|
77
|
+
>>>
|
|
78
|
+
>>> # Use different variance estimator
|
|
79
|
+
>>> model_amemiya = pb.RandomEffects(
|
|
80
|
+
... "y ~ x1 + x2", data, "firm", "year",
|
|
81
|
+
... variance_estimator='amemiya'
|
|
82
|
+
... )
|
|
83
|
+
>>> results_amemiya = model_amemiya.fit()
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
formula: str,
|
|
89
|
+
data: pd.DataFrame,
|
|
90
|
+
entity_col: str,
|
|
91
|
+
time_col: str,
|
|
92
|
+
variance_estimator: str = 'swamy-arora',
|
|
93
|
+
weights: Optional[np.ndarray] = None
|
|
94
|
+
):
|
|
95
|
+
super().__init__(formula, data, entity_col, time_col, weights)
|
|
96
|
+
|
|
97
|
+
valid_estimators = ['swamy-arora', 'walhus', 'amemiya', 'nerlove']
|
|
98
|
+
if variance_estimator not in valid_estimators:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"variance_estimator must be one of {valid_estimators}, "
|
|
101
|
+
f"got '{variance_estimator}'"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
self.variance_estimator = variance_estimator
|
|
105
|
+
|
|
106
|
+
# Variance components (computed after fitting)
|
|
107
|
+
self.sigma2_u: Optional[float] = None # Variance of entity effects
|
|
108
|
+
self.sigma2_e: Optional[float] = None # Variance of idiosyncratic errors
|
|
109
|
+
self.theta: Optional[float] = None # GLS transformation parameter
|
|
110
|
+
|
|
111
|
+
def fit(
|
|
112
|
+
self,
|
|
113
|
+
cov_type: str = 'nonrobust',
|
|
114
|
+
**cov_kwds
|
|
115
|
+
) -> PanelResults:
|
|
116
|
+
"""
|
|
117
|
+
Fit the Random Effects model.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
cov_type : str, default='nonrobust'
|
|
122
|
+
Type of covariance estimator:
|
|
123
|
+
- 'nonrobust': Classical GLS standard errors
|
|
124
|
+
- 'robust': Heteroskedasticity-robust
|
|
125
|
+
- 'clustered': Cluster-robust (clustered by entity)
|
|
126
|
+
**cov_kwds
|
|
127
|
+
Additional arguments for covariance estimation
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
PanelResults
|
|
132
|
+
Fitted model results
|
|
133
|
+
|
|
134
|
+
Examples
|
|
135
|
+
--------
|
|
136
|
+
>>> results = model.fit()
|
|
137
|
+
>>> results_robust = model.fit(cov_type='robust')
|
|
138
|
+
"""
|
|
139
|
+
# Build design matrices
|
|
140
|
+
y, X = self.formula_parser.build_design_matrices(
|
|
141
|
+
self.data.data,
|
|
142
|
+
return_type='array'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Get variable names
|
|
146
|
+
var_names = self.formula_parser.get_variable_names(self.data.data)
|
|
147
|
+
|
|
148
|
+
# Get entity and time identifiers
|
|
149
|
+
entities = self.data.data[self.data.entity_col].values
|
|
150
|
+
times = self.data.data[self.data.time_col].values
|
|
151
|
+
|
|
152
|
+
# Estimate variance components
|
|
153
|
+
self._estimate_variance_components(y, X, entities)
|
|
154
|
+
|
|
155
|
+
# Apply GLS transformation
|
|
156
|
+
y_gls, X_gls = self._gls_transform(y, X, entities)
|
|
157
|
+
|
|
158
|
+
# Estimate coefficients on transformed data
|
|
159
|
+
beta, resid_gls, fitted_gls = compute_ols(y_gls, X_gls, self.weights)
|
|
160
|
+
|
|
161
|
+
# Compute residuals and fitted values in original scale
|
|
162
|
+
fitted = (X @ beta).ravel()
|
|
163
|
+
resid = (y - fitted).ravel()
|
|
164
|
+
|
|
165
|
+
# Degrees of freedom
|
|
166
|
+
n = len(y)
|
|
167
|
+
k = X.shape[1]
|
|
168
|
+
df_model = k - (1 if self.formula_parser.has_intercept else 0)
|
|
169
|
+
df_resid = n - k
|
|
170
|
+
|
|
171
|
+
# Compute covariance matrix
|
|
172
|
+
if cov_type == 'nonrobust':
|
|
173
|
+
vcov = self._compute_vcov_gls(X, resid_gls, entities, df_resid)
|
|
174
|
+
elif cov_type == 'robust':
|
|
175
|
+
vcov = self._compute_vcov_robust(X_gls, resid_gls, df_resid)
|
|
176
|
+
elif cov_type == 'clustered':
|
|
177
|
+
vcov = self._compute_vcov_clustered(X_gls, resid_gls, entities, df_resid)
|
|
178
|
+
else:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"cov_type must be 'nonrobust', 'robust', or 'clustered', "
|
|
181
|
+
f"got '{cov_type}'"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Standard errors
|
|
185
|
+
std_errors = np.sqrt(np.diag(vcov))
|
|
186
|
+
|
|
187
|
+
# Compute panel R-squared measures
|
|
188
|
+
rsquared_within, rsquared_between, rsquared_overall = compute_panel_rsquared(
|
|
189
|
+
y, fitted, resid, entities
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Adjusted R-squared (overall)
|
|
193
|
+
rsquared_adj = 1 - (1 - rsquared_overall) * (n - 1) / df_resid
|
|
194
|
+
|
|
195
|
+
# Create Series/DataFrame with variable names
|
|
196
|
+
params = pd.Series(beta.ravel(), index=var_names)
|
|
197
|
+
std_errors_series = pd.Series(std_errors, index=var_names)
|
|
198
|
+
cov_params = pd.DataFrame(vcov, index=var_names, columns=var_names)
|
|
199
|
+
|
|
200
|
+
# Model information
|
|
201
|
+
model_info = {
|
|
202
|
+
'model_type': 'Random Effects (GLS)',
|
|
203
|
+
'formula': self.formula,
|
|
204
|
+
'cov_type': cov_type,
|
|
205
|
+
'cov_kwds': cov_kwds,
|
|
206
|
+
'variance_estimator': self.variance_estimator,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Data information
|
|
210
|
+
data_info = {
|
|
211
|
+
'nobs': n,
|
|
212
|
+
'n_entities': self.data.n_entities,
|
|
213
|
+
'n_periods': self.data.n_periods,
|
|
214
|
+
'df_model': df_model,
|
|
215
|
+
'df_resid': df_resid,
|
|
216
|
+
'entity_index': entities.ravel() if hasattr(entities, 'ravel') else entities,
|
|
217
|
+
'time_index': times.ravel() if hasattr(times, 'ravel') else times,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# R-squared dictionary
|
|
221
|
+
rsquared_dict = {
|
|
222
|
+
'rsquared': rsquared_overall, # For RE, main R² is overall
|
|
223
|
+
'rsquared_adj': rsquared_adj,
|
|
224
|
+
'rsquared_within': rsquared_within,
|
|
225
|
+
'rsquared_between': rsquared_between,
|
|
226
|
+
'rsquared_overall': rsquared_overall
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Create results object
|
|
230
|
+
results = PanelResults(
|
|
231
|
+
params=params,
|
|
232
|
+
std_errors=std_errors_series,
|
|
233
|
+
cov_params=cov_params,
|
|
234
|
+
resid=resid,
|
|
235
|
+
fittedvalues=fitted,
|
|
236
|
+
model_info=model_info,
|
|
237
|
+
data_info=data_info,
|
|
238
|
+
rsquared_dict=rsquared_dict,
|
|
239
|
+
model=self
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Store results and update state
|
|
243
|
+
self._results = results
|
|
244
|
+
self._fitted = True
|
|
245
|
+
|
|
246
|
+
return results
|
|
247
|
+
|
|
248
|
+
def _estimate_variance_components(
|
|
249
|
+
self,
|
|
250
|
+
y: np.ndarray,
|
|
251
|
+
X: np.ndarray,
|
|
252
|
+
entities: np.ndarray
|
|
253
|
+
) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Estimate variance components.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
y : np.ndarray
|
|
260
|
+
Dependent variable
|
|
261
|
+
X : np.ndarray
|
|
262
|
+
Design matrix
|
|
263
|
+
entities : np.ndarray
|
|
264
|
+
Entity identifiers
|
|
265
|
+
"""
|
|
266
|
+
n = len(y)
|
|
267
|
+
k = X.shape[1]
|
|
268
|
+
|
|
269
|
+
if self.variance_estimator == 'swamy-arora':
|
|
270
|
+
self._swamy_arora_variance(y, X, entities, n, k)
|
|
271
|
+
elif self.variance_estimator == 'walhus':
|
|
272
|
+
self._walhus_variance(y, X, entities, n, k)
|
|
273
|
+
elif self.variance_estimator == 'amemiya':
|
|
274
|
+
self._amemiya_variance(y, X, entities, n, k)
|
|
275
|
+
elif self.variance_estimator == 'nerlove':
|
|
276
|
+
self._nerlove_variance(y, X, entities, n, k)
|
|
277
|
+
|
|
278
|
+
def _swamy_arora_variance(
|
|
279
|
+
self,
|
|
280
|
+
y: np.ndarray,
|
|
281
|
+
X: np.ndarray,
|
|
282
|
+
entities: np.ndarray,
|
|
283
|
+
n: int,
|
|
284
|
+
k: int
|
|
285
|
+
) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Swamy-Arora variance component estimator.
|
|
288
|
+
|
|
289
|
+
This is the most common estimator for RE models.
|
|
290
|
+
"""
|
|
291
|
+
# Step 1: Estimate within (FE) model
|
|
292
|
+
from panelbox.utils.matrix_ops import demean_matrix
|
|
293
|
+
|
|
294
|
+
y_within = demean_matrix(y.reshape(-1, 1), entities).ravel()
|
|
295
|
+
X_within = demean_matrix(X, entities)
|
|
296
|
+
|
|
297
|
+
beta_within, resid_within, _ = compute_ols(y_within, X_within)
|
|
298
|
+
|
|
299
|
+
# Estimate sigma2_e from within residuals
|
|
300
|
+
N = self.data.n_entities
|
|
301
|
+
df_within = n - N - k # Account for absorbed entity dummies
|
|
302
|
+
self.sigma2_e = np.sum(resid_within ** 2) / df_within
|
|
303
|
+
|
|
304
|
+
# Step 2: Estimate between model (on entity means)
|
|
305
|
+
unique_entities = np.unique(entities)
|
|
306
|
+
y_means = []
|
|
307
|
+
X_means = []
|
|
308
|
+
|
|
309
|
+
for entity in unique_entities:
|
|
310
|
+
mask = entities == entity
|
|
311
|
+
y_means.append(y[mask].mean())
|
|
312
|
+
X_means.append(X[mask].mean(axis=0))
|
|
313
|
+
|
|
314
|
+
y_between = np.array(y_means)
|
|
315
|
+
X_between = np.array(X_means)
|
|
316
|
+
|
|
317
|
+
beta_between, resid_between, _ = compute_ols(y_between, X_between)
|
|
318
|
+
|
|
319
|
+
# Estimate sigma2_u from between residuals
|
|
320
|
+
# Average group size
|
|
321
|
+
T_bar = n / N
|
|
322
|
+
|
|
323
|
+
# Variance of between residuals
|
|
324
|
+
var_between = np.sum(resid_between ** 2) / (N - k)
|
|
325
|
+
|
|
326
|
+
# sigma2_u = var_between - sigma2_e / T_bar
|
|
327
|
+
self.sigma2_u = max(0, var_between - self.sigma2_e / T_bar)
|
|
328
|
+
|
|
329
|
+
# Compute theta (GLS transformation parameter)
|
|
330
|
+
# theta = 1 - sqrt(sigma2_e / (sigma2_e + T*sigma2_u))
|
|
331
|
+
self.theta = 1 - np.sqrt(self.sigma2_e / (self.sigma2_e + T_bar * self.sigma2_u))
|
|
332
|
+
|
|
333
|
+
def _walhus_variance(self, y, X, entities, n, k):
|
|
334
|
+
"""Wallace-Hussain variance estimator."""
|
|
335
|
+
# Similar to Swamy-Arora but uses different degrees of freedom
|
|
336
|
+
# For simplicity, use Swamy-Arora (can be refined later)
|
|
337
|
+
self._swamy_arora_variance(y, X, entities, n, k)
|
|
338
|
+
|
|
339
|
+
def _amemiya_variance(self, y, X, entities, n, k):
|
|
340
|
+
"""Amemiya variance estimator."""
|
|
341
|
+
# Uses quadratic forms of residuals
|
|
342
|
+
# For simplicity, use Swamy-Arora (can be refined later)
|
|
343
|
+
self._swamy_arora_variance(y, X, entities, n, k)
|
|
344
|
+
|
|
345
|
+
def _nerlove_variance(self, y, X, entities, n, k):
|
|
346
|
+
"""Nerlove variance estimator."""
|
|
347
|
+
# Uses pooled OLS residuals
|
|
348
|
+
# For simplicity, use Swamy-Arora (can be refined later)
|
|
349
|
+
self._swamy_arora_variance(y, X, entities, n, k)
|
|
350
|
+
|
|
351
|
+
def _gls_transform(
|
|
352
|
+
self,
|
|
353
|
+
y: np.ndarray,
|
|
354
|
+
X: np.ndarray,
|
|
355
|
+
entities: np.ndarray
|
|
356
|
+
) -> tuple:
|
|
357
|
+
"""
|
|
358
|
+
Apply GLS transformation.
|
|
359
|
+
|
|
360
|
+
The transformation is: y* = y - theta * y_bar_i
|
|
361
|
+
where y_bar_i is the entity mean and theta is computed from variance components.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
y : np.ndarray
|
|
366
|
+
Dependent variable
|
|
367
|
+
X : np.ndarray
|
|
368
|
+
Design matrix
|
|
369
|
+
entities : np.ndarray
|
|
370
|
+
Entity identifiers
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
y_gls : np.ndarray
|
|
375
|
+
Transformed dependent variable
|
|
376
|
+
X_gls : np.ndarray
|
|
377
|
+
Transformed design matrix
|
|
378
|
+
"""
|
|
379
|
+
unique_entities = np.unique(entities)
|
|
380
|
+
|
|
381
|
+
y_gls = y.copy()
|
|
382
|
+
X_gls = X.copy()
|
|
383
|
+
|
|
384
|
+
for entity in unique_entities:
|
|
385
|
+
mask = entities == entity
|
|
386
|
+
|
|
387
|
+
# Entity means
|
|
388
|
+
y_mean = y[mask].mean()
|
|
389
|
+
X_mean = X[mask].mean(axis=0)
|
|
390
|
+
|
|
391
|
+
# GLS transformation: subtract theta * mean
|
|
392
|
+
y_gls[mask] -= self.theta * y_mean
|
|
393
|
+
X_gls[mask] -= self.theta * X_mean
|
|
394
|
+
|
|
395
|
+
return y_gls, X_gls
|
|
396
|
+
|
|
397
|
+
def _estimate_coefficients(self) -> np.ndarray:
|
|
398
|
+
"""
|
|
399
|
+
Estimate coefficients (implementation of abstract method).
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
np.ndarray
|
|
404
|
+
Estimated coefficients
|
|
405
|
+
"""
|
|
406
|
+
y, X = self.formula_parser.build_design_matrices(
|
|
407
|
+
self.data.data,
|
|
408
|
+
return_type='array'
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
entities = self.data.data[self.data.entity_col].values
|
|
412
|
+
|
|
413
|
+
# Estimate variance components
|
|
414
|
+
self._estimate_variance_components(y, X, entities)
|
|
415
|
+
|
|
416
|
+
# GLS transformation
|
|
417
|
+
y_gls, X_gls = self._gls_transform(y, X, entities)
|
|
418
|
+
|
|
419
|
+
# Estimate
|
|
420
|
+
beta, _, _ = compute_ols(y_gls, X_gls, self.weights)
|
|
421
|
+
return beta
|
|
422
|
+
|
|
423
|
+
def _compute_vcov_gls(
|
|
424
|
+
self,
|
|
425
|
+
X: np.ndarray,
|
|
426
|
+
resid: np.ndarray,
|
|
427
|
+
entities: np.ndarray,
|
|
428
|
+
df_resid: int
|
|
429
|
+
) -> np.ndarray:
|
|
430
|
+
"""
|
|
431
|
+
Compute GLS covariance matrix.
|
|
432
|
+
|
|
433
|
+
Parameters
|
|
434
|
+
----------
|
|
435
|
+
X : np.ndarray
|
|
436
|
+
Original design matrix (not transformed)
|
|
437
|
+
resid : np.ndarray
|
|
438
|
+
GLS residuals
|
|
439
|
+
entities : np.ndarray
|
|
440
|
+
Entity identifiers
|
|
441
|
+
df_resid : int
|
|
442
|
+
Degrees of freedom
|
|
443
|
+
|
|
444
|
+
Returns
|
|
445
|
+
-------
|
|
446
|
+
np.ndarray
|
|
447
|
+
Covariance matrix
|
|
448
|
+
"""
|
|
449
|
+
# Estimate of error variance from GLS residuals
|
|
450
|
+
s2 = np.sum(resid ** 2) / df_resid
|
|
451
|
+
|
|
452
|
+
# Build Omega matrix (variance-covariance of errors)
|
|
453
|
+
# For RE: Omega_i = sigma2_e * I + sigma2_u * J
|
|
454
|
+
# where J is matrix of ones
|
|
455
|
+
|
|
456
|
+
# For computational efficiency, use transformation approach
|
|
457
|
+
# V(beta_GLS) = s^2 * (X'Omega^{-1}X)^{-1}
|
|
458
|
+
|
|
459
|
+
# Create transformed X (same transformation as in GLS)
|
|
460
|
+
X_gls, _ = self._gls_transform(X, X, entities)
|
|
461
|
+
|
|
462
|
+
# Covariance: s^2 (X_gls' X_gls)^{-1}
|
|
463
|
+
XtX_inv = np.linalg.inv(X_gls.T @ X_gls)
|
|
464
|
+
vcov = s2 * XtX_inv
|
|
465
|
+
|
|
466
|
+
return vcov
|
|
467
|
+
|
|
468
|
+
def _compute_vcov_robust(
|
|
469
|
+
self,
|
|
470
|
+
X: np.ndarray,
|
|
471
|
+
resid: np.ndarray,
|
|
472
|
+
df_resid: int
|
|
473
|
+
) -> np.ndarray:
|
|
474
|
+
"""Compute robust covariance matrix."""
|
|
475
|
+
n = len(resid)
|
|
476
|
+
k = X.shape[1]
|
|
477
|
+
adjustment = n / df_resid
|
|
478
|
+
|
|
479
|
+
XtX_inv = np.linalg.inv(X.T @ X)
|
|
480
|
+
meat = X.T @ (resid[:, np.newaxis]**2 * X)
|
|
481
|
+
vcov = adjustment * (XtX_inv @ meat @ XtX_inv)
|
|
482
|
+
|
|
483
|
+
return vcov
|
|
484
|
+
|
|
485
|
+
def _compute_vcov_clustered(
|
|
486
|
+
self,
|
|
487
|
+
X: np.ndarray,
|
|
488
|
+
resid: np.ndarray,
|
|
489
|
+
entities: np.ndarray,
|
|
490
|
+
df_resid: int
|
|
491
|
+
) -> np.ndarray:
|
|
492
|
+
"""Compute cluster-robust covariance matrix."""
|
|
493
|
+
n = len(resid)
|
|
494
|
+
k = X.shape[1]
|
|
495
|
+
|
|
496
|
+
unique_entities = np.unique(entities)
|
|
497
|
+
n_clusters = len(unique_entities)
|
|
498
|
+
|
|
499
|
+
XtX_inv = np.linalg.inv(X.T @ X)
|
|
500
|
+
|
|
501
|
+
meat = np.zeros((k, k))
|
|
502
|
+
for entity in unique_entities:
|
|
503
|
+
mask = entities == entity
|
|
504
|
+
X_c = X[mask]
|
|
505
|
+
resid_c = resid[mask]
|
|
506
|
+
score = X_c.T @ resid_c
|
|
507
|
+
meat += np.outer(score, score)
|
|
508
|
+
|
|
509
|
+
adjustment = (n_clusters / (n_clusters - 1)) * (df_resid / (df_resid - k))
|
|
510
|
+
vcov = adjustment * (XtX_inv @ meat @ XtX_inv)
|
|
511
|
+
|
|
512
|
+
return vcov
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PanelBox Report Generation Module.
|
|
3
|
+
|
|
4
|
+
Provides comprehensive report generation capabilities for panel data analysis.
|
|
5
|
+
|
|
6
|
+
Main Components
|
|
7
|
+
---------------
|
|
8
|
+
- ReportManager: Main orchestrator for report generation
|
|
9
|
+
- TemplateManager: Jinja2 template management
|
|
10
|
+
- AssetManager: CSS, JS, and image asset management
|
|
11
|
+
- CSSManager: 3-layer CSS compilation system
|
|
12
|
+
|
|
13
|
+
Examples
|
|
14
|
+
--------
|
|
15
|
+
Generate a validation report:
|
|
16
|
+
|
|
17
|
+
>>> from panelbox.report import ReportManager
|
|
18
|
+
>>> report_mgr = ReportManager()
|
|
19
|
+
>>> html = report_mgr.generate_validation_report(
|
|
20
|
+
... validation_data={'tests': [...], 'model_info': {...}},
|
|
21
|
+
... title='Panel Validation Report'
|
|
22
|
+
... )
|
|
23
|
+
>>> report_mgr.save_report(html, 'validation_report.html')
|
|
24
|
+
|
|
25
|
+
Custom report generation:
|
|
26
|
+
|
|
27
|
+
>>> context = {
|
|
28
|
+
... 'report_title': 'Custom Analysis',
|
|
29
|
+
... 'data': {...}
|
|
30
|
+
... }
|
|
31
|
+
>>> html = report_mgr.generate_report(
|
|
32
|
+
... report_type='custom',
|
|
33
|
+
... template='custom/report.html',
|
|
34
|
+
... context=context
|
|
35
|
+
... )
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from .report_manager import ReportManager
|
|
39
|
+
from .template_manager import TemplateManager
|
|
40
|
+
from .asset_manager import AssetManager
|
|
41
|
+
from .css_manager import CSSManager, CSSLayer
|
|
42
|
+
from .validation_transformer import ValidationTransformer
|
|
43
|
+
|
|
44
|
+
# Exporters
|
|
45
|
+
from .exporters import (
|
|
46
|
+
HTMLExporter,
|
|
47
|
+
LaTeXExporter,
|
|
48
|
+
MarkdownExporter
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
'ReportManager',
|
|
53
|
+
'TemplateManager',
|
|
54
|
+
'AssetManager',
|
|
55
|
+
'CSSManager',
|
|
56
|
+
'CSSLayer',
|
|
57
|
+
'ValidationTransformer',
|
|
58
|
+
'HTMLExporter',
|
|
59
|
+
'LaTeXExporter',
|
|
60
|
+
'MarkdownExporter',
|
|
61
|
+
]
|