panelbox 0.2.0__py3-none-any.whl → 0.4.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 +41 -0
- panelbox/__version__.py +13 -1
- panelbox/core/formula_parser.py +9 -2
- panelbox/core/panel_data.py +1 -1
- panelbox/datasets/__init__.py +39 -0
- panelbox/datasets/load.py +334 -0
- panelbox/gmm/difference_gmm.py +63 -15
- panelbox/gmm/estimator.py +46 -5
- panelbox/gmm/system_gmm.py +136 -21
- panelbox/models/static/__init__.py +4 -0
- panelbox/models/static/between.py +434 -0
- panelbox/models/static/first_difference.py +494 -0
- panelbox/models/static/fixed_effects.py +80 -11
- panelbox/models/static/pooled_ols.py +80 -11
- panelbox/models/static/random_effects.py +52 -10
- panelbox/standard_errors/__init__.py +119 -0
- panelbox/standard_errors/clustered.py +386 -0
- panelbox/standard_errors/comparison.py +528 -0
- panelbox/standard_errors/driscoll_kraay.py +386 -0
- panelbox/standard_errors/newey_west.py +324 -0
- panelbox/standard_errors/pcse.py +358 -0
- panelbox/standard_errors/robust.py +324 -0
- panelbox/standard_errors/utils.py +390 -0
- panelbox/validation/__init__.py +6 -0
- panelbox/validation/robustness/__init__.py +51 -0
- panelbox/validation/robustness/bootstrap.py +933 -0
- panelbox/validation/robustness/checks.py +143 -0
- panelbox/validation/robustness/cross_validation.py +538 -0
- panelbox/validation/robustness/influence.py +364 -0
- panelbox/validation/robustness/jackknife.py +457 -0
- panelbox/validation/robustness/outliers.py +529 -0
- panelbox/validation/robustness/sensitivity.py +809 -0
- {panelbox-0.2.0.dist-info → panelbox-0.4.0.dist-info}/METADATA +32 -3
- {panelbox-0.2.0.dist-info → panelbox-0.4.0.dist-info}/RECORD +38 -21
- {panelbox-0.2.0.dist-info → panelbox-0.4.0.dist-info}/WHEEL +1 -1
- {panelbox-0.2.0.dist-info → panelbox-0.4.0.dist-info}/entry_points.txt +0 -0
- {panelbox-0.2.0.dist-info → panelbox-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {panelbox-0.2.0.dist-info → panelbox-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard Error Comparison Tools
|
|
3
|
+
|
|
4
|
+
This module provides tools for comparing different types of standard errors
|
|
5
|
+
for the same model specification. It allows researchers to assess the impact
|
|
6
|
+
of different SE assumptions on inference.
|
|
7
|
+
|
|
8
|
+
Classes
|
|
9
|
+
-------
|
|
10
|
+
StandardErrorComparison
|
|
11
|
+
Compare multiple standard error types for a given model
|
|
12
|
+
|
|
13
|
+
Examples
|
|
14
|
+
--------
|
|
15
|
+
>>> import panelbox as pb
|
|
16
|
+
>>> import pandas as pd
|
|
17
|
+
>>>
|
|
18
|
+
>>> # Fit model
|
|
19
|
+
>>> fe = pb.FixedEffects("y ~ x1 + x2", data, "entity", "time")
|
|
20
|
+
>>> results = fe.fit()
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Compare all SE types
|
|
23
|
+
>>> comparison = pb.StandardErrorComparison(results)
|
|
24
|
+
>>> comp_df = comparison.compare_all()
|
|
25
|
+
>>> print(comp_df)
|
|
26
|
+
>>>
|
|
27
|
+
>>> # Plot comparison
|
|
28
|
+
>>> comparison.plot_comparison()
|
|
29
|
+
|
|
30
|
+
References
|
|
31
|
+
----------
|
|
32
|
+
- Petersen, M. A. (2009). Estimating standard errors in finance panel data sets:
|
|
33
|
+
Comparing approaches. Review of Financial Studies, 22(1), 435-480.
|
|
34
|
+
- Thompson, S. B. (2011). Simple formulas for standard errors that cluster by
|
|
35
|
+
both firm and time. Journal of Financial Economics, 99(1), 1-10.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import numpy as np
|
|
39
|
+
import pandas as pd
|
|
40
|
+
from typing import Dict, List, Optional, Union, Any
|
|
41
|
+
from dataclasses import dataclass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ComparisonResult:
|
|
46
|
+
"""
|
|
47
|
+
Results from comparing multiple standard error types.
|
|
48
|
+
|
|
49
|
+
Attributes
|
|
50
|
+
----------
|
|
51
|
+
se_comparison : pd.DataFrame
|
|
52
|
+
DataFrame with columns for each SE type and rows for each coefficient
|
|
53
|
+
se_ratios : pd.DataFrame
|
|
54
|
+
Ratios of each SE type relative to nonrobust (baseline)
|
|
55
|
+
t_stats : pd.DataFrame
|
|
56
|
+
t-statistics for each coefficient under each SE type
|
|
57
|
+
p_values : pd.DataFrame
|
|
58
|
+
p-values for each coefficient under each SE type
|
|
59
|
+
ci_lower : pd.DataFrame
|
|
60
|
+
Lower bounds of 95% confidence intervals
|
|
61
|
+
ci_upper : pd.DataFrame
|
|
62
|
+
Upper bounds of 95% confidence intervals
|
|
63
|
+
significance : pd.DataFrame
|
|
64
|
+
Significance indicators (*, **, ***) for each SE type
|
|
65
|
+
summary_stats : pd.DataFrame
|
|
66
|
+
Summary statistics across SE types
|
|
67
|
+
"""
|
|
68
|
+
se_comparison: pd.DataFrame
|
|
69
|
+
se_ratios: pd.DataFrame
|
|
70
|
+
t_stats: pd.DataFrame
|
|
71
|
+
p_values: pd.DataFrame
|
|
72
|
+
ci_lower: pd.DataFrame
|
|
73
|
+
ci_upper: pd.DataFrame
|
|
74
|
+
significance: pd.DataFrame
|
|
75
|
+
summary_stats: pd.DataFrame
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class StandardErrorComparison:
|
|
79
|
+
"""
|
|
80
|
+
Compare multiple standard error types for a panel data model.
|
|
81
|
+
|
|
82
|
+
This class facilitates comparison of different robust standard error
|
|
83
|
+
estimators to assess the sensitivity of inference to SE assumptions.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
model_results : PanelResults
|
|
88
|
+
Fitted model results object
|
|
89
|
+
|
|
90
|
+
Attributes
|
|
91
|
+
----------
|
|
92
|
+
model_results : PanelResults
|
|
93
|
+
Original model results
|
|
94
|
+
coef_names : list
|
|
95
|
+
Coefficient names
|
|
96
|
+
coefficients : np.ndarray
|
|
97
|
+
Point estimates (same across all SE types)
|
|
98
|
+
df_resid : int
|
|
99
|
+
Residual degrees of freedom
|
|
100
|
+
|
|
101
|
+
Methods
|
|
102
|
+
-------
|
|
103
|
+
compare_all(se_types=None, **kwargs)
|
|
104
|
+
Compare all specified SE types
|
|
105
|
+
compare_pair(se_type1, se_type2, **kwargs)
|
|
106
|
+
Compare two specific SE types
|
|
107
|
+
plot_comparison(result=None, alpha=0.05)
|
|
108
|
+
Plot comparison of standard errors
|
|
109
|
+
summary(result=None)
|
|
110
|
+
Print summary of comparison
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
Compare all SE types:
|
|
115
|
+
|
|
116
|
+
>>> fe = FixedEffects("y ~ x1 + x2", data, "entity", "time")
|
|
117
|
+
>>> results = fe.fit()
|
|
118
|
+
>>> comparison = StandardErrorComparison(results)
|
|
119
|
+
>>> comp = comparison.compare_all()
|
|
120
|
+
>>> print(comp.se_comparison)
|
|
121
|
+
|
|
122
|
+
Compare specific pair:
|
|
123
|
+
|
|
124
|
+
>>> comp = comparison.compare_pair('robust', 'clustered')
|
|
125
|
+
>>> print(f"Max difference: {comp.se_ratios.max().max():.3f}")
|
|
126
|
+
|
|
127
|
+
Plot comparison:
|
|
128
|
+
|
|
129
|
+
>>> comparison.plot_comparison()
|
|
130
|
+
|
|
131
|
+
References
|
|
132
|
+
----------
|
|
133
|
+
- Petersen, M. A. (2009). Review of Financial Studies, 22(1), 435-480.
|
|
134
|
+
- Thompson, S. B. (2011). Journal of Financial Economics, 99(1), 1-10.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, model_results):
|
|
138
|
+
"""
|
|
139
|
+
Initialize comparison with fitted model results.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
model_results : PanelResults
|
|
144
|
+
Fitted model results from FixedEffects, RandomEffects, or PooledOLS
|
|
145
|
+
"""
|
|
146
|
+
self.model_results = model_results
|
|
147
|
+
self.coef_names = model_results.params.index.tolist()
|
|
148
|
+
self.coefficients = model_results.params.values
|
|
149
|
+
self.df_resid = model_results.df_resid
|
|
150
|
+
|
|
151
|
+
# Extract model info for computing SEs
|
|
152
|
+
self._extract_model_info()
|
|
153
|
+
|
|
154
|
+
def _extract_model_info(self):
|
|
155
|
+
"""Extract model information for computing different SEs."""
|
|
156
|
+
# Store original model object if available
|
|
157
|
+
# Check multiple possible attribute names
|
|
158
|
+
self.model = getattr(self.model_results, 'model', None)
|
|
159
|
+
|
|
160
|
+
# If model not available, store what we need from results
|
|
161
|
+
if self.model is None:
|
|
162
|
+
# Store residuals and fitted values
|
|
163
|
+
self.resid = self.model_results.resid
|
|
164
|
+
self.fittedvalues = self.model_results.fittedvalues
|
|
165
|
+
|
|
166
|
+
# Try to reconstruct design matrix from model_info
|
|
167
|
+
# This is a fallback if we can't refit the model
|
|
168
|
+
self._has_model = False
|
|
169
|
+
else:
|
|
170
|
+
self._has_model = True
|
|
171
|
+
|
|
172
|
+
def compare_all(
|
|
173
|
+
self,
|
|
174
|
+
se_types: Optional[List[str]] = None,
|
|
175
|
+
**kwargs
|
|
176
|
+
) -> ComparisonResult:
|
|
177
|
+
"""
|
|
178
|
+
Compare all specified standard error types.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
se_types : list of str, optional
|
|
183
|
+
List of SE types to compare. If None, uses default list:
|
|
184
|
+
['nonrobust', 'robust', 'hc3', 'clustered', 'twoway', 'driscoll_kraay']
|
|
185
|
+
**kwargs : dict
|
|
186
|
+
Additional parameters for specific SE types:
|
|
187
|
+
- max_lags : int, for driscoll_kraay and newey_west
|
|
188
|
+
- kernel : str, for driscoll_kraay and newey_west
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
ComparisonResult
|
|
193
|
+
Object containing all comparison results
|
|
194
|
+
|
|
195
|
+
Examples
|
|
196
|
+
--------
|
|
197
|
+
>>> comparison = StandardErrorComparison(results)
|
|
198
|
+
>>> comp = comparison.compare_all()
|
|
199
|
+
>>> print(comp.se_comparison)
|
|
200
|
+
|
|
201
|
+
>>> # Custom SE types
|
|
202
|
+
>>> comp = comparison.compare_all(['nonrobust', 'robust', 'clustered'])
|
|
203
|
+
|
|
204
|
+
>>> # With parameters
|
|
205
|
+
>>> comp = comparison.compare_all(
|
|
206
|
+
... se_types=['driscoll_kraay', 'newey_west'],
|
|
207
|
+
... max_lags=3
|
|
208
|
+
... )
|
|
209
|
+
"""
|
|
210
|
+
if se_types is None:
|
|
211
|
+
# Default list of SE types to compare
|
|
212
|
+
se_types = ['nonrobust', 'robust', 'hc3', 'clustered']
|
|
213
|
+
|
|
214
|
+
# Add advanced types if T is large enough
|
|
215
|
+
if hasattr(self.model_results, 'nobs') and self.model_results.nobs > 100:
|
|
216
|
+
se_types.extend(['driscoll_kraay', 'newey_west'])
|
|
217
|
+
|
|
218
|
+
# Store standard errors for each type
|
|
219
|
+
se_dict = {}
|
|
220
|
+
|
|
221
|
+
for se_type in se_types:
|
|
222
|
+
try:
|
|
223
|
+
# Refit model with specific SE type
|
|
224
|
+
if self.model is not None:
|
|
225
|
+
# Get SE-specific kwargs
|
|
226
|
+
se_kwargs = self._get_se_kwargs(se_type, **kwargs)
|
|
227
|
+
results = self.model.fit(cov_type=se_type, **se_kwargs)
|
|
228
|
+
se_dict[se_type] = results.std_errors.values
|
|
229
|
+
else:
|
|
230
|
+
# Can't refit, skip this SE type
|
|
231
|
+
print(f"Warning: Cannot refit model for {se_type}")
|
|
232
|
+
continue
|
|
233
|
+
except Exception as e:
|
|
234
|
+
print(f"Warning: Failed to compute {se_type} SEs: {str(e)}")
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
if not se_dict:
|
|
238
|
+
raise ValueError("No SE types could be computed successfully")
|
|
239
|
+
|
|
240
|
+
# Create comparison DataFrame
|
|
241
|
+
se_comparison = pd.DataFrame(se_dict, index=self.coef_names)
|
|
242
|
+
|
|
243
|
+
# Compute ratios relative to nonrobust (if available)
|
|
244
|
+
if 'nonrobust' in se_dict:
|
|
245
|
+
se_ratios = se_comparison.div(se_comparison['nonrobust'], axis=0)
|
|
246
|
+
else:
|
|
247
|
+
# Use first SE type as baseline
|
|
248
|
+
baseline = list(se_dict.keys())[0]
|
|
249
|
+
se_ratios = se_comparison.div(se_comparison[baseline], axis=0)
|
|
250
|
+
|
|
251
|
+
# Compute t-statistics
|
|
252
|
+
t_stats = pd.DataFrame(
|
|
253
|
+
{se_type: self.coefficients / se_dict[se_type]
|
|
254
|
+
for se_type in se_dict.keys()},
|
|
255
|
+
index=self.coef_names
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Compute p-values (two-tailed)
|
|
259
|
+
from scipy import stats
|
|
260
|
+
p_values = pd.DataFrame(
|
|
261
|
+
{se_type: 2 * (1 - stats.t.cdf(np.abs(t_stats[se_type]), self.df_resid))
|
|
262
|
+
for se_type in se_dict.keys()},
|
|
263
|
+
index=self.coef_names
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Compute 95% confidence intervals
|
|
267
|
+
t_crit = stats.t.ppf(0.975, self.df_resid)
|
|
268
|
+
ci_lower = pd.DataFrame(
|
|
269
|
+
{se_type: self.coefficients - t_crit * se_dict[se_type]
|
|
270
|
+
for se_type in se_dict.keys()},
|
|
271
|
+
index=self.coef_names
|
|
272
|
+
)
|
|
273
|
+
ci_upper = pd.DataFrame(
|
|
274
|
+
{se_type: self.coefficients + t_crit * se_dict[se_type]
|
|
275
|
+
for se_type in se_dict.keys()},
|
|
276
|
+
index=self.coef_names
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Significance indicators
|
|
280
|
+
significance = p_values.copy()
|
|
281
|
+
significance = significance.applymap(self._significance_stars)
|
|
282
|
+
|
|
283
|
+
# Summary statistics
|
|
284
|
+
summary_stats = pd.DataFrame({
|
|
285
|
+
'mean_se': se_comparison.mean(axis=1),
|
|
286
|
+
'std_se': se_comparison.std(axis=1),
|
|
287
|
+
'min_se': se_comparison.min(axis=1),
|
|
288
|
+
'max_se': se_comparison.max(axis=1),
|
|
289
|
+
'range_se': se_comparison.max(axis=1) - se_comparison.min(axis=1),
|
|
290
|
+
'cv_se': se_comparison.std(axis=1) / se_comparison.mean(axis=1) # Coefficient of variation
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return ComparisonResult(
|
|
294
|
+
se_comparison=se_comparison,
|
|
295
|
+
se_ratios=se_ratios,
|
|
296
|
+
t_stats=t_stats,
|
|
297
|
+
p_values=p_values,
|
|
298
|
+
ci_lower=ci_lower,
|
|
299
|
+
ci_upper=ci_upper,
|
|
300
|
+
significance=significance,
|
|
301
|
+
summary_stats=summary_stats
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def compare_pair(
|
|
305
|
+
self,
|
|
306
|
+
se_type1: str,
|
|
307
|
+
se_type2: str,
|
|
308
|
+
**kwargs
|
|
309
|
+
) -> ComparisonResult:
|
|
310
|
+
"""
|
|
311
|
+
Compare two specific standard error types.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
se_type1 : str
|
|
316
|
+
First SE type (e.g., 'nonrobust')
|
|
317
|
+
se_type2 : str
|
|
318
|
+
Second SE type (e.g., 'clustered')
|
|
319
|
+
**kwargs : dict
|
|
320
|
+
Additional parameters for SE types
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
ComparisonResult
|
|
325
|
+
Comparison results for the two SE types
|
|
326
|
+
|
|
327
|
+
Examples
|
|
328
|
+
--------
|
|
329
|
+
>>> comp = comparison.compare_pair('robust', 'clustered')
|
|
330
|
+
>>> print(comp.se_ratios)
|
|
331
|
+
"""
|
|
332
|
+
return self.compare_all(se_types=[se_type1, se_type2], **kwargs)
|
|
333
|
+
|
|
334
|
+
def plot_comparison(
|
|
335
|
+
self,
|
|
336
|
+
result: Optional[ComparisonResult] = None,
|
|
337
|
+
alpha: float = 0.05,
|
|
338
|
+
figsize: tuple = (12, 8)
|
|
339
|
+
):
|
|
340
|
+
"""
|
|
341
|
+
Plot comparison of standard errors and confidence intervals.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
result : ComparisonResult, optional
|
|
346
|
+
Pre-computed comparison result. If None, computes comparison.
|
|
347
|
+
alpha : float, default=0.05
|
|
348
|
+
Significance level for confidence intervals
|
|
349
|
+
figsize : tuple, default=(12, 8)
|
|
350
|
+
Figure size (width, height)
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
fig : matplotlib.figure.Figure
|
|
355
|
+
The figure object
|
|
356
|
+
|
|
357
|
+
Examples
|
|
358
|
+
--------
|
|
359
|
+
>>> comparison.plot_comparison()
|
|
360
|
+
>>>
|
|
361
|
+
>>> # Custom figure size
|
|
362
|
+
>>> comparison.plot_comparison(figsize=(14, 10))
|
|
363
|
+
|
|
364
|
+
Notes
|
|
365
|
+
-----
|
|
366
|
+
Requires matplotlib to be installed.
|
|
367
|
+
"""
|
|
368
|
+
try:
|
|
369
|
+
import matplotlib.pyplot as plt
|
|
370
|
+
except ImportError:
|
|
371
|
+
raise ImportError(
|
|
372
|
+
"Matplotlib is required for plotting. "
|
|
373
|
+
"Install it with: pip install matplotlib"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if result is None:
|
|
377
|
+
result = self.compare_all()
|
|
378
|
+
|
|
379
|
+
n_coefs = len(self.coef_names)
|
|
380
|
+
n_se_types = len(result.se_comparison.columns)
|
|
381
|
+
|
|
382
|
+
# Create figure with subplots
|
|
383
|
+
fig, axes = plt.subplots(2, 1, figsize=figsize)
|
|
384
|
+
|
|
385
|
+
# Plot 1: Standard Errors Comparison
|
|
386
|
+
ax1 = axes[0]
|
|
387
|
+
result.se_comparison.plot(kind='bar', ax=ax1)
|
|
388
|
+
ax1.set_title('Standard Errors Comparison', fontsize=14, fontweight='bold')
|
|
389
|
+
ax1.set_xlabel('Coefficient', fontsize=12)
|
|
390
|
+
ax1.set_ylabel('Standard Error', fontsize=12)
|
|
391
|
+
ax1.legend(title='SE Type', bbox_to_anchor=(1.05, 1), loc='upper left')
|
|
392
|
+
ax1.grid(axis='y', alpha=0.3)
|
|
393
|
+
|
|
394
|
+
# Plot 2: Coefficient Estimates with Confidence Intervals
|
|
395
|
+
ax2 = axes[1]
|
|
396
|
+
x = np.arange(n_coefs)
|
|
397
|
+
width = 0.8 / n_se_types
|
|
398
|
+
|
|
399
|
+
for i, se_type in enumerate(result.se_comparison.columns):
|
|
400
|
+
offset = (i - n_se_types/2 + 0.5) * width
|
|
401
|
+
ax2.errorbar(
|
|
402
|
+
x + offset,
|
|
403
|
+
self.coefficients,
|
|
404
|
+
yerr=[
|
|
405
|
+
self.coefficients - result.ci_lower[se_type].values,
|
|
406
|
+
result.ci_upper[se_type].values - self.coefficients
|
|
407
|
+
],
|
|
408
|
+
fmt='o',
|
|
409
|
+
label=se_type,
|
|
410
|
+
capsize=5,
|
|
411
|
+
capthick=2
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
ax2.axhline(y=0, color='black', linestyle='--', alpha=0.3)
|
|
415
|
+
ax2.set_title('Coefficient Estimates with 95% Confidence Intervals',
|
|
416
|
+
fontsize=14, fontweight='bold')
|
|
417
|
+
ax2.set_xlabel('Coefficient', fontsize=12)
|
|
418
|
+
ax2.set_ylabel('Estimate', fontsize=12)
|
|
419
|
+
ax2.set_xticks(x)
|
|
420
|
+
ax2.set_xticklabels(self.coef_names, rotation=45, ha='right')
|
|
421
|
+
ax2.legend(title='SE Type', bbox_to_anchor=(1.05, 1), loc='upper left')
|
|
422
|
+
ax2.grid(axis='y', alpha=0.3)
|
|
423
|
+
|
|
424
|
+
plt.tight_layout()
|
|
425
|
+
return fig
|
|
426
|
+
|
|
427
|
+
def summary(self, result: Optional[ComparisonResult] = None):
|
|
428
|
+
"""
|
|
429
|
+
Print summary of standard error comparison.
|
|
430
|
+
|
|
431
|
+
Parameters
|
|
432
|
+
----------
|
|
433
|
+
result : ComparisonResult, optional
|
|
434
|
+
Pre-computed comparison result. If None, computes comparison.
|
|
435
|
+
|
|
436
|
+
Examples
|
|
437
|
+
--------
|
|
438
|
+
>>> comparison.summary()
|
|
439
|
+
"""
|
|
440
|
+
if result is None:
|
|
441
|
+
result = self.compare_all()
|
|
442
|
+
|
|
443
|
+
print("=" * 80)
|
|
444
|
+
print("STANDARD ERROR COMPARISON SUMMARY")
|
|
445
|
+
print("=" * 80)
|
|
446
|
+
print()
|
|
447
|
+
|
|
448
|
+
print("Standard Errors by Type:")
|
|
449
|
+
print("-" * 80)
|
|
450
|
+
print(result.se_comparison.to_string())
|
|
451
|
+
print()
|
|
452
|
+
|
|
453
|
+
print("Standard Error Ratios (relative to baseline):")
|
|
454
|
+
print("-" * 80)
|
|
455
|
+
print(result.se_ratios.to_string(float_format=lambda x: f"{x:.3f}"))
|
|
456
|
+
print()
|
|
457
|
+
|
|
458
|
+
print("Significance Levels (* p<0.10, ** p<0.05, *** p<0.01):")
|
|
459
|
+
print("-" * 80)
|
|
460
|
+
|
|
461
|
+
# Combine coefficients with significance
|
|
462
|
+
sig_table = pd.DataFrame({
|
|
463
|
+
'Coefficient': self.coefficients
|
|
464
|
+
})
|
|
465
|
+
for col in result.significance.columns:
|
|
466
|
+
sig_table[col] = result.significance[col]
|
|
467
|
+
print(sig_table.to_string(float_format=lambda x: f"{x:.4f}" if isinstance(x, float) else str(x)))
|
|
468
|
+
print()
|
|
469
|
+
|
|
470
|
+
print("Summary Statistics Across SE Types:")
|
|
471
|
+
print("-" * 80)
|
|
472
|
+
print(result.summary_stats.to_string(float_format=lambda x: f"{x:.4f}"))
|
|
473
|
+
print()
|
|
474
|
+
|
|
475
|
+
# Inference sensitivity analysis
|
|
476
|
+
print("Inference Sensitivity:")
|
|
477
|
+
print("-" * 80)
|
|
478
|
+
|
|
479
|
+
# Count significant coefficients by SE type
|
|
480
|
+
sig_counts = (result.p_values < 0.05).sum()
|
|
481
|
+
print(f"Coefficients significant at 5% level:")
|
|
482
|
+
for se_type, count in sig_counts.items():
|
|
483
|
+
print(f" {se_type:20s}: {count}/{len(self.coef_names)}")
|
|
484
|
+
print()
|
|
485
|
+
|
|
486
|
+
# Identify coefficients with inconsistent inference
|
|
487
|
+
sig_matrix = result.p_values < 0.05
|
|
488
|
+
inconsistent = sig_matrix.sum(axis=1)
|
|
489
|
+
inconsistent = inconsistent[(inconsistent > 0) & (inconsistent < len(result.p_values.columns))]
|
|
490
|
+
|
|
491
|
+
if len(inconsistent) > 0:
|
|
492
|
+
print("⚠️ Coefficients with inconsistent inference across SE types:")
|
|
493
|
+
for coef in inconsistent.index:
|
|
494
|
+
sig_types = sig_matrix.loc[coef]
|
|
495
|
+
sig_list = [st for st, is_sig in sig_types.items() if is_sig]
|
|
496
|
+
nonsig_list = [st for st, is_sig in sig_types.items() if not is_sig]
|
|
497
|
+
print(f" {coef}:")
|
|
498
|
+
print(f" Significant with: {', '.join(sig_list)}")
|
|
499
|
+
print(f" Not significant with: {', '.join(nonsig_list)}")
|
|
500
|
+
else:
|
|
501
|
+
print("✓ Inference is consistent across all SE types")
|
|
502
|
+
|
|
503
|
+
print()
|
|
504
|
+
print("=" * 80)
|
|
505
|
+
|
|
506
|
+
def _get_se_kwargs(self, se_type: str, **kwargs) -> Dict[str, Any]:
|
|
507
|
+
"""Get SE-specific keyword arguments."""
|
|
508
|
+
se_kwargs = {}
|
|
509
|
+
|
|
510
|
+
if se_type in ['driscoll_kraay', 'newey_west']:
|
|
511
|
+
if 'max_lags' in kwargs:
|
|
512
|
+
se_kwargs['max_lags'] = kwargs['max_lags']
|
|
513
|
+
if 'kernel' in kwargs:
|
|
514
|
+
se_kwargs['kernel'] = kwargs['kernel']
|
|
515
|
+
|
|
516
|
+
return se_kwargs
|
|
517
|
+
|
|
518
|
+
@staticmethod
|
|
519
|
+
def _significance_stars(p_value: float) -> str:
|
|
520
|
+
"""Convert p-value to significance stars."""
|
|
521
|
+
if p_value < 0.01:
|
|
522
|
+
return '***'
|
|
523
|
+
elif p_value < 0.05:
|
|
524
|
+
return '**'
|
|
525
|
+
elif p_value < 0.10:
|
|
526
|
+
return '*'
|
|
527
|
+
else:
|
|
528
|
+
return ''
|