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,510 @@
1
+ """
2
+ LaTeX Exporter for PanelBox Reports.
3
+
4
+ Exports validation and regression results to LaTeX format for academic papers.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Dict, Any, List, Optional, Union
9
+ import datetime
10
+
11
+
12
+ class LaTeXExporter:
13
+ """
14
+ Exports PanelBox reports to LaTeX format.
15
+
16
+ Creates publication-ready LaTeX tables for academic papers.
17
+ Supports validation test results, regression tables, and summary statistics.
18
+
19
+ Parameters
20
+ ----------
21
+ table_style : str, default='booktabs'
22
+ LaTeX table style: 'booktabs', 'standard', or 'threeparttable'
23
+ float_format : str, default='.3f'
24
+ Float formatting string
25
+ escape_special_chars : bool, default=True
26
+ Escape LaTeX special characters in strings
27
+
28
+ Examples
29
+ --------
30
+ >>> from panelbox.report.exporters import LaTeXExporter
31
+ >>>
32
+ >>> exporter = LaTeXExporter(table_style='booktabs')
33
+ >>> latex = exporter.export_validation_tests(tests)
34
+ >>> exporter.save(latex, 'validation_table.tex')
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ table_style: str = 'booktabs',
40
+ float_format: str = '.3f',
41
+ escape_special_chars: bool = True
42
+ ):
43
+ """Initialize LaTeX Exporter."""
44
+ if table_style not in ('booktabs', 'standard', 'threeparttable'):
45
+ raise ValueError(
46
+ f"Invalid table_style: {table_style}. "
47
+ "Must be 'booktabs', 'standard', or 'threeparttable'."
48
+ )
49
+
50
+ self.table_style = table_style
51
+ self.float_format = float_format
52
+ self.escape_special_chars = escape_special_chars
53
+
54
+ def export_validation_tests(
55
+ self,
56
+ tests: List[Dict[str, Any]],
57
+ caption: str = "Validation Test Results",
58
+ label: str = "tab:validation"
59
+ ) -> str:
60
+ """
61
+ Export validation tests to LaTeX table.
62
+
63
+ Parameters
64
+ ----------
65
+ tests : list of dict
66
+ List of test results (from ValidationTransformer)
67
+ caption : str, default="Validation Test Results"
68
+ Table caption
69
+ label : str, default="tab:validation"
70
+ LaTeX label for cross-referencing
71
+
72
+ Returns
73
+ -------
74
+ str
75
+ LaTeX table code
76
+
77
+ Examples
78
+ --------
79
+ >>> latex = exporter.export_validation_tests(
80
+ ... tests,
81
+ ... caption="Panel Data Validation Tests",
82
+ ... label="tab:validation"
83
+ ... )
84
+ """
85
+ lines = []
86
+
87
+ # Begin table
88
+ lines.append(r"\begin{table}[htbp]")
89
+ lines.append(r" \centering")
90
+ lines.append(f" \\caption{{{caption}}}")
91
+ lines.append(f" \\label{{{label}}}")
92
+
93
+ if self.table_style == 'booktabs':
94
+ lines.append(r" \begin{tabular}{lcccc}")
95
+ lines.append(r" \toprule")
96
+ else:
97
+ lines.append(r" \begin{tabular}{|l|c|c|c|c|}")
98
+ lines.append(r" \hline")
99
+
100
+ # Header
101
+ lines.append(r" Test & Statistic & P-value & DF & Result \\")
102
+
103
+ if self.table_style == 'booktabs':
104
+ lines.append(r" \midrule")
105
+ else:
106
+ lines.append(r" \hline")
107
+
108
+ # Group by category
109
+ categories = {}
110
+ for test in tests:
111
+ cat = test['category']
112
+ if cat not in categories:
113
+ categories[cat] = []
114
+ categories[cat].append(test)
115
+
116
+ # Add rows by category
117
+ for category, cat_tests in categories.items():
118
+ # Category header
119
+ if self.table_style == 'booktabs':
120
+ lines.append(f" \\multicolumn{{5}}{{l}}{{\\textit{{{category}}}}} \\\\")
121
+ else:
122
+ lines.append(f" \\multicolumn{{5}}{{|l|}}{{\\textbf{{{category}}}}} \\\\")
123
+ lines.append(r" \hline")
124
+
125
+ # Test rows
126
+ for test in cat_tests:
127
+ name = self._escape(test['name'])
128
+ stat = self._format_float(test['statistic'])
129
+ pval = self._format_pvalue(test['pvalue'])
130
+ df = test['df'] if test['df'] else '--'
131
+ result = test['result']
132
+
133
+ # Add significance stars
134
+ stars = test.get('significance', '')
135
+
136
+ lines.append(
137
+ f" {name} & {stat} & {pval}{stars} & {df} & {result} \\\\"
138
+ )
139
+
140
+ if self.table_style != 'booktabs':
141
+ lines.append(r" \hline")
142
+
143
+ # End table
144
+ if self.table_style == 'booktabs':
145
+ lines.append(r" \bottomrule")
146
+ else:
147
+ lines.append(r" \hline")
148
+
149
+ lines.append(r" \end{tabular}")
150
+
151
+ # Add notes
152
+ if self.table_style == 'booktabs':
153
+ lines.append(r" \medskip")
154
+ lines.append(r" \begin{minipage}{\textwidth}")
155
+ lines.append(r" \small")
156
+ lines.append(r" \textit{Note:} Significance levels: *** p<0.001, ** p<0.01, * p<0.05, . p<0.1.")
157
+ lines.append(r" \end{minipage}")
158
+
159
+ lines.append(r"\end{table}")
160
+
161
+ return "\n".join(lines)
162
+
163
+ def export_regression_table(
164
+ self,
165
+ coefficients: List[Dict[str, Any]],
166
+ model_info: Dict[str, Any],
167
+ caption: str = "Regression Results",
168
+ label: str = "tab:regression"
169
+ ) -> str:
170
+ """
171
+ Export regression results to LaTeX table.
172
+
173
+ Parameters
174
+ ----------
175
+ coefficients : list of dict
176
+ List of coefficient results
177
+ model_info : dict
178
+ Model information (R², N, etc.)
179
+ caption : str, default="Regression Results"
180
+ Table caption
181
+ label : str, default="tab:regression"
182
+ LaTeX label
183
+
184
+ Returns
185
+ -------
186
+ str
187
+ LaTeX table code
188
+
189
+ Examples
190
+ --------
191
+ >>> latex = exporter.export_regression_table(
192
+ ... coefficients=coefs,
193
+ ... model_info=info,
194
+ ... caption="Fixed Effects Regression"
195
+ ... )
196
+ """
197
+ lines = []
198
+
199
+ # Begin table
200
+ lines.append(r"\begin{table}[htbp]")
201
+ lines.append(r" \centering")
202
+ lines.append(f" \\caption{{{caption}}}")
203
+ lines.append(f" \\label{{{label}}}")
204
+
205
+ if self.table_style == 'booktabs':
206
+ lines.append(r" \begin{tabular}{lcccc}")
207
+ lines.append(r" \toprule")
208
+ else:
209
+ lines.append(r" \begin{tabular}{|l|c|c|c|c|}")
210
+ lines.append(r" \hline")
211
+
212
+ # Header
213
+ lines.append(r" Variable & Coefficient & Std. Error & t-statistic & P-value \\")
214
+
215
+ if self.table_style == 'booktabs':
216
+ lines.append(r" \midrule")
217
+ else:
218
+ lines.append(r" \hline")
219
+
220
+ # Coefficient rows
221
+ for coef in coefficients:
222
+ var = self._escape(coef['variable'])
223
+ beta = self._format_float(coef['coefficient'])
224
+ se = self._format_float(coef['std_error'])
225
+ tstat = self._format_float(coef['t_statistic'])
226
+ pval = self._format_pvalue(coef['pvalue'])
227
+
228
+ # Add significance stars
229
+ stars = self._get_stars(coef['pvalue'])
230
+
231
+ lines.append(
232
+ f" {var} & {beta}{stars} & ({se}) & {tstat} & {pval} \\\\"
233
+ )
234
+
235
+ if self.table_style == 'booktabs':
236
+ lines.append(r" \midrule")
237
+ else:
238
+ lines.append(r" \hline")
239
+
240
+ # Model statistics
241
+ lines.append(r" \multicolumn{5}{l}{\textit{Model Statistics}} \\")
242
+
243
+ if 'r_squared' in model_info:
244
+ r2 = self._format_float(model_info['r_squared'])
245
+ lines.append(f" R² & \\multicolumn{{4}}{{c}}{{{r2}}} \\\\")
246
+
247
+ if 'nobs' in model_info:
248
+ nobs = model_info['nobs']
249
+ lines.append(f" Observations & \\multicolumn{{4}}{{c}}{{{nobs}}} \\\\")
250
+
251
+ if 'n_entities' in model_info:
252
+ n_ent = model_info['n_entities']
253
+ lines.append(f" Entities & \\multicolumn{{4}}{{c}}{{{n_ent}}} \\\\")
254
+
255
+ # End table
256
+ if self.table_style == 'booktabs':
257
+ lines.append(r" \bottomrule")
258
+ else:
259
+ lines.append(r" \hline")
260
+
261
+ lines.append(r" \end{tabular}")
262
+
263
+ # Add notes
264
+ if self.table_style == 'booktabs':
265
+ lines.append(r" \medskip")
266
+ lines.append(r" \begin{minipage}{\textwidth}")
267
+ lines.append(r" \small")
268
+ lines.append(r" \textit{Note:} Standard errors in parentheses. ")
269
+ lines.append(r" Significance levels: *** p<0.001, ** p<0.01, * p<0.05.")
270
+ lines.append(r" \end{minipage}")
271
+
272
+ lines.append(r"\end{table}")
273
+
274
+ return "\n".join(lines)
275
+
276
+ def export_summary_stats(
277
+ self,
278
+ stats: List[Dict[str, Any]],
279
+ caption: str = "Summary Statistics",
280
+ label: str = "tab:summary"
281
+ ) -> str:
282
+ """
283
+ Export summary statistics to LaTeX table.
284
+
285
+ Parameters
286
+ ----------
287
+ stats : list of dict
288
+ List of variable statistics
289
+ caption : str, default="Summary Statistics"
290
+ Table caption
291
+ label : str, default="tab:summary"
292
+ LaTeX label
293
+
294
+ Returns
295
+ -------
296
+ str
297
+ LaTeX table code
298
+
299
+ Examples
300
+ --------
301
+ >>> latex = exporter.export_summary_stats(
302
+ ... stats,
303
+ ... caption="Descriptive Statistics"
304
+ ... )
305
+ """
306
+ lines = []
307
+
308
+ # Begin table
309
+ lines.append(r"\begin{table}[htbp]")
310
+ lines.append(r" \centering")
311
+ lines.append(f" \\caption{{{caption}}}")
312
+ lines.append(f" \\label{{{label}}}")
313
+
314
+ if self.table_style == 'booktabs':
315
+ lines.append(r" \begin{tabular}{lcccccc}")
316
+ lines.append(r" \toprule")
317
+ else:
318
+ lines.append(r" \begin{tabular}{|l|c|c|c|c|c|c|}")
319
+ lines.append(r" \hline")
320
+
321
+ # Header
322
+ lines.append(r" Variable & N & Mean & Std. Dev. & Min & Max & Median \\")
323
+
324
+ if self.table_style == 'booktabs':
325
+ lines.append(r" \midrule")
326
+ else:
327
+ lines.append(r" \hline")
328
+
329
+ # Data rows
330
+ for stat in stats:
331
+ var = self._escape(stat['variable'])
332
+ n = stat['count']
333
+ mean = self._format_float(stat['mean'])
334
+ std = self._format_float(stat['std'])
335
+ min_val = self._format_float(stat['min'])
336
+ max_val = self._format_float(stat['max'])
337
+ median = self._format_float(stat.get('median', stat.get('50%', 0)))
338
+
339
+ lines.append(
340
+ f" {var} & {n} & {mean} & {std} & {min_val} & {max_val} & {median} \\\\"
341
+ )
342
+
343
+ # End table
344
+ if self.table_style == 'booktabs':
345
+ lines.append(r" \bottomrule")
346
+ else:
347
+ lines.append(r" \hline")
348
+
349
+ lines.append(r" \end{tabular}")
350
+ lines.append(r"\end{table}")
351
+
352
+ return "\n".join(lines)
353
+
354
+ def save(
355
+ self,
356
+ latex_content: str,
357
+ output_path: Union[str, Path],
358
+ overwrite: bool = False,
359
+ add_preamble: bool = False
360
+ ) -> Path:
361
+ """
362
+ Save LaTeX content to file.
363
+
364
+ Parameters
365
+ ----------
366
+ latex_content : str
367
+ LaTeX content
368
+ output_path : str or Path
369
+ Output file path
370
+ overwrite : bool, default=False
371
+ Overwrite existing file
372
+ add_preamble : bool, default=False
373
+ Add complete LaTeX document preamble
374
+
375
+ Returns
376
+ -------
377
+ Path
378
+ Path to saved file
379
+
380
+ Examples
381
+ --------
382
+ >>> exporter.save(latex, 'table.tex')
383
+ >>> # With preamble for standalone compilation
384
+ >>> exporter.save(latex, 'table.tex', add_preamble=True)
385
+ """
386
+ output_path = Path(output_path)
387
+
388
+ # Check if file exists
389
+ if output_path.exists() and not overwrite:
390
+ raise FileExistsError(
391
+ f"File already exists: {output_path}. "
392
+ "Use overwrite=True to replace."
393
+ )
394
+
395
+ # Create parent directories
396
+ output_path.parent.mkdir(parents=True, exist_ok=True)
397
+
398
+ # Add preamble if requested
399
+ if add_preamble:
400
+ latex_content = self._add_preamble(latex_content)
401
+
402
+ # Write file
403
+ output_path.write_text(latex_content, encoding='utf-8')
404
+
405
+ return output_path
406
+
407
+ def _add_preamble(self, content: str) -> str:
408
+ """
409
+ Add LaTeX preamble for standalone document.
410
+
411
+ Parameters
412
+ ----------
413
+ content : str
414
+ LaTeX table content
415
+
416
+ Returns
417
+ -------
418
+ str
419
+ Complete LaTeX document
420
+ """
421
+ preamble = r"""\documentclass[11pt]{article}
422
+ \usepackage[utf8]{inputenc}
423
+ \usepackage{booktabs}
424
+ \usepackage{graphicx}
425
+ \usepackage{geometry}
426
+ \geometry{margin=1in}
427
+
428
+ \begin{document}
429
+
430
+ """
431
+
432
+ postamble = r"""
433
+
434
+ \end{document}
435
+ """
436
+
437
+ return preamble + content + postamble
438
+
439
+ def _escape(self, text: str) -> str:
440
+ """
441
+ Escape LaTeX special characters.
442
+
443
+ Parameters
444
+ ----------
445
+ text : str
446
+ Text to escape
447
+
448
+ Returns
449
+ -------
450
+ str
451
+ Escaped text
452
+ """
453
+ if not self.escape_special_chars:
454
+ return text
455
+
456
+ # LaTeX special characters
457
+ replacements = {
458
+ '&': r'\&',
459
+ '%': r'\%',
460
+ '$': r'\$',
461
+ '#': r'\#',
462
+ '_': r'\_',
463
+ '{': r'\{',
464
+ '}': r'\}',
465
+ '~': r'\textasciitilde{}',
466
+ '^': r'\textasciicircum{}',
467
+ '\\': r'\textbackslash{}',
468
+ }
469
+
470
+ for char, replacement in replacements.items():
471
+ text = text.replace(char, replacement)
472
+
473
+ return text
474
+
475
+ def _format_float(self, value: float) -> str:
476
+ """Format float value."""
477
+ try:
478
+ return f"{value:{self.float_format}}"
479
+ except (ValueError, TypeError):
480
+ return str(value)
481
+
482
+ def _format_pvalue(self, pvalue: float) -> str:
483
+ """Format p-value."""
484
+ try:
485
+ if pvalue < 0.001:
486
+ return f"{pvalue:.2e}"
487
+ return f"{pvalue:.4f}"
488
+ except (ValueError, TypeError):
489
+ return str(pvalue)
490
+
491
+ def _get_stars(self, pvalue: float) -> str:
492
+ """Get significance stars."""
493
+ try:
494
+ if pvalue < 0.001:
495
+ return r'^{***}'
496
+ elif pvalue < 0.01:
497
+ return r'^{**}'
498
+ elif pvalue < 0.05:
499
+ return r'^{*}'
500
+ return ''
501
+ except (ValueError, TypeError):
502
+ return ''
503
+
504
+ def __repr__(self) -> str:
505
+ """String representation."""
506
+ return (
507
+ f"LaTeXExporter("
508
+ f"table_style='{self.table_style}', "
509
+ f"float_format='{self.float_format}')"
510
+ )