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
panelbox/gmm/tests.py ADDED
@@ -0,0 +1,535 @@
1
+ """
2
+ GMM Specification Tests
3
+ ========================
4
+
5
+ Specification tests for GMM models including Hansen J-test, Sargan test,
6
+ Arellano-Bond autocorrelation tests, and Difference-in-Hansen test.
7
+
8
+ Classes
9
+ -------
10
+ GMMTests : Specification tests for GMM models
11
+
12
+ References
13
+ ----------
14
+ .. [1] Hansen, L. P. (1982). "Large Sample Properties of Generalized Method
15
+ of Moments Estimators." Econometrica, 50(4), 1029-1054.
16
+
17
+ .. [2] Sargan, J. D. (1958). "The Estimation of Economic Relationships using
18
+ Instrumental Variables." Econometrica, 26(3), 393-415.
19
+
20
+ .. [3] Arellano, M., & Bond, S. (1991). "Some Tests of Specification for Panel
21
+ Data: Monte Carlo Evidence and an Application to Employment Equations."
22
+ Review of Economic Studies, 58(2), 277-297.
23
+ """
24
+
25
+ from typing import Tuple, Optional
26
+ import numpy as np
27
+ from scipy import stats
28
+ from panelbox.gmm.results import TestResult
29
+
30
+
31
+ class GMMTests:
32
+ """
33
+ Specification tests for GMM models.
34
+
35
+ Implements:
36
+ - Hansen J-test of overidentifying restrictions
37
+ - Sargan test (non-robust version)
38
+ - Arellano-Bond AR(1) and AR(2) tests
39
+ - Difference-in-Hansen test for instrument subsets
40
+
41
+ Examples
42
+ --------
43
+ >>> tester = GMMTests()
44
+ >>> hansen = tester.hansen_j_test(residuals, Z, W, n_params)
45
+ >>> ar2 = tester.arellano_bond_ar_test(residuals_diff, order=2)
46
+ """
47
+
48
+ def hansen_j_test(self,
49
+ residuals: np.ndarray,
50
+ Z: np.ndarray,
51
+ W: np.ndarray,
52
+ n_params: int) -> TestResult:
53
+ """
54
+ Hansen (1982) J-test of overidentifying restrictions.
55
+
56
+ Tests the validity of the moment conditions (instrument validity).
57
+ Under the null hypothesis that instruments are valid, the J-statistic
58
+ follows a chi-square distribution.
59
+
60
+ Parameters
61
+ ----------
62
+ residuals : np.ndarray
63
+ Model residuals (n x 1)
64
+ Z : np.ndarray
65
+ Instrument matrix (n x n_instruments)
66
+ W : np.ndarray
67
+ GMM weight matrix (n_instruments x n_instruments)
68
+ n_params : int
69
+ Number of parameters estimated
70
+
71
+ Returns
72
+ -------
73
+ TestResult
74
+ Hansen J-test result
75
+
76
+ Notes
77
+ -----
78
+ H0: All instruments are valid (E[Z'ε] = 0)
79
+ Test statistic: J = n * (Z'ε)' W (Z'ε) ~ χ²(n_instruments - n_params)
80
+
81
+ Interpretation:
82
+ - p-value < 0.10: Reject H0 (instruments invalid)
83
+ - p-value > 0.25: Suspicious (may indicate weak instruments)
84
+ - 0.10 < p-value < 0.25: Good (instruments valid)
85
+
86
+ References
87
+ ----------
88
+ Hansen, L. P. (1982). Econometrica, 50(4), 1029-1054.
89
+ """
90
+ # Remove missing values
91
+ residuals = residuals.flatten() if residuals.ndim > 1 else residuals
92
+ valid_mask = ~np.isnan(residuals)
93
+ resid_clean = residuals[valid_mask]
94
+ Z_clean = Z[valid_mask, :]
95
+
96
+ n = len(resid_clean)
97
+ n_instruments = Z_clean.shape[1]
98
+
99
+ # Compute moment conditions: g_n = (1/n) Z'ε
100
+ g_n = (Z_clean.T @ resid_clean) / n
101
+
102
+ # Compute J statistic: J = n * g_n' W g_n
103
+ J_stat = n * (g_n.T @ W @ g_n)
104
+
105
+ # Degrees of freedom
106
+ df = n_instruments - n_params
107
+
108
+ if df <= 0:
109
+ # Exactly identified or under-identified
110
+ return TestResult(
111
+ name='Hansen J-test',
112
+ statistic=np.nan,
113
+ pvalue=np.nan,
114
+ df=df,
115
+ distribution='chi2',
116
+ null_hypothesis='All instruments are valid',
117
+ conclusion='N/A (exactly/under-identified)',
118
+ details={'message': 'Test not applicable: model is exactly or under-identified'}
119
+ )
120
+
121
+ # P-value from chi-square distribution
122
+ pvalue = 1 - stats.chi2.cdf(J_stat, df)
123
+
124
+ return TestResult(
125
+ name='Hansen J-test',
126
+ statistic=J_stat,
127
+ pvalue=pvalue,
128
+ df=df,
129
+ distribution='chi2',
130
+ null_hypothesis='All instruments are valid (overid restrictions hold)',
131
+ details={
132
+ 'n_instruments': n_instruments,
133
+ 'n_params': n_params,
134
+ 'overid_restrictions': df
135
+ }
136
+ )
137
+
138
+ def sargan_test(self,
139
+ residuals: np.ndarray,
140
+ Z: np.ndarray,
141
+ n_params: int) -> TestResult:
142
+ """
143
+ Sargan (1958) test of overidentifying restrictions.
144
+
145
+ Non-robust version of Hansen J-test. Only valid under homoskedasticity,
146
+ but reported for compatibility with Stata xtabond2.
147
+
148
+ Parameters
149
+ ----------
150
+ residuals : np.ndarray
151
+ Model residuals (n x 1)
152
+ Z : np.ndarray
153
+ Instrument matrix (n x n_instruments)
154
+ n_params : int
155
+ Number of parameters estimated
156
+
157
+ Returns
158
+ -------
159
+ TestResult
160
+ Sargan test result
161
+
162
+ Notes
163
+ -----
164
+ H0: All instruments are valid (under homoskedasticity)
165
+ Test statistic: S = (Z'ε)' (Z'Z)^{-1} (Z'ε) ~ χ²(n_instruments - n_params)
166
+
167
+ This is equivalent to Hansen J-test with W = (Z'Z)^{-1}.
168
+ Not robust to heteroskedasticity - use Hansen J-test for robustness.
169
+
170
+ References
171
+ ----------
172
+ Sargan, J. D. (1958). Econometrica, 26(3), 393-415.
173
+ """
174
+ # Remove missing values
175
+ residuals = residuals.flatten() if residuals.ndim > 1 else residuals
176
+ valid_mask = ~np.isnan(residuals)
177
+ resid_clean = residuals[valid_mask]
178
+ Z_clean = Z[valid_mask, :]
179
+
180
+ n = len(resid_clean)
181
+ n_instruments = Z_clean.shape[1]
182
+
183
+ # Degrees of freedom
184
+ df = n_instruments - n_params
185
+
186
+ if df <= 0:
187
+ return TestResult(
188
+ name='Sargan test',
189
+ statistic=np.nan,
190
+ pvalue=np.nan,
191
+ df=df,
192
+ distribution='chi2',
193
+ null_hypothesis='All instruments are valid (homoskedasticity)',
194
+ conclusion='N/A (exactly/under-identified)',
195
+ details={'message': 'Test not applicable: model is exactly or under-identified'}
196
+ )
197
+
198
+ # Compute Sargan statistic
199
+ # S = (Z'ε)' (Z'Z)^{-1} (Z'ε)
200
+ Zte = Z_clean.T @ resid_clean
201
+ ZtZ = Z_clean.T @ Z_clean
202
+
203
+ try:
204
+ ZtZ_inv = np.linalg.inv(ZtZ)
205
+ except np.linalg.LinAlgError:
206
+ ZtZ_inv = np.linalg.pinv(ZtZ)
207
+
208
+ S_stat = Zte.T @ ZtZ_inv @ Zte
209
+
210
+ # P-value from chi-square distribution
211
+ pvalue = 1 - stats.chi2.cdf(S_stat, df)
212
+
213
+ return TestResult(
214
+ name='Sargan test',
215
+ statistic=S_stat,
216
+ pvalue=pvalue,
217
+ df=df,
218
+ distribution='chi2',
219
+ null_hypothesis='All instruments valid (assumes homoskedasticity)',
220
+ details={
221
+ 'n_instruments': n_instruments,
222
+ 'n_params': n_params,
223
+ 'note': 'Not robust to heteroskedasticity. Use Hansen J-test for robustness.'
224
+ }
225
+ )
226
+
227
+ def arellano_bond_ar_test(self,
228
+ residuals_diff: np.ndarray,
229
+ ids: np.ndarray,
230
+ order: int = 1) -> TestResult:
231
+ """
232
+ Arellano-Bond (1991) test for autocorrelation in residuals.
233
+
234
+ Tests for serial correlation in the differenced residuals.
235
+ Critical for validating moment conditions in GMM.
236
+
237
+ Parameters
238
+ ----------
239
+ residuals_diff : np.ndarray
240
+ First-differenced residuals (Δε_it)
241
+ ids : np.ndarray
242
+ Cross-sectional unit identifiers
243
+ order : int
244
+ Order of autocorrelation to test (1 or 2)
245
+
246
+ Returns
247
+ -------
248
+ TestResult
249
+ AR test result
250
+
251
+ Notes
252
+ -----
253
+ H0: No autocorrelation of order `order` in differenced residuals
254
+
255
+ AR(1) test:
256
+ - Expected to REJECT (first-differencing induces MA(1))
257
+ - Reported for completeness, not a failure if rejected
258
+
259
+ AR(2) test:
260
+ - Should NOT reject (critical test)
261
+ - If AR(2) is rejected, moment conditions are invalid
262
+ - This invalidates lagged levels as instruments
263
+
264
+ Test statistic is asymptotically N(0,1).
265
+
266
+ References
267
+ ----------
268
+ Arellano, M., & Bond, S. (1991). Review of Economic Studies, 58(2), 277-297.
269
+ """
270
+ # Remove missing values
271
+ valid_mask = ~np.isnan(residuals_diff)
272
+ resid_clean = residuals_diff[valid_mask]
273
+ ids_clean = ids[valid_mask]
274
+
275
+ # Compute lagged residuals by group
276
+ unique_ids = np.unique(ids_clean)
277
+ n_groups = len(unique_ids)
278
+
279
+ # Store products of residuals and their lags
280
+ products = []
281
+
282
+ for group_id in unique_ids:
283
+ # Get residuals for this group
284
+ mask = ids_clean == group_id
285
+ group_resid = resid_clean[mask]
286
+
287
+ # Compute products: Δε_it * Δε_{i,t-order}
288
+ for t in range(order, len(group_resid)):
289
+ product = group_resid[t] * group_resid[t - order]
290
+ products.append(product)
291
+
292
+ if len(products) == 0:
293
+ return TestResult(
294
+ name=f'AR({order}) test',
295
+ statistic=np.nan,
296
+ pvalue=np.nan,
297
+ df=None,
298
+ distribution='normal',
299
+ null_hypothesis=f'No AR({order}) in differenced residuals',
300
+ conclusion='N/A (insufficient data)',
301
+ details={'message': 'Insufficient observations for AR test'}
302
+ )
303
+
304
+ products = np.array(products)
305
+
306
+ # Compute test statistic
307
+ # Under H0, E[Δε_it * Δε_{i,t-k}] = 0
308
+ mean_product = np.mean(products)
309
+ var_product = np.var(products, ddof=1)
310
+
311
+ if var_product == 0:
312
+ return TestResult(
313
+ name=f'AR({order}) test',
314
+ statistic=np.nan,
315
+ pvalue=np.nan,
316
+ df=None,
317
+ distribution='normal',
318
+ null_hypothesis=f'No AR({order}) in differenced residuals',
319
+ conclusion='N/A (zero variance)',
320
+ details={'message': 'Zero variance in products'}
321
+ )
322
+
323
+ # Normalize by standard error
324
+ se_product = np.sqrt(var_product / len(products))
325
+ z_stat = mean_product / se_product
326
+
327
+ # P-value from standard normal (two-sided test)
328
+ pvalue = 2 * (1 - stats.norm.cdf(np.abs(z_stat)))
329
+
330
+ # Determine null hypothesis and expected result
331
+ if order == 1:
332
+ null_hyp = 'No AR(1) in differenced residuals'
333
+ details = {
334
+ 'note': 'AR(1) rejection is EXPECTED due to MA(1) induced by differencing',
335
+ 'n_products': len(products)
336
+ }
337
+ else:
338
+ null_hyp = f'No AR({order}) in differenced residuals'
339
+ details = {
340
+ 'note': f'AR({order}) rejection indicates INVALID moment conditions',
341
+ 'n_products': len(products)
342
+ }
343
+
344
+ return TestResult(
345
+ name=f'AR({order}) test',
346
+ statistic=z_stat,
347
+ pvalue=pvalue,
348
+ df=None,
349
+ distribution='normal',
350
+ null_hypothesis=null_hyp,
351
+ details=details
352
+ )
353
+
354
+ def difference_in_hansen(self,
355
+ residuals: np.ndarray,
356
+ Z_full: np.ndarray,
357
+ Z_subset: np.ndarray,
358
+ W_full: np.ndarray,
359
+ W_subset: np.ndarray,
360
+ n_params: int,
361
+ subset_name: str = 'subset') -> TestResult:
362
+ """
363
+ Difference-in-Hansen test for instrument subsets.
364
+
365
+ Tests the validity of a specific subset of instruments by comparing
366
+ Hansen J statistics with and without the subset.
367
+
368
+ Parameters
369
+ ----------
370
+ residuals : np.ndarray
371
+ Model residuals (n x 1)
372
+ Z_full : np.ndarray
373
+ Full instrument matrix (n x n_instruments_full)
374
+ Z_subset : np.ndarray
375
+ Subset instrument matrix to test (n x n_instruments_subset)
376
+ W_full : np.ndarray
377
+ Weight matrix for full model
378
+ W_subset : np.ndarray
379
+ Weight matrix for model without subset
380
+ n_params : int
381
+ Number of parameters
382
+ subset_name : str
383
+ Name of instrument subset being tested
384
+
385
+ Returns
386
+ -------
387
+ TestResult
388
+ Difference-in-Hansen test result
389
+
390
+ Notes
391
+ -----
392
+ H0: Subset of instruments is valid
393
+
394
+ Test statistic: D = J_subset - J_full ~ χ²(df_subset - df_full)
395
+
396
+ Common uses in System GMM:
397
+ - Test validity of level instruments
398
+ - Test validity of specific GMM-type instruments
399
+ - Compare collapsed vs. uncollapsed instruments
400
+
401
+ References
402
+ ----------
403
+ Roodman, D. (2009). Stata Journal, 9(1), 86-136.
404
+ """
405
+ # Remove missing values
406
+ residuals = residuals.flatten() if residuals.ndim > 1 else residuals
407
+ valid_mask = ~np.isnan(residuals)
408
+ resid_clean = residuals[valid_mask]
409
+ Z_full_clean = Z_full[valid_mask, :] # 2D indexing
410
+ Z_subset_clean = Z_subset[valid_mask, :] # 2D indexing
411
+
412
+ n = len(resid_clean)
413
+
414
+ # Compute Hansen J for full model
415
+ n_instruments_full = Z_full_clean.shape[1]
416
+ df_full = n_instruments_full - n_params
417
+
418
+ g_n_full = (Z_full_clean.T @ resid_clean) / n
419
+ J_full = n * (g_n_full.T @ W_full @ g_n_full)
420
+
421
+ # Compute Hansen J for model without subset
422
+ n_instruments_subset = Z_subset_clean.shape[1]
423
+ df_subset = n_instruments_subset - n_params
424
+
425
+ g_n_subset = (Z_subset_clean.T @ resid_clean) / n
426
+ J_subset = n * (g_n_subset.T @ W_subset @ g_n_subset)
427
+
428
+ # Difference-in-Hansen statistic
429
+ D_stat = J_subset - J_full
430
+ df_diff = df_subset - df_full
431
+
432
+ if df_diff <= 0:
433
+ return TestResult(
434
+ name=f'Diff-in-Hansen ({subset_name})',
435
+ statistic=np.nan,
436
+ pvalue=np.nan,
437
+ df=df_diff,
438
+ distribution='chi2',
439
+ null_hypothesis=f'{subset_name} instruments are valid',
440
+ conclusion='N/A (invalid df)',
441
+ details={'message': 'Invalid degrees of freedom for difference test'}
442
+ )
443
+
444
+ # P-value from chi-square distribution
445
+ pvalue = 1 - stats.chi2.cdf(D_stat, df_diff)
446
+
447
+ return TestResult(
448
+ name=f'Diff-in-Hansen ({subset_name})',
449
+ statistic=D_stat,
450
+ pvalue=pvalue,
451
+ df=df_diff,
452
+ distribution='chi2',
453
+ null_hypothesis=f'{subset_name} instruments are valid',
454
+ details={
455
+ 'J_full': J_full,
456
+ 'J_subset': J_subset,
457
+ 'n_instruments_tested': df_diff
458
+ }
459
+ )
460
+
461
+ def weak_instruments_test(self,
462
+ X: np.ndarray,
463
+ Z: np.ndarray) -> Tuple[float, bool]:
464
+ """
465
+ Simple weak instruments diagnostic.
466
+
467
+ Computes the F-statistic from the first-stage regression of each
468
+ endogenous variable on the instruments.
469
+
470
+ Parameters
471
+ ----------
472
+ X : np.ndarray
473
+ Endogenous regressors (n x k)
474
+ Z : np.ndarray
475
+ Instruments (n x n_instruments)
476
+
477
+ Returns
478
+ -------
479
+ f_stat : float
480
+ Minimum F-statistic across first-stage regressions
481
+ weak : bool
482
+ True if instruments may be weak (F < 10)
483
+
484
+ Notes
485
+ -----
486
+ Rule of thumb: F-statistic > 10 suggests instruments are not weak.
487
+
488
+ For more sophisticated weak instruments tests, see:
489
+ - Stock & Yogo (2005) critical values
490
+ - Montiel Olea & Pflueger (2013) effective F-statistic
491
+ """
492
+ # Remove missing values
493
+ valid_mask = ~np.isnan(X).any(axis=1) & ~np.isnan(Z).any(axis=1)
494
+ X_clean = X[valid_mask]
495
+ Z_clean = Z[valid_mask]
496
+
497
+ n, k = X_clean.shape
498
+ n_instruments = Z_clean.shape[1]
499
+
500
+ # Compute F-statistics for each endogenous variable
501
+ f_stats = []
502
+
503
+ for j in range(k):
504
+ x_j = X_clean[:, j]
505
+
506
+ # First-stage regression: x_j = Z * π + u
507
+ ZtZ = Z_clean.T @ Z_clean
508
+ Ztx = Z_clean.T @ x_j
509
+
510
+ try:
511
+ ZtZ_inv = np.linalg.inv(ZtZ)
512
+ except np.linalg.LinAlgError:
513
+ ZtZ_inv = np.linalg.pinv(ZtZ)
514
+
515
+ pi = ZtZ_inv @ Ztx
516
+ x_j_fitted = Z_clean @ pi
517
+ resid = x_j - x_j_fitted
518
+
519
+ # Compute F-statistic
520
+ # F = (R² / k) / ((1 - R²) / (n - k - 1))
521
+ ss_total = np.sum((x_j - np.mean(x_j)) ** 2)
522
+ ss_resid = np.sum(resid ** 2)
523
+ r_squared = 1 - (ss_resid / ss_total)
524
+
525
+ if r_squared >= 1.0:
526
+ f_stat = np.inf
527
+ else:
528
+ f_stat = (r_squared / n_instruments) / ((1 - r_squared) / (n - n_instruments - 1))
529
+
530
+ f_stats.append(f_stat)
531
+
532
+ min_f_stat = np.min(f_stats)
533
+ weak = min_f_stat < 10.0
534
+
535
+ return min_f_stat, weak
@@ -0,0 +1,11 @@
1
+ """
2
+ Panel econometric models.
3
+
4
+ This package contains implementations of various panel data estimators.
5
+ """
6
+
7
+ from panelbox.models.static.pooled_ols import PooledOLS
8
+
9
+ __all__ = [
10
+ 'PooledOLS',
11
+ ]
File without changes
File without changes
@@ -0,0 +1,13 @@
1
+ """
2
+ Static panel models.
3
+ """
4
+
5
+ from panelbox.models.static.pooled_ols import PooledOLS
6
+ from panelbox.models.static.fixed_effects import FixedEffects
7
+ from panelbox.models.static.random_effects import RandomEffects
8
+
9
+ __all__ = [
10
+ 'PooledOLS',
11
+ 'FixedEffects',
12
+ 'RandomEffects',
13
+ ]