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,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
+ ]