systemgmmkit 0.5.8.dev0__tar.gz → 0.5.9__tar.gz
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.
- {systemgmmkit-0.5.8.dev0/src/systemgmmkit.egg-info → systemgmmkit-0.5.9}/PKG-INFO +1 -1
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/pyproject.toml +2 -4
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/__init__.py +3 -1
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/native_gmm.py +42 -681
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9/src/systemgmmkit.egg-info}/PKG-INFO +1 -1
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/LICENSE +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/README.md +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/setup.cfg +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/cli.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/diagnostics/__init__.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/diagnostics/gmm.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/diagnostics/panel.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/diagnostics.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/dynamic_panel.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/estimators/first_difference.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/fixed_effects.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/gmm_parity_policy.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/linear.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/panel_iv.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/parity.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/postestimation.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/presets.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/pydynpd_backend.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/pydynpd_output_parser.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/random_effects.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/reporting/__init__.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/reporting/parity.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/reporting.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/spec.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/suite.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/tables.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/validation.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit.egg-info/SOURCES.txt +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit.egg-info/dependency_links.txt +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit.egg-info/entry_points.txt +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit.egg-info/requires.txt +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit.egg-info/top_level.txt +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_apply_gmm_parity_policy_script.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_first_difference_reporting.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_fixed_effects.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_generic_presets.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_gmm_parity_policy.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_ols_postestimation.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_parity_native_gmm.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_pydynpd_backend.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_pydynpd_output_parser.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_random_effects_iv_tables.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_spec_command.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_validation_diagnostics.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_decomposition_controls_certification.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_decomposition_controls_scaffold.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_no_controls_certification.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_no_controls_scaffold.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_parity.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_three_way_controls_certification.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_three_way_controls_scaffold.py +0 -0
- {systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/tests/test_xtabond2_system_gmm_uncorrected_se_baseline.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "systemgmmkit"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.9"
|
|
8
8
|
description = "Generic panel-data econometrics workflow helpers for FE, RE, IV/2SLS, and Difference/System GMM in Python."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -51,7 +51,7 @@ where = ["src"]
|
|
|
51
51
|
|
|
52
52
|
[tool.ruff]
|
|
53
53
|
line-length = 100
|
|
54
|
-
target-version = "0.5.
|
|
54
|
+
target-version = "0.5.9"
|
|
55
55
|
|
|
56
56
|
[tool.ruff.lint]
|
|
57
57
|
select = ["E", "F", "I", "B", "UP", "SIM"]
|
|
@@ -72,5 +72,3 @@ line-ending = "auto"
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
@@ -39,10 +39,6 @@ class NativeGMMResult:
|
|
|
39
39
|
sargan_p: float | None = None
|
|
40
40
|
ar1_p: float | None = None
|
|
41
41
|
ar2_p: float | None = None
|
|
42
|
-
ar1_z: float | None = None
|
|
43
|
-
ar2_z: float | None = None
|
|
44
|
-
ar1: float | None = None
|
|
45
|
-
ar2: float | None = None
|
|
46
42
|
z_shape: tuple[int, int] | None = None
|
|
47
43
|
w_shape: tuple[int, int] | None = None
|
|
48
44
|
j_stat: float | None = None
|
|
@@ -53,16 +49,6 @@ class NativeGMMResult:
|
|
|
53
49
|
overid_df: int | None = None
|
|
54
50
|
hansen_j_error: str | None = None
|
|
55
51
|
|
|
56
|
-
# Stata/xtabond2-style diagnostic aliases for export/comparison layers.
|
|
57
|
-
hansen: float | None = None
|
|
58
|
-
hansenp: float | None = None
|
|
59
|
-
sargan: float | None = None
|
|
60
|
-
sarganp: float | None = None
|
|
61
|
-
n_g: int | None = None
|
|
62
|
-
df_m: int | None = None
|
|
63
|
-
df_r: int | None = None
|
|
64
|
-
j_p: float | None = None
|
|
65
|
-
|
|
66
52
|
def summary_frame(self) -> pd.DataFrame:
|
|
67
53
|
return pd.DataFrame(
|
|
68
54
|
{
|
|
@@ -1253,461 +1239,66 @@ def _native_overid_diagnostics(
|
|
|
1253
1239
|
return pvalue, pvalue
|
|
1254
1240
|
|
|
1255
1241
|
|
|
1256
|
-
|
|
1257
|
-
def _native_export_ar_debug_inputs(
|
|
1258
|
-
*,
|
|
1259
|
-
spec: DynamicPanelSpec,
|
|
1260
|
-
data: pd.DataFrame,
|
|
1261
|
-
entity: str,
|
|
1262
|
-
time: str | None,
|
|
1263
|
-
row_index: np.ndarray,
|
|
1264
|
-
residual_vec: np.ndarray,
|
|
1265
|
-
row_meta: object | None = None,
|
|
1266
|
-
) -> None:
|
|
1267
|
-
"""Export residual-level inputs for AR diagnostic parity research.
|
|
1268
|
-
|
|
1269
|
-
Diagnostic-only. Activated only when SYSTEMGMMKIT_AR_DEBUG_DIR is set.
|
|
1270
|
-
"""
|
|
1271
|
-
|
|
1272
|
-
import json as _json
|
|
1273
|
-
import os as _os
|
|
1274
|
-
from pathlib import Path as _Path
|
|
1275
|
-
|
|
1276
|
-
debug_dir_raw = _os.getenv("SYSTEMGMMKIT_AR_DEBUG_DIR")
|
|
1277
|
-
if not debug_dir_raw:
|
|
1278
|
-
return
|
|
1279
|
-
|
|
1280
|
-
debug_dir = _Path(debug_dir_raw)
|
|
1281
|
-
debug_dir.mkdir(parents=True, exist_ok=True)
|
|
1282
|
-
|
|
1283
|
-
safe_name = "".join(
|
|
1284
|
-
ch if ch.isalnum() or ch in "-_." else "_"
|
|
1285
|
-
for ch in str(getattr(spec, "name", "native_gmm"))
|
|
1286
|
-
)
|
|
1287
|
-
|
|
1288
|
-
resid = np.asarray(residual_vec, dtype=float).reshape(-1)
|
|
1289
|
-
idx = np.asarray(row_index).reshape(-1)
|
|
1290
|
-
|
|
1291
|
-
base = data.copy()
|
|
1292
|
-
if time is not None and time in base.columns:
|
|
1293
|
-
base = base.sort_values([entity, time]).copy()
|
|
1294
|
-
else:
|
|
1295
|
-
base = base.sort_values([entity]).copy()
|
|
1296
|
-
|
|
1297
|
-
rows = None
|
|
1298
|
-
|
|
1299
|
-
try:
|
|
1300
|
-
idx_int = idx.astype(int)
|
|
1301
|
-
if (
|
|
1302
|
-
idx_int.size >= resid.size
|
|
1303
|
-
and idx_int.min(initial=0) >= 0
|
|
1304
|
-
and idx_int.max(initial=-1) < len(base)
|
|
1305
|
-
):
|
|
1306
|
-
rows = base.iloc[idx_int[: resid.size]].copy()
|
|
1307
|
-
except Exception:
|
|
1308
|
-
rows = None
|
|
1309
|
-
|
|
1310
|
-
if rows is None:
|
|
1311
|
-
try:
|
|
1312
|
-
if idx.size >= resid.size:
|
|
1313
|
-
rows = data.loc[idx[: resid.size]].copy()
|
|
1314
|
-
except Exception:
|
|
1315
|
-
rows = None
|
|
1316
|
-
|
|
1317
|
-
if rows is None or len(rows) != resid.size:
|
|
1318
|
-
rows = base.iloc[: min(len(base), resid.size)].copy()
|
|
1319
|
-
resid = resid[: len(rows)]
|
|
1320
|
-
|
|
1321
|
-
out = pd.DataFrame(
|
|
1322
|
-
{
|
|
1323
|
-
"spec_name": str(getattr(spec, "name", "")),
|
|
1324
|
-
"system": bool(getattr(spec, "system", False)),
|
|
1325
|
-
"transformation": str(getattr(spec, "transformation", "")),
|
|
1326
|
-
"row_number": np.arange(len(rows), dtype=int),
|
|
1327
|
-
"row_index": idx[: len(rows)] if idx.size >= len(rows) else np.arange(len(rows)),
|
|
1328
|
-
"entity": rows[entity].to_numpy() if entity in rows.columns else np.nan,
|
|
1329
|
-
"time": rows[time].to_numpy() if time is not None and time in rows.columns else np.nan,
|
|
1330
|
-
"residual": resid,
|
|
1331
|
-
}
|
|
1332
|
-
)
|
|
1333
|
-
|
|
1334
|
-
try:
|
|
1335
|
-
meta = list(row_meta) if row_meta is not None else []
|
|
1336
|
-
if len(meta) >= len(out):
|
|
1337
|
-
eq_values = []
|
|
1338
|
-
raw_values = []
|
|
1339
|
-
for item in meta[: len(out)]:
|
|
1340
|
-
raw_values.append(_json.dumps(item, default=str))
|
|
1341
|
-
eq = None
|
|
1342
|
-
if isinstance(item, dict):
|
|
1343
|
-
eq = item.get("eq") or item.get("equation") or item.get("block")
|
|
1344
|
-
else:
|
|
1345
|
-
eq = (
|
|
1346
|
-
getattr(item, "eq", None)
|
|
1347
|
-
or getattr(item, "equation", None)
|
|
1348
|
-
or getattr(item, "block", None)
|
|
1349
|
-
)
|
|
1350
|
-
eq_values.append(eq)
|
|
1351
|
-
out["equation"] = eq_values
|
|
1352
|
-
out["row_meta"] = raw_values
|
|
1353
|
-
except Exception:
|
|
1354
|
-
pass
|
|
1355
|
-
|
|
1356
|
-
out_path = debug_dir / f"{safe_name}_ar_debug_inputs.csv"
|
|
1357
|
-
out.to_csv(out_path, index=False)
|
|
1358
|
-
|
|
1359
|
-
meta_path = debug_dir / f"{safe_name}_ar_debug_inputs_meta.json"
|
|
1360
|
-
meta_path.write_text(
|
|
1361
|
-
_json.dumps(
|
|
1362
|
-
{
|
|
1363
|
-
"spec_name": str(getattr(spec, "name", "")),
|
|
1364
|
-
"system": bool(getattr(spec, "system", False)),
|
|
1365
|
-
"transformation": str(getattr(spec, "transformation", "")),
|
|
1366
|
-
"n_rows": int(len(out)),
|
|
1367
|
-
"source": "systemgmmkit native AR debug export",
|
|
1368
|
-
},
|
|
1369
|
-
indent=2,
|
|
1370
|
-
),
|
|
1371
|
-
encoding="utf-8",
|
|
1372
|
-
)
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
def _native_xtabond2_ab_serial_correlation_tests(
|
|
1242
|
+
def _native_ab_serial_correlation_pvalues(
|
|
1377
1243
|
*,
|
|
1378
1244
|
data: pd.DataFrame,
|
|
1379
1245
|
entity: str,
|
|
1380
|
-
time: str
|
|
1246
|
+
time: str,
|
|
1381
1247
|
row_index: np.ndarray,
|
|
1382
|
-
|
|
1248
|
+
residuals: np.ndarray,
|
|
1383
1249
|
X: np.ndarray,
|
|
1384
|
-
Z: np.ndarray,
|
|
1385
|
-
W: np.ndarray,
|
|
1386
|
-
bread: np.ndarray,
|
|
1387
|
-
cov: np.ndarray,
|
|
1388
1250
|
coef_names: list[str],
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
) -> dict[str, float | None]:
|
|
1393
|
-
"""Compute xtabond2-compatible Arellano-Bond AR(1)/AR(2) tests.
|
|
1394
|
-
|
|
1395
|
-
This follows xtabond2's robust/two-step specification-test denominator:
|
|
1396
|
-
|
|
1397
|
-
m2VZXA = -2 * e(V_raw) * X'Z * A
|
|
1398
|
-
d = wHw + Xw * (m2VZXA * ZHw' + V_robust * Xw')
|
|
1399
|
-
ar_l = ew_l / sqrt(d)
|
|
1400
|
-
|
|
1401
|
-
where V_raw is the uncorrected GMM bread matrix and V_robust is the
|
|
1402
|
-
robust/Windmeijer base covariance before xtabond2's final small-sample
|
|
1403
|
-
reposting correction. In this implementation that is recovered as:
|
|
1404
|
-
|
|
1405
|
-
V_robust = cov / (cov_correction * windmeijer_xtabond2_small_correction)
|
|
1251
|
+
system: bool,
|
|
1252
|
+
) -> tuple[float | None, float | None]:
|
|
1253
|
+
"""Experimental Arellano-Bond AR(1)/AR(2)-style residual tests.
|
|
1406
1254
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1255
|
+
Uses residuals from differenced-equation rows. For System GMM, level rows
|
|
1256
|
+
are excluded using the level-only `_con` marker.
|
|
1409
1257
|
"""
|
|
1410
|
-
resid = np.asarray(residual_vec, dtype=float).reshape(-1)
|
|
1411
|
-
X = np.asarray(X, dtype=float)
|
|
1412
|
-
Z = np.asarray(Z, dtype=float)
|
|
1413
|
-
W = np.asarray(W, dtype=float)
|
|
1414
|
-
bread = np.asarray(bread, dtype=float)
|
|
1415
|
-
cov = np.asarray(cov, dtype=float)
|
|
1416
|
-
|
|
1417
|
-
if resid.size == 0:
|
|
1418
|
-
return {"ar1_z": None, "ar1_p": None, "ar2_z": None, "ar2_p": None}
|
|
1419
|
-
|
|
1420
|
-
if X.shape[0] != resid.size or Z.shape[0] != resid.size:
|
|
1421
|
-
return {"ar1_z": None, "ar1_p": None, "ar2_z": None, "ar2_p": None}
|
|
1422
|
-
|
|
1423
|
-
base = data.copy()
|
|
1424
|
-
if time is not None and time in base.columns:
|
|
1425
|
-
base = base.sort_values([entity, time]).copy()
|
|
1426
|
-
else:
|
|
1427
|
-
base = base.sort_values([entity]).copy()
|
|
1428
|
-
|
|
1429
|
-
idx = np.asarray(row_index).reshape(-1)
|
|
1430
|
-
|
|
1431
1258
|
try:
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
idx_int.size == resid.size
|
|
1435
|
-
and idx_int.min(initial=0) >= 0
|
|
1436
|
-
and idx_int.max(initial=-1) < len(base)
|
|
1437
|
-
):
|
|
1438
|
-
rows = base.iloc[idx_int].copy()
|
|
1439
|
-
elif idx.size == resid.size:
|
|
1440
|
-
rows = data.loc[idx].copy()
|
|
1441
|
-
else:
|
|
1442
|
-
raise ValueError("row_index length does not match residual length")
|
|
1443
|
-
except Exception:
|
|
1444
|
-
# Conservative fallback matching the transformed-equation row order.
|
|
1445
|
-
parts = []
|
|
1446
|
-
for _, g in base.groupby(entity, sort=False):
|
|
1447
|
-
if len(g) > 1:
|
|
1448
|
-
parts.append(g.iloc[1:].copy())
|
|
1449
|
-
rows = pd.concat(parts, axis=0) if parts else base.iloc[:0].copy()
|
|
1450
|
-
rows = rows.iloc[: resid.size].copy()
|
|
1451
|
-
|
|
1452
|
-
if len(rows) != resid.size:
|
|
1453
|
-
n_common = min(len(rows), resid.size, X.shape[0], Z.shape[0])
|
|
1454
|
-
rows = rows.iloc[:n_common].copy()
|
|
1455
|
-
resid = resid[:n_common]
|
|
1456
|
-
X = X[:n_common, :]
|
|
1457
|
-
Z = Z[:n_common, :]
|
|
1458
|
-
|
|
1459
|
-
row_df = pd.DataFrame(
|
|
1460
|
-
{
|
|
1461
|
-
"row_pos": np.arange(resid.size, dtype=int),
|
|
1462
|
-
"entity": rows[entity].to_numpy() if entity in rows.columns else np.arange(resid.size),
|
|
1463
|
-
"resid": resid,
|
|
1464
|
-
}
|
|
1465
|
-
)
|
|
1466
|
-
|
|
1467
|
-
if time is not None and time in rows.columns:
|
|
1468
|
-
row_df["time"] = pd.to_numeric(rows[time].to_numpy(), errors="coerce")
|
|
1469
|
-
else:
|
|
1470
|
-
row_df["time"] = row_df["row_pos"].astype(float)
|
|
1471
|
-
|
|
1472
|
-
row_df["entity_str"] = row_df["entity"].astype(str)
|
|
1473
|
-
|
|
1474
|
-
if spec is not None and bool(getattr(spec, "system", False)) and "_con" in coef_names:
|
|
1475
|
-
con_idx = coef_names.index("_con")
|
|
1476
|
-
# Current stacked System-GMM design: _con == 0 for transformed/difference
|
|
1477
|
-
# rows and _con == 1 for level-equation rows.
|
|
1478
|
-
ar_equation_mask = np.isclose(X[:, con_idx], 0.0)
|
|
1479
|
-
else:
|
|
1480
|
-
ar_equation_mask = np.ones(resid.size, dtype=bool)
|
|
1481
|
-
|
|
1482
|
-
denom_scale = float(cov_correction) * float(windmeijer_xtabond2_small_correction)
|
|
1483
|
-
if not np.isfinite(denom_scale) or denom_scale <= 0:
|
|
1484
|
-
denom_scale = 1.0
|
|
1485
|
-
|
|
1486
|
-
v_robust_for_ar = cov / denom_scale
|
|
1487
|
-
|
|
1488
|
-
ZX = X.T @ Z
|
|
1489
|
-
m2VZXA = -2.0 * bread @ ZX @ W
|
|
1490
|
-
|
|
1491
|
-
def _compute_lag(lag: int) -> tuple[float | None, float | None]:
|
|
1492
|
-
df = row_df.copy()
|
|
1493
|
-
df["w"] = 0.0
|
|
1494
|
-
df["is_ar_equation"] = ar_equation_mask
|
|
1495
|
-
|
|
1496
|
-
ar_df = df[df["is_ar_equation"]].copy()
|
|
1497
|
-
ar_df = ar_df.sort_values(["entity_str", "time", "row_pos"]).copy()
|
|
1498
|
-
|
|
1499
|
-
lagged_rows: list[tuple[int, float]] = []
|
|
1500
|
-
|
|
1501
|
-
for _, g in ar_df.groupby("entity_str", sort=False):
|
|
1502
|
-
g = g.sort_values(["time", "row_pos"]).copy()
|
|
1503
|
-
r = g["resid"].to_numpy(dtype=float)
|
|
1504
|
-
pos = g["row_pos"].to_numpy(dtype=int)
|
|
1505
|
-
|
|
1506
|
-
if len(r) <= lag:
|
|
1507
|
-
continue
|
|
1508
|
-
|
|
1509
|
-
for jj in range(lag, len(r)):
|
|
1510
|
-
lagged_rows.append((int(pos[jj]), float(r[jj - lag])))
|
|
1511
|
-
|
|
1512
|
-
if lagged_rows:
|
|
1513
|
-
lagged = pd.DataFrame(lagged_rows, columns=["row_pos", "w_value"])
|
|
1514
|
-
df = df.merge(lagged, on="row_pos", how="left")
|
|
1515
|
-
df["w"] = df["w_value"].fillna(0.0)
|
|
1516
|
-
df = df.drop(columns=["w_value"])
|
|
1517
|
-
|
|
1518
|
-
df["ewvar"] = np.where(df["is_ar_equation"], df["w"] * df["resid"], np.nan)
|
|
1519
|
-
|
|
1520
|
-
ew = float(np.nansum(df["ewvar"].to_numpy(dtype=float)))
|
|
1521
|
-
if not np.isfinite(ew) or ew == 0:
|
|
1522
|
-
return None, None
|
|
1523
|
-
|
|
1524
|
-
group_ew = (
|
|
1525
|
-
df.groupby("entity_str", sort=False)["ewvar"]
|
|
1526
|
-
.sum(min_count=1)
|
|
1527
|
-
.fillna(0.0)
|
|
1528
|
-
.rename("group_ew")
|
|
1529
|
-
)
|
|
1530
|
-
df = df.join(group_ew, on="entity_str")
|
|
1531
|
-
|
|
1532
|
-
w = df["w"].to_numpy(dtype=float)
|
|
1533
|
-
group_ew_row = df["group_ew"].to_numpy(dtype=float)
|
|
1534
|
-
|
|
1535
|
-
# xtabond2 robust branch:
|
|
1536
|
-
# by id: ewi = sum(ewvar)
|
|
1537
|
-
# etmp = sign(ewi) * e
|
|
1538
|
-
# iweight = abs(ewi)
|
|
1539
|
-
# weighted etmp contribution is therefore ewi * e.
|
|
1540
|
-
weighted_e = group_ew_row * resid
|
|
1541
|
-
|
|
1542
|
-
ZHw = weighted_e.reshape(1, -1) @ Z
|
|
1543
|
-
wHw = float(np.asarray(weighted_e.reshape(1, -1) @ w.reshape(-1, 1)).squeeze())
|
|
1544
|
-
|
|
1545
|
-
mask = df["is_ar_equation"].to_numpy(dtype=bool)
|
|
1546
|
-
Xw = w[mask].reshape(1, -1) @ X[mask, :]
|
|
1547
|
-
|
|
1548
|
-
d = float(
|
|
1549
|
-
np.asarray(
|
|
1550
|
-
wHw + Xw @ (m2VZXA @ ZHw.T + v_robust_for_ar @ Xw.T)
|
|
1551
|
-
).squeeze()
|
|
1552
|
-
)
|
|
1553
|
-
|
|
1554
|
-
if not np.isfinite(d) or d <= 0:
|
|
1555
|
-
return None, None
|
|
1556
|
-
|
|
1557
|
-
z = float(ew / np.sqrt(d))
|
|
1558
|
-
p = float(2.0 * stats.norm.sf(abs(z)))
|
|
1559
|
-
return z, p
|
|
1560
|
-
|
|
1561
|
-
ar1_z, ar1_p = _compute_lag(1)
|
|
1562
|
-
ar2_z, ar2_p = _compute_lag(2)
|
|
1563
|
-
|
|
1564
|
-
return {
|
|
1565
|
-
"ar1_z": ar1_z,
|
|
1566
|
-
"ar1_p": ar1_p,
|
|
1567
|
-
"ar2_z": ar2_z,
|
|
1568
|
-
"ar2_p": ar2_p,
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1259
|
+
resid = np.asarray(residuals, dtype=float).reshape(-1)
|
|
1260
|
+
idx = np.asarray(row_index)
|
|
1571
1261
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
entity: str,
|
|
1576
|
-
time: str | None,
|
|
1577
|
-
row_index: np.ndarray,
|
|
1578
|
-
residual_vec: np.ndarray,
|
|
1579
|
-
spec: DynamicPanelSpec | None = None,
|
|
1580
|
-
**_: object,
|
|
1581
|
-
) -> dict[str, float | None]:
|
|
1582
|
-
"""Compute experimental Arellano-Bond-style AR(1)/AR(2) diagnostics.
|
|
1583
|
-
|
|
1584
|
-
WARNING:
|
|
1585
|
-
This diagnostic is not yet xtabond2-certified. It is exposed for
|
|
1586
|
-
post-estimation development only and must not be described as parity-certified
|
|
1587
|
-
until the AR candidate harness reproduces Stata's AR(1)/AR(2) z-statistics.
|
|
1588
|
-
|
|
1589
|
-
This post-estimation diagnostic intentionally does not affect coefficient
|
|
1590
|
-
or covariance estimation.
|
|
1591
|
-
|
|
1592
|
-
Implementation policy:
|
|
1593
|
-
- Use entity-level differenced-equation residuals.
|
|
1594
|
-
- For System GMM, ignore the level-equation residual block and use only the
|
|
1595
|
-
differenced-equation residual block.
|
|
1596
|
-
- Report signed z-statistics and two-sided normal p-values.
|
|
1597
|
-
- Use entity-cluster moment aggregation: for lag m, compute each entity's
|
|
1598
|
-
residual product sum and standardize the cross-entity moment.
|
|
1599
|
-
|
|
1600
|
-
This is closer to xtabond2's m-test logic than the previous simple pooled
|
|
1601
|
-
residual correlation heuristic, and it exposes z-statistics for parity
|
|
1602
|
-
comparison.
|
|
1603
|
-
"""
|
|
1604
|
-
|
|
1605
|
-
from scipy import stats as _stats
|
|
1606
|
-
|
|
1607
|
-
resid = np.asarray(residual_vec, dtype=float).reshape(-1)
|
|
1608
|
-
idx = np.asarray(row_index).reshape(-1)
|
|
1609
|
-
|
|
1610
|
-
if resid.size == 0:
|
|
1611
|
-
return {"ar1_z": None, "ar1_p": None, "ar2_z": None, "ar2_p": None}
|
|
1612
|
-
|
|
1613
|
-
# Sort data deterministically by entity/time. Use row_index to map residuals
|
|
1614
|
-
# back to entity/time rows whenever possible.
|
|
1615
|
-
base = data.copy()
|
|
1616
|
-
if time is not None and time in base.columns:
|
|
1617
|
-
base = base.sort_values([entity, time]).copy()
|
|
1618
|
-
else:
|
|
1619
|
-
base = base.sort_values([entity]).copy()
|
|
1620
|
-
|
|
1621
|
-
# For System GMM, residual_vec usually stacks:
|
|
1622
|
-
# [differenced-equation residuals, level-equation residuals]
|
|
1623
|
-
# AR tests should use only the differenced-equation block.
|
|
1624
|
-
# In the maintained FD001 benchmark this corresponds to sum(T_i - 1).
|
|
1625
|
-
if spec is not None and bool(getattr(spec, "system", False)):
|
|
1626
|
-
diff_n = int(
|
|
1627
|
-
sum(max(len(g) - 1, 0) for _, g in base.groupby(entity, sort=False))
|
|
1628
|
-
)
|
|
1629
|
-
if 0 < diff_n <= resid.size:
|
|
1630
|
-
resid = resid[:diff_n]
|
|
1631
|
-
idx = idx[: min(idx.size, diff_n)]
|
|
1632
|
-
|
|
1633
|
-
# Build residual frame. Prefer positional row mapping because native matrix
|
|
1634
|
-
# construction uses sorted working rows.
|
|
1635
|
-
try:
|
|
1636
|
-
idx_int = idx.astype(int)
|
|
1637
|
-
if idx_int.size == resid.size and idx_int.min(initial=0) >= 0 and idx_int.max(initial=-1) < len(base):
|
|
1638
|
-
rows = base.iloc[idx_int].copy()
|
|
1639
|
-
elif idx.size == resid.size:
|
|
1640
|
-
rows = data.loc[idx].copy()
|
|
1262
|
+
if system and "_con" in coef_names:
|
|
1263
|
+
con_col = coef_names.index("_con")
|
|
1264
|
+
diff_mask = ~np.isclose(np.asarray(X[:, con_col], dtype=float), 1.0)
|
|
1641
1265
|
else:
|
|
1642
|
-
|
|
1643
|
-
except Exception:
|
|
1644
|
-
# Fallback: take the final len(resid) sorted differenced rows by entity.
|
|
1645
|
-
parts = []
|
|
1646
|
-
for _, g in base.groupby(entity, sort=False):
|
|
1647
|
-
if len(g) > 1:
|
|
1648
|
-
parts.append(g.iloc[1:].copy())
|
|
1649
|
-
rows = pd.concat(parts, axis=0) if parts else base.iloc[:0].copy()
|
|
1650
|
-
rows = rows.iloc[: resid.size].copy()
|
|
1651
|
-
|
|
1652
|
-
if len(rows) != resid.size:
|
|
1653
|
-
n = min(len(rows), resid.size)
|
|
1654
|
-
rows = rows.iloc[:n].copy()
|
|
1655
|
-
resid = resid[:n]
|
|
1656
|
-
|
|
1657
|
-
rows["_native_ab_resid"] = resid
|
|
1658
|
-
|
|
1659
|
-
if time is not None and time in rows.columns:
|
|
1660
|
-
rows = rows.sort_values([entity, time]).copy()
|
|
1661
|
-
else:
|
|
1662
|
-
rows = rows.sort_values([entity]).copy()
|
|
1266
|
+
diff_mask = np.ones_like(resid, dtype=bool)
|
|
1663
1267
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
for _, g in rows.groupby(entity, sort=False):
|
|
1668
|
-
u = np.asarray(g["_native_ab_resid"], dtype=float)
|
|
1669
|
-
u = u[np.isfinite(u)]
|
|
1670
|
-
|
|
1671
|
-
if u.size <= lag:
|
|
1672
|
-
continue
|
|
1268
|
+
idx = idx[diff_mask]
|
|
1269
|
+
resid = resid[diff_mask]
|
|
1673
1270
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1271
|
+
panel_keys = data.loc[idx, [entity, time]].copy()
|
|
1272
|
+
panel_keys["_resid"] = resid
|
|
1273
|
+
panel_keys = panel_keys.sort_values([entity, time])
|
|
1677
1274
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1275
|
+
def _lag_test(lag: int) -> float | None:
|
|
1276
|
+
d = panel_keys.copy()
|
|
1277
|
+
d["_lag"] = d.groupby(entity)["_resid"].shift(lag)
|
|
1278
|
+
d = d.dropna(subset=["_resid", "_lag"])
|
|
1680
1279
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
denominator = float(np.sqrt(np.sum(m * m)))
|
|
1280
|
+
if len(d) < 5:
|
|
1281
|
+
return None
|
|
1684
1282
|
|
|
1685
|
-
|
|
1686
|
-
|
|
1283
|
+
a = d["_resid"].to_numpy(dtype=float)
|
|
1284
|
+
b = d["_lag"].to_numpy(dtype=float)
|
|
1687
1285
|
|
|
1688
|
-
|
|
1689
|
-
|
|
1286
|
+
denom = float(np.sqrt(np.sum(a * a) * np.sum(b * b)))
|
|
1287
|
+
if denom <= 0:
|
|
1288
|
+
return None
|
|
1690
1289
|
|
|
1691
|
-
|
|
1290
|
+
corr = float(np.sum(a * b) / denom)
|
|
1291
|
+
z = corr * np.sqrt(len(d))
|
|
1692
1292
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1293
|
+
if not np.isfinite(z):
|
|
1294
|
+
return None
|
|
1695
1295
|
|
|
1696
|
-
|
|
1697
|
-
"ar1_z": ar1_z,
|
|
1698
|
-
"ar1_p": ar1_p,
|
|
1699
|
-
"ar2_z": ar2_z,
|
|
1700
|
-
"ar2_p": ar2_p,
|
|
1701
|
-
}
|
|
1296
|
+
return float(2.0 * stats.norm.sf(abs(z)))
|
|
1702
1297
|
|
|
1298
|
+
return _lag_test(1), _lag_test(2)
|
|
1703
1299
|
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
) -> tuple[float | None, float | None]:
|
|
1707
|
-
"""Backward-compatible wrapper returning only AR p-values."""
|
|
1708
|
-
|
|
1709
|
-
tests = _native_ab_serial_correlation_tests(**kwargs)
|
|
1710
|
-
return tests.get("ar1_p"), tests.get("ar2_p")
|
|
1300
|
+
except Exception:
|
|
1301
|
+
return None, None
|
|
1711
1302
|
|
|
1712
1303
|
|
|
1713
1304
|
def _native_fod_difference_windmeijer_covariance(
|
|
@@ -2159,155 +1750,16 @@ def run_native_dynamic_panel_gmm(
|
|
|
2159
1750
|
n_params=len(names),
|
|
2160
1751
|
)
|
|
2161
1752
|
|
|
2162
|
-
|
|
2163
|
-
#
|
|
2164
|
-
# This dump is intentionally placed after final W, bread, residuals, and
|
|
2165
|
-
# covariance are available. The earlier SYSTEMGMMKIT_NATIVE_MATRIX_DUMP_DIR
|
|
2166
|
-
# dump occurs before final System-GMM Z/W/covariance construction and is
|
|
2167
|
-
# therefore not sufficient for reproducing xtabond2's AR denominator:
|
|
2168
|
-
# d = wHw + Xw * (m2VZXA * ZHw' + Vrobust * Xw')
|
|
2169
|
-
import os as _native_ar_formula_os
|
|
2170
|
-
|
|
2171
|
-
native_ar_formula_dump_dir = _native_ar_formula_os.getenv(
|
|
2172
|
-
"SYSTEMGMMKIT_NATIVE_AR_FORMULA_DUMP_DIR"
|
|
2173
|
-
)
|
|
2174
|
-
if native_ar_formula_dump_dir:
|
|
2175
|
-
import json as _native_ar_formula_json
|
|
2176
|
-
from pathlib import Path as _NativeArFormulaDumpPath
|
|
2177
|
-
|
|
2178
|
-
dump_dir = _NativeArFormulaDumpPath(native_ar_formula_dump_dir)
|
|
2179
|
-
dump_dir.mkdir(parents=True, exist_ok=True)
|
|
2180
|
-
|
|
2181
|
-
safe_name = "".join(
|
|
2182
|
-
ch if ch.isalnum() or ch in "-_." else "_"
|
|
2183
|
-
for ch in str(getattr(spec, "name", "native_gmm"))
|
|
2184
|
-
)
|
|
2185
|
-
|
|
2186
|
-
npz_path = dump_dir / f"{safe_name}_ar_formula_inputs.npz"
|
|
2187
|
-
json_path = dump_dir / f"{safe_name}_ar_formula_inputs.json"
|
|
2188
|
-
row_meta_path = dump_dir / f"{safe_name}_row_meta.json"
|
|
2189
|
-
|
|
2190
|
-
def _object_array_from_list(values):
|
|
2191
|
-
arr = np.empty(len(values), dtype=object)
|
|
2192
|
-
for ii, vv in enumerate(values):
|
|
2193
|
-
arr[ii] = np.asarray(vv)
|
|
2194
|
-
return arr
|
|
2195
|
-
|
|
2196
|
-
_group_indices_obj = _object_array_from_list(group_indices_for_cov)
|
|
2197
|
-
|
|
2198
|
-
_maybe_windmeijer_base_cov = locals().get("windmeijer_base_cov", None)
|
|
2199
|
-
_maybe_vcov_step1 = locals().get("vcov_step1", None)
|
|
2200
|
-
_maybe_s_group = locals().get("s_group", None)
|
|
2201
|
-
_maybe_s_group_diag = locals().get("_s_group_diag", None)
|
|
2202
|
-
|
|
2203
|
-
np.savez_compressed(
|
|
2204
|
-
npz_path,
|
|
2205
|
-
y=np.asarray(y, dtype=float),
|
|
2206
|
-
X=np.asarray(X, dtype=float),
|
|
2207
|
-
Z=np.asarray(Z, dtype=float),
|
|
2208
|
-
beta=np.asarray(beta, dtype=float),
|
|
2209
|
-
beta1=np.asarray(beta1, dtype=float),
|
|
2210
|
-
residuals=np.asarray(residuals, dtype=float),
|
|
2211
|
-
residuals1=np.asarray(residuals1, dtype=float),
|
|
2212
|
-
residual_vec=np.asarray(residual_vec, dtype=float),
|
|
2213
|
-
W1=np.asarray(W1, dtype=float),
|
|
2214
|
-
W=np.asarray(W, dtype=float),
|
|
2215
|
-
bread=np.asarray(bread, dtype=float),
|
|
2216
|
-
D=np.asarray(D, dtype=float),
|
|
2217
|
-
ZX=np.asarray(D, dtype=float),
|
|
2218
|
-
cov=np.asarray(cov, dtype=float),
|
|
2219
|
-
windmeijer_base_cov=(
|
|
2220
|
-
np.asarray(_maybe_windmeijer_base_cov, dtype=float)
|
|
2221
|
-
if _maybe_windmeijer_base_cov is not None
|
|
2222
|
-
else np.asarray([], dtype=float)
|
|
2223
|
-
),
|
|
2224
|
-
vcov_step1=(
|
|
2225
|
-
np.asarray(_maybe_vcov_step1, dtype=float)
|
|
2226
|
-
if _maybe_vcov_step1 is not None
|
|
2227
|
-
else np.asarray([], dtype=float)
|
|
2228
|
-
),
|
|
2229
|
-
s_group=(
|
|
2230
|
-
np.asarray(_maybe_s_group, dtype=float)
|
|
2231
|
-
if _maybe_s_group is not None
|
|
2232
|
-
else np.asarray([], dtype=float)
|
|
2233
|
-
),
|
|
2234
|
-
s_group_diag=(
|
|
2235
|
-
np.asarray(_maybe_s_group_diag, dtype=float)
|
|
2236
|
-
if _maybe_s_group_diag is not None
|
|
2237
|
-
else np.asarray([], dtype=float)
|
|
2238
|
-
),
|
|
2239
|
-
row_index=np.asarray([str(v) for v in row_index], dtype=object),
|
|
2240
|
-
row_entities_for_cov=np.asarray([str(v) for v in row_entities_for_cov], dtype=object),
|
|
2241
|
-
group_indices=_group_indices_obj,
|
|
2242
|
-
coef_names=np.asarray(names, dtype=object),
|
|
2243
|
-
instrument_names=np.asarray(instrument_names, dtype=object),
|
|
2244
|
-
)
|
|
2245
|
-
|
|
2246
|
-
with contextlib.suppress(Exception):
|
|
2247
|
-
row_meta_path.write_text(
|
|
2248
|
-
_native_ar_formula_json.dumps(row_meta, indent=2, default=str),
|
|
2249
|
-
encoding="utf-8",
|
|
2250
|
-
)
|
|
2251
|
-
|
|
2252
|
-
json_path.write_text(
|
|
2253
|
-
_native_ar_formula_json.dumps(
|
|
2254
|
-
{
|
|
2255
|
-
"spec": str(getattr(spec, "name", "")),
|
|
2256
|
-
"system": bool(getattr(spec, "system", False)),
|
|
2257
|
-
"transformation": str(getattr(spec, "transformation", "")),
|
|
2258
|
-
"steps": str(getattr(spec, "steps", "")),
|
|
2259
|
-
"windmeijer_requested": bool(windmeijer_requested),
|
|
2260
|
-
"use_twostep": bool(use_twostep),
|
|
2261
|
-
"nobs_effective": int(nobs_effective),
|
|
2262
|
-
"nobs_reported": int(nobs_reported),
|
|
2263
|
-
"n_groups_for_cov": int(n_groups_for_cov),
|
|
2264
|
-
"n": int(n),
|
|
2265
|
-
"k": int(k),
|
|
2266
|
-
"j": int(Z.shape[1]),
|
|
2267
|
-
"x_shape": list(X.shape),
|
|
2268
|
-
"z_shape": list(Z.shape),
|
|
2269
|
-
"w1_shape": list(W1.shape),
|
|
2270
|
-
"w_shape": list(W.shape),
|
|
2271
|
-
"bread_shape": list(bread.shape),
|
|
2272
|
-
"cov_shape": list(cov.shape),
|
|
2273
|
-
"coef_names": list(names),
|
|
2274
|
-
"instrument_names": list(instrument_names),
|
|
2275
|
-
"cov_correction": float(locals().get("cov_correction", 1.0)),
|
|
2276
|
-
"windmeijer_xtabond2_small_correction": float(
|
|
2277
|
-
locals().get("windmeijer_xtabond2_small_correction", 1.0)
|
|
2278
|
-
),
|
|
2279
|
-
"has_windmeijer_base_cov": _maybe_windmeijer_base_cov is not None,
|
|
2280
|
-
"has_vcov_step1": _maybe_vcov_step1 is not None,
|
|
2281
|
-
"has_s_group": _maybe_s_group is not None,
|
|
2282
|
-
"has_s_group_diag": _maybe_s_group_diag is not None,
|
|
2283
|
-
},
|
|
2284
|
-
indent=2,
|
|
2285
|
-
),
|
|
2286
|
-
encoding="utf-8",
|
|
2287
|
-
)
|
|
2288
|
-
|
|
2289
|
-
_ar_serial = _native_xtabond2_ab_serial_correlation_tests(
|
|
1753
|
+
ar1_p, ar2_p = _native_ab_serial_correlation_pvalues(
|
|
2290
1754
|
data=data,
|
|
2291
1755
|
entity=entity,
|
|
2292
1756
|
time=time,
|
|
2293
1757
|
row_index=np.asarray(row_index),
|
|
2294
|
-
|
|
1758
|
+
residuals=residual_vec,
|
|
2295
1759
|
X=X,
|
|
2296
|
-
Z=Z,
|
|
2297
|
-
W=W,
|
|
2298
|
-
bread=bread,
|
|
2299
|
-
cov=cov,
|
|
2300
1760
|
coef_names=names,
|
|
2301
|
-
|
|
2302
|
-
cov_correction=float(locals().get("cov_correction", 1.0)),
|
|
2303
|
-
windmeijer_xtabond2_small_correction=float(
|
|
2304
|
-
locals().get("windmeijer_xtabond2_small_correction", 1.0)
|
|
2305
|
-
),
|
|
1761
|
+
system=spec.system,
|
|
2306
1762
|
)
|
|
2307
|
-
ar1_z = _ar_serial.get("ar1_z")
|
|
2308
|
-
ar1_p = _ar_serial.get("ar1_p")
|
|
2309
|
-
ar2_z = _ar_serial.get("ar2_z")
|
|
2310
|
-
ar2_p = _ar_serial.get("ar2_p")
|
|
2311
1763
|
|
|
2312
1764
|
_u_col = residual_vec.reshape(-1, 1)
|
|
2313
1765
|
_ztu = Z.T @ _u_col
|
|
@@ -2385,70 +1837,6 @@ def run_native_dynamic_panel_gmm(
|
|
|
2385
1837
|
# reporting a misleading pseudo-Hansen p-value.
|
|
2386
1838
|
hansen_p = None
|
|
2387
1839
|
|
|
2388
|
-
# xtabond2-compatible overidentification diagnostics for the maintained
|
|
2389
|
-
# first-difference two-step path.
|
|
2390
|
-
#
|
|
2391
|
-
# xtabond2 computes:
|
|
2392
|
-
# Sargan = e1' Z A1 e1, with A1 rescaled by 1/sig2
|
|
2393
|
-
# Hansen = e2' Z A2 e2, with robust two-step moment covariance scaling
|
|
2394
|
-
#
|
|
2395
|
-
# In the native matrix normalization used here, the FD001 parity harness
|
|
2396
|
-
# identifies the equivalent formulas as:
|
|
2397
|
-
# Hansen = (u2' Z W2 Z' u2) / n_groups
|
|
2398
|
-
# Sargan = (u1' Z W1 Z' u1) / sig2
|
|
2399
|
-
# sig2 = sum(e1_diff^2) / nobs_effective / 2
|
|
2400
|
-
#
|
|
2401
|
-
# This override is deliberately restricted to FD two-step style estimation
|
|
2402
|
-
# to avoid changing non-FD/FOD behavior before separate certification.
|
|
2403
|
-
try:
|
|
2404
|
-
_is_fd_transformation_for_overid = (
|
|
2405
|
-
str(getattr(spec, "transformation", "")).strip().lower() == "fd"
|
|
2406
|
-
)
|
|
2407
|
-
|
|
2408
|
-
if use_twostep and _is_fd_transformation_for_overid:
|
|
2409
|
-
_u2_overid = np.asarray(residual_vec, dtype=float).reshape(-1, 1)
|
|
2410
|
-
_u1_overid = np.asarray(residuals1, dtype=float).reshape(-1, 1)
|
|
2411
|
-
|
|
2412
|
-
_ztu2 = Z.T @ _u2_overid
|
|
2413
|
-
_hansen_raw_xtabond2 = float(np.asarray(_ztu2.T @ W @ _ztu2).squeeze())
|
|
2414
|
-
_hansen_j_stat = _hansen_raw_xtabond2 / max(float(n_groups_for_cov), 1.0)
|
|
2415
|
-
|
|
2416
|
-
if _overid_df > 0 and np.isfinite(_hansen_j_stat) and _hansen_j_stat >= 0:
|
|
2417
|
-
_hansen_p_candidate = float(stats.chi2.sf(_hansen_j_stat, _overid_df))
|
|
2418
|
-
else:
|
|
2419
|
-
_hansen_p_candidate = None
|
|
2420
|
-
|
|
2421
|
-
if spec.system and "_con" in names:
|
|
2422
|
-
_con_col_for_sargan_sig2 = names.index("_con")
|
|
2423
|
-
_sargan_sig2_mask = np.isclose(X[:, _con_col_for_sargan_sig2], 0.0)
|
|
2424
|
-
else:
|
|
2425
|
-
_sargan_sig2_mask = np.ones(np.asarray(residuals1).reshape(-1).shape[0], dtype=bool)
|
|
2426
|
-
|
|
2427
|
-
_e1_for_sig2 = np.asarray(residuals1, dtype=float).reshape(-1)[_sargan_sig2_mask]
|
|
2428
|
-
_e1_for_sig2 = _e1_for_sig2[np.isfinite(_e1_for_sig2)]
|
|
2429
|
-
|
|
2430
|
-
if _e1_for_sig2.size:
|
|
2431
|
-
_sig2_xtabond2_sargan = (
|
|
2432
|
-
float(np.sum(_e1_for_sig2 ** 2))
|
|
2433
|
-
/ max(float(nobs_effective), 1.0)
|
|
2434
|
-
/ 2.0
|
|
2435
|
-
)
|
|
2436
|
-
|
|
2437
|
-
if np.isfinite(_sig2_xtabond2_sargan) and _sig2_xtabond2_sargan > 0:
|
|
2438
|
-
_ztu1 = Z.T @ _u1_overid
|
|
2439
|
-
_sargan_unscaled_xtabond2 = float(
|
|
2440
|
-
np.asarray(_ztu1.T @ W1 @ _ztu1).squeeze()
|
|
2441
|
-
)
|
|
2442
|
-
_sargan_stat = _sargan_unscaled_xtabond2 / _sig2_xtabond2_sargan
|
|
2443
|
-
|
|
2444
|
-
if _overid_df > 0 and np.isfinite(_sargan_stat) and _sargan_stat >= 0:
|
|
2445
|
-
sargan_p = float(stats.chi2.sf(_sargan_stat, _overid_df))
|
|
2446
|
-
else:
|
|
2447
|
-
sargan_p = None
|
|
2448
|
-
|
|
2449
|
-
except Exception as exc:
|
|
2450
|
-
_hansen_j_error = f"{type(exc).__name__}: {exc}"
|
|
2451
|
-
|
|
2452
1840
|
|
|
2453
1841
|
_ztu_norm = float(np.linalg.norm(_ztu))
|
|
2454
1842
|
_w_norm = float(np.linalg.norm(W))
|
|
@@ -2550,11 +1938,6 @@ def run_native_dynamic_panel_gmm(
|
|
|
2550
1938
|
index=False,
|
|
2551
1939
|
)
|
|
2552
1940
|
|
|
2553
|
-
_df_m_export = int(len(names) - (1 if "_con" in names else 0))
|
|
2554
|
-
_df_r_export = int(n_groups_for_cov - (1 if bool(spec.system) else 0))
|
|
2555
|
-
_j_p_export = None
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
1941
|
return NativeGMMResult(
|
|
2559
1942
|
spec=spec,
|
|
2560
1943
|
nobs=int(nobs_reported),
|
|
@@ -2585,18 +1968,10 @@ def run_native_dynamic_panel_gmm(
|
|
|
2585
1968
|
),
|
|
2586
1969
|
],
|
|
2587
1970
|
instrument_names=list(instrument_names),
|
|
2588
|
-
|
|
2589
|
-
# group-moment robust covariance when available. The earlier
|
|
2590
|
-
# hansen_p variable is intentionally left undefined for two-step
|
|
2591
|
-
# robust/Windmeijer paths to avoid reporting the old pseudo-Hansen.
|
|
2592
|
-
hansen_p=_hansen_p_candidate,
|
|
1971
|
+
hansen_p=hansen_p,
|
|
2593
1972
|
sargan_p=sargan_p,
|
|
2594
1973
|
ar1_p=ar1_p,
|
|
2595
1974
|
ar2_p=ar2_p,
|
|
2596
|
-
ar1_z=ar1_z,
|
|
2597
|
-
ar2_z=ar2_z,
|
|
2598
|
-
ar1=ar1_z,
|
|
2599
|
-
ar2=ar2_z,
|
|
2600
1975
|
z_shape=tuple(Z.shape),
|
|
2601
1976
|
w_shape=tuple(W.shape),
|
|
2602
1977
|
j_stat=_j_stat,
|
|
@@ -2606,20 +1981,6 @@ def run_native_dynamic_panel_gmm(
|
|
|
2606
1981
|
sargan_j_stat=_sargan_stat,
|
|
2607
1982
|
overid_df=_overid_df,
|
|
2608
1983
|
hansen_j_error=_hansen_j_error,
|
|
2609
|
-
|
|
2610
|
-
# Export aliases used by Stata/xtabond2 comparison workflows.
|
|
2611
|
-
hansen=_hansen_j_stat,
|
|
2612
|
-
hansenp=_hansen_p_candidate,
|
|
2613
|
-
sargan=_sargan_stat,
|
|
2614
|
-
sarganp=(
|
|
2615
|
-
float(stats.chi2.sf(_sargan_stat, _overid_df))
|
|
2616
|
-
if _sargan_stat is not None and _overid_df is not None and _overid_df > 0
|
|
2617
|
-
else None
|
|
2618
|
-
),
|
|
2619
|
-
n_g=int(data[entity].nunique()) if entity in data.columns else None,
|
|
2620
|
-
df_m=_df_m_export,
|
|
2621
|
-
df_r=_df_r_export,
|
|
2622
|
-
j_p=_j_p_export,
|
|
2623
1984
|
)
|
|
2624
1985
|
|
|
2625
1986
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit/estimators/first_difference.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemgmmkit-0.5.8.dev0 → systemgmmkit-0.5.9}/src/systemgmmkit.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|