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,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Static Validation Renderer.
|
|
3
|
+
|
|
4
|
+
Generates static charts for validation reports using Matplotlib.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import matplotlib
|
|
14
|
+
matplotlib.use('Agg') # Non-interactive backend
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
import numpy as np
|
|
17
|
+
MATPLOTLIB_AVAILABLE = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
MATPLOTLIB_AVAILABLE = False
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"Matplotlib not available. Static charts will not be generated. "
|
|
22
|
+
"Install with: pip install matplotlib"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StaticValidationRenderer:
|
|
27
|
+
"""
|
|
28
|
+
Renders static validation charts using Matplotlib.
|
|
29
|
+
|
|
30
|
+
Creates publication-quality static charts suitable for PDFs,
|
|
31
|
+
printed reports, and non-interactive viewing.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
figure_size : tuple, default=(10, 6)
|
|
36
|
+
Default figure size (width, height) in inches
|
|
37
|
+
dpi : int, default=150
|
|
38
|
+
Resolution in dots per inch
|
|
39
|
+
style : str, default='seaborn-v0_8-darkgrid'
|
|
40
|
+
Matplotlib style to use
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
>>> from panelbox.report.renderers import StaticValidationRenderer
|
|
45
|
+
>>>
|
|
46
|
+
>>> renderer = StaticValidationRenderer(dpi=300)
|
|
47
|
+
>>> charts = renderer.render_validation_charts(validation_data)
|
|
48
|
+
>>> # charts contains base64-encoded PNG images
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
figure_size: Tuple[int, int] = (10, 6),
|
|
54
|
+
dpi: int = 150,
|
|
55
|
+
style: str = 'seaborn-v0_8-darkgrid'
|
|
56
|
+
):
|
|
57
|
+
"""Initialize Static Validation Renderer."""
|
|
58
|
+
if not MATPLOTLIB_AVAILABLE:
|
|
59
|
+
raise ImportError(
|
|
60
|
+
"Matplotlib is required for static chart rendering. "
|
|
61
|
+
"Install with: pip install matplotlib"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
self.figure_size = figure_size
|
|
65
|
+
self.dpi = dpi
|
|
66
|
+
self.style = style
|
|
67
|
+
|
|
68
|
+
# Set style
|
|
69
|
+
try:
|
|
70
|
+
plt.style.use(style)
|
|
71
|
+
except:
|
|
72
|
+
# Fallback to default if style not available
|
|
73
|
+
plt.style.use('default')
|
|
74
|
+
|
|
75
|
+
def render_validation_charts(
|
|
76
|
+
self,
|
|
77
|
+
validation_data: Dict[str, Any]
|
|
78
|
+
) -> Dict[str, str]:
|
|
79
|
+
"""
|
|
80
|
+
Render all validation charts as base64 PNG images.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
validation_data : dict
|
|
85
|
+
Validation data from ValidationTransformer
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
dict
|
|
90
|
+
Dictionary mapping chart names to base64 image data URIs
|
|
91
|
+
|
|
92
|
+
Examples
|
|
93
|
+
--------
|
|
94
|
+
>>> charts = renderer.render_validation_charts(validation_data)
|
|
95
|
+
>>> overview_img = charts['test_overview']
|
|
96
|
+
>>> # Use in HTML: <img src="{{overview_img}}">
|
|
97
|
+
"""
|
|
98
|
+
charts = {}
|
|
99
|
+
|
|
100
|
+
chart_data = validation_data.get('charts', {})
|
|
101
|
+
|
|
102
|
+
if 'test_overview' in chart_data:
|
|
103
|
+
charts['test_overview'] = self._render_test_overview(
|
|
104
|
+
chart_data['test_overview']
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if 'pvalue_distribution' in chart_data:
|
|
108
|
+
charts['pvalue_distribution'] = self._render_pvalue_distribution(
|
|
109
|
+
chart_data['pvalue_distribution']
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if 'test_statistics' in chart_data:
|
|
113
|
+
charts['test_statistics'] = self._render_test_statistics(
|
|
114
|
+
chart_data['test_statistics']
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return charts
|
|
118
|
+
|
|
119
|
+
def _render_test_overview(self, data: Dict[str, Any]) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Render test overview bar chart.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
data : dict
|
|
126
|
+
Chart data with categories, passed, and failed counts
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
str
|
|
131
|
+
Base64-encoded PNG image data URI
|
|
132
|
+
"""
|
|
133
|
+
fig, ax = plt.subplots(figsize=self.figure_size, dpi=self.dpi)
|
|
134
|
+
|
|
135
|
+
categories = data['categories']
|
|
136
|
+
passed = data['passed']
|
|
137
|
+
failed = data['failed']
|
|
138
|
+
|
|
139
|
+
x = np.arange(len(categories))
|
|
140
|
+
width = 0.35
|
|
141
|
+
|
|
142
|
+
# Stacked bar chart
|
|
143
|
+
ax.bar(x, passed, width, label='Passed', color='#10b981')
|
|
144
|
+
ax.bar(x, failed, width, bottom=passed, label='Failed', color='#ef4444')
|
|
145
|
+
|
|
146
|
+
ax.set_xlabel('Test Category', fontsize=12, fontweight='bold')
|
|
147
|
+
ax.set_ylabel('Number of Tests', fontsize=12, fontweight='bold')
|
|
148
|
+
ax.set_title('Test Results by Category', fontsize=14, fontweight='bold', pad=20)
|
|
149
|
+
ax.set_xticks(x)
|
|
150
|
+
ax.set_xticklabels(categories, rotation=45, ha='right')
|
|
151
|
+
ax.legend(loc='upper right')
|
|
152
|
+
ax.grid(axis='y', alpha=0.3)
|
|
153
|
+
|
|
154
|
+
plt.tight_layout()
|
|
155
|
+
|
|
156
|
+
return self._fig_to_base64(fig)
|
|
157
|
+
|
|
158
|
+
def _render_pvalue_distribution(self, data: Dict[str, Any]) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Render p-value distribution chart.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
data : dict
|
|
165
|
+
Chart data with test names and p-values
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
str
|
|
170
|
+
Base64-encoded PNG image data URI
|
|
171
|
+
"""
|
|
172
|
+
fig, ax = plt.subplots(figsize=self.figure_size, dpi=self.dpi)
|
|
173
|
+
|
|
174
|
+
test_names = data['test_names']
|
|
175
|
+
pvalues = data['pvalues']
|
|
176
|
+
|
|
177
|
+
x = np.arange(len(test_names))
|
|
178
|
+
|
|
179
|
+
# Color-code by significance
|
|
180
|
+
colors = []
|
|
181
|
+
for pval in pvalues:
|
|
182
|
+
if pval < 0.01:
|
|
183
|
+
colors.append('#ef4444') # Red - highly significant
|
|
184
|
+
elif pval < 0.05:
|
|
185
|
+
colors.append('#f59e0b') # Orange - significant
|
|
186
|
+
elif pval < 0.1:
|
|
187
|
+
colors.append('#eab308') # Yellow - marginally significant
|
|
188
|
+
else:
|
|
189
|
+
colors.append('#10b981') # Green - not significant
|
|
190
|
+
|
|
191
|
+
ax.bar(x, pvalues, color=colors, alpha=0.8, edgecolor='black', linewidth=0.5)
|
|
192
|
+
|
|
193
|
+
# Add significance threshold lines
|
|
194
|
+
ax.axhline(y=0.05, color='red', linestyle='--', linewidth=2, label='α = 0.05')
|
|
195
|
+
ax.axhline(y=0.01, color='darkred', linestyle=':', linewidth=1.5, label='α = 0.01')
|
|
196
|
+
|
|
197
|
+
ax.set_xlabel('Test Name', fontsize=12, fontweight='bold')
|
|
198
|
+
ax.set_ylabel('P-value', fontsize=12, fontweight='bold')
|
|
199
|
+
ax.set_title('P-values by Test', fontsize=14, fontweight='bold', pad=20)
|
|
200
|
+
ax.set_xticks(x)
|
|
201
|
+
ax.set_xticklabels(test_names, rotation=45, ha='right', fontsize=9)
|
|
202
|
+
ax.set_yscale('log')
|
|
203
|
+
ax.legend(loc='upper right')
|
|
204
|
+
ax.grid(axis='y', alpha=0.3, which='both')
|
|
205
|
+
|
|
206
|
+
plt.tight_layout()
|
|
207
|
+
|
|
208
|
+
return self._fig_to_base64(fig)
|
|
209
|
+
|
|
210
|
+
def _render_test_statistics(self, data: Dict[str, Any]) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Render test statistics scatter plot.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
data : dict
|
|
217
|
+
Chart data with test names and statistics
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
str
|
|
222
|
+
Base64-encoded PNG image data URI
|
|
223
|
+
"""
|
|
224
|
+
fig, ax = plt.subplots(figsize=self.figure_size, dpi=self.dpi)
|
|
225
|
+
|
|
226
|
+
test_names = data['test_names']
|
|
227
|
+
statistics = data['statistics']
|
|
228
|
+
|
|
229
|
+
x = np.arange(len(test_names))
|
|
230
|
+
|
|
231
|
+
ax.scatter(x, statistics, s=100, color='#2563eb', alpha=0.7,
|
|
232
|
+
edgecolors='#1e40af', linewidth=2, zorder=3)
|
|
233
|
+
|
|
234
|
+
ax.set_xlabel('Test Name', fontsize=12, fontweight='bold')
|
|
235
|
+
ax.set_ylabel('Test Statistic', fontsize=12, fontweight='bold')
|
|
236
|
+
ax.set_title('Test Statistics', fontsize=14, fontweight='bold', pad=20)
|
|
237
|
+
ax.set_xticks(x)
|
|
238
|
+
ax.set_xticklabels(test_names, rotation=45, ha='right', fontsize=9)
|
|
239
|
+
ax.grid(axis='y', alpha=0.3)
|
|
240
|
+
|
|
241
|
+
plt.tight_layout()
|
|
242
|
+
|
|
243
|
+
return self._fig_to_base64(fig)
|
|
244
|
+
|
|
245
|
+
def render_summary_chart(
|
|
246
|
+
self,
|
|
247
|
+
summary: Dict[str, Any]
|
|
248
|
+
) -> str:
|
|
249
|
+
"""
|
|
250
|
+
Render summary pie chart.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
summary : dict
|
|
255
|
+
Summary data with total_passed and total_failed
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
str
|
|
260
|
+
Base64-encoded PNG image data URI
|
|
261
|
+
|
|
262
|
+
Examples
|
|
263
|
+
--------
|
|
264
|
+
>>> summary_chart = renderer.render_summary_chart(summary)
|
|
265
|
+
"""
|
|
266
|
+
fig, ax = plt.subplots(figsize=(8, 8), dpi=self.dpi)
|
|
267
|
+
|
|
268
|
+
passed = summary['total_passed']
|
|
269
|
+
failed = summary['total_failed']
|
|
270
|
+
|
|
271
|
+
sizes = [passed, failed]
|
|
272
|
+
labels = [f'Passed ({passed})', f'Failed ({failed})']
|
|
273
|
+
colors = ['#10b981', '#ef4444']
|
|
274
|
+
explode = (0.05, 0.05)
|
|
275
|
+
|
|
276
|
+
wedges, texts, autotexts = ax.pie(
|
|
277
|
+
sizes,
|
|
278
|
+
explode=explode,
|
|
279
|
+
labels=labels,
|
|
280
|
+
colors=colors,
|
|
281
|
+
autopct='%1.1f%%',
|
|
282
|
+
shadow=True,
|
|
283
|
+
startangle=90
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Enhance text
|
|
287
|
+
for text in texts:
|
|
288
|
+
text.set_fontsize(12)
|
|
289
|
+
text.set_fontweight('bold')
|
|
290
|
+
|
|
291
|
+
for autotext in autotexts:
|
|
292
|
+
autotext.set_color('white')
|
|
293
|
+
autotext.set_fontsize(12)
|
|
294
|
+
autotext.set_fontweight('bold')
|
|
295
|
+
|
|
296
|
+
ax.set_title(
|
|
297
|
+
f'Test Results Summary\n({summary["total_tests"]} total tests)',
|
|
298
|
+
fontsize=14,
|
|
299
|
+
fontweight='bold',
|
|
300
|
+
pad=20
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
plt.tight_layout()
|
|
304
|
+
|
|
305
|
+
return self._fig_to_base64(fig)
|
|
306
|
+
|
|
307
|
+
def _fig_to_base64(self, fig) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Convert matplotlib figure to base64 data URI.
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
fig : matplotlib.figure.Figure
|
|
314
|
+
Figure to convert
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
str
|
|
319
|
+
Base64-encoded PNG data URI
|
|
320
|
+
"""
|
|
321
|
+
buf = BytesIO()
|
|
322
|
+
fig.savefig(buf, format='png', dpi=self.dpi, bbox_inches='tight')
|
|
323
|
+
buf.seek(0)
|
|
324
|
+
|
|
325
|
+
# Encode as base64
|
|
326
|
+
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
|
|
327
|
+
|
|
328
|
+
# Close figure to free memory
|
|
329
|
+
plt.close(fig)
|
|
330
|
+
|
|
331
|
+
# Create data URI
|
|
332
|
+
return f"data:image/png;base64,{img_base64}"
|
|
333
|
+
|
|
334
|
+
def __repr__(self) -> str:
|
|
335
|
+
"""String representation."""
|
|
336
|
+
return (
|
|
337
|
+
f"StaticValidationRenderer("
|
|
338
|
+
f"figure_size={self.figure_size}, "
|
|
339
|
+
f"dpi={self.dpi}, "
|
|
340
|
+
f"style='{self.style}')"
|
|
341
|
+
)
|