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