diff --git a/MANIFEST.in b/MANIFEST.in index 6a2cbbfe94..ba1f7c16e8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ recursive-include doc/source * recursive-include linearmodels *.pyx recursive-include linearmodels/tests *.txt *.csv *.dta recursive-include linearmodels/datasets * +recursive-include linearmodels/iv/_tf_data *.npz *.json *.rst LICENSE recursive-include examples * include linearmodels/_version.py include LICENSE.md diff --git a/doc/source/iv/index.rst b/doc/source/iv/index.rst index 6ef173cdac..4799d67331 100644 --- a/doc/source/iv/index.rst +++ b/doc/source/iv/index.rst @@ -26,6 +26,7 @@ tests. examples/advanced-examples.ipynb examples/using-formulas.ipynb examples/absorbing-regression.ipynb + weak-instruments reference mathematical-formula diff --git a/doc/source/iv/weak-instruments.rst b/doc/source/iv/weak-instruments.rst new file mode 100644 index 0000000000..2a61641140 --- /dev/null +++ b/doc/source/iv/weak-instruments.rst @@ -0,0 +1,93 @@ +Confidence intervals with a weak instrument +=========================================== + +:meth:`~linearmodels.iv.results.IVResults.tf_conf_int` constructs a tF interval +for one endogenous regressor and one excluded instrument. It is +an explicit alternative to the conventional :meth:`~linearmodels.iv.results.IVResults.conf_int`. +It supports IV2SLS with unadjusted or heteroskedasticity-robust covariance, +``debiased=False``, and constant observation weights. Other configurations raise +``ValueError``. Included exogenous controls are allowed. + +For example, use proximity to a four-year college as an instrument for education: + +.. doctest:: + + >>> from linearmodels.datasets import card + >>> from linearmodels.iv import IV2SLS + >>> data = card.load() + >>> columns = ["lwage", "educ", "exper", "expersq", "black", "south", "smsa", "nearc4"] + >>> data = data[columns].dropna() + >>> formula = "lwage ~ 1 + exper + expersq + black + south + smsa + [educ ~ nearc4]" + >>> result = IV2SLS.from_formula(formula, data).fit(cov_type="robust") + >>> interval = result.tf_conf_int() + >>> list(interval.index), list(interval.columns) + (['educ'], ['lower', 'upper']) + >>> conventional = result.conf_int().loc["educ"] + >>> bool(interval.loc["educ", "lower"] <= conventional["lower"]) + True + >>> bool(interval.loc["educ", "upper"] >= conventional["upper"]) + True + >>> interval_99 = result.tf_conf_int(level=0.99) + >>> bool(interval_99.loc["educ", "lower"] < interval.loc["educ", "lower"]) + True + >>> bool(interval_99.loc["educ", "upper"] > interval.loc["educ", "upper"]) + True + +The first-stage statistic is the squared t statistic for the excluded instrument, +using the fitted model's covariance convention. The coefficient and SE are reused. +Neither the original SE nor the conventional p-values and intervals are changed. + +The default ``method="table"`` uses bundled tables generated by +`tf-inference `_ from Appendix B.3 +of Lee, McCrary, Moreira and Porter's +`Valid t-Ratio Inference for IV `_. +The `author manuscript `_ +contains the construction. The generated tables contain at least 16,385 rows +per level and are cached after the first load. They require no additional +dependency, network access or numerical calibration at runtime. + +``level`` is a confidence level, matching ``conf_int(level=...)``; the default +is 0.95. Bundled levels are 0.75, 0.8, 0.85, 0.875, 0.9, 0.925, 0.94, 0.95, +0.96, 0.97, 0.975, 0.98, 0.99, 0.995, 0.998, 0.999, 0.9995 and 0.9999. +No interpolation across confidence levels or automatic numerical computation +is performed. An unavailable level raises an error. + +For an unconventional level, explicitly request numerical calibration:: + + interval = result.tf_conf_int(level=0.963, method="compute") + +This requires Python 3.11 or later and the optional tf-inference package. +The validated source revision can be installed with:: + + python -m pip install git+https://github.com/finite-sample/tf-inference.git@ebaa0f8ff0286203b0a1488abdf3330ace84c4fe + +The first computation can take several seconds; the calibration is cached by +level for later calls. The numerical implementation accepts levels between +0.75 and 0.9999. For custom numerical settings, inspection of calibration +diagnostics, or export of reusable constants, use tf-inference directly with +``alpha=1-level``. The finite calibration search should be checked beyond its +search settings before relying on an unconventional level for research. + +The unbounded-interval threshold is the squared normal critical value for the +requested level. At 95% it is about 3.84145882. F strictly above this boundary +gives finite bounds, including the interval between this boundary and 4. +For F=10 the 95% critical value is about 3.4303537; an estimate of 1 with SE=0.4 +therefore gives bounds of approximately [-0.37214, 2.37214]. Each confidence +level has its own calibrated plateau. At 99%, for example, its critical value +is about 2.72549, above the normal 99% critical value of 2.57583. + +The code interpolates the regularized squared critical value +``(F - boundary) * critical(F)**2``, which has a finite limit at the boundary. +The tables' source revision, license, checksums, construction settings and +validation provenance are included in ``linearmodels/iv/_tf_data``. + +Calibration uses deterministic integration, a finite nuisance-parameter grid +and local searches. Held-out checks and grid refinement assess the numerical +approximation; they do not prove a uniform size bound. This implementation does +not claim the conservative rounding guarantee of the paper's printed-table +interpolation. It is not a p-value calculation or a guarantee of finite-sample +coverage. Instrument validity and the +model's other identifying and sampling assumptions still matter. The newer VtF +procedure uses additional information and can yield shorter intervals; this +method implements tF, not VtF. The existing ``anderson_rubin`` property is a test +of overidentification, not a weak-instrument confidence set for this coefficient. diff --git a/linearmodels/iv/_tf.py b/linearmodels/iv/_tf.py new file mode 100644 index 0000000000..5a066c5cf3 --- /dev/null +++ b/linearmodels/iv/_tf.py @@ -0,0 +1,92 @@ +"""Generated tF critical values for single-instrument IV inference.""" + +from functools import lru_cache +import hashlib +from importlib import import_module +from importlib.resources import files +import io +import json +from math import inf, isfinite, sqrt +from typing import Any + +from numpy import load +from scipy.interpolate import PchipInterpolator + +_TF_ALPHAS = ( + 0.0001, + 0.0005, + 0.001, + 0.002, + 0.005, + 0.01, + 0.02, + 0.025, + 0.03, + 0.04, + 0.05, + 0.06, + 0.075, + 0.1, + 0.125, + 0.15, + 0.2, + 0.25, +) +_TF_LEVELS = {1 - alpha: alpha for alpha in _TF_ALPHAS} + + +@lru_cache(maxsize=32) +def _tf_table(alpha: float) -> tuple[float, float, PchipInterpolator]: + root = files("linearmodels.iv").joinpath("_tf_data") + prefix = f"alpha-{alpha!r}" + metadata = json.loads(root.joinpath(prefix + ".json").read_text()) + if metadata["schema_version"] != 1 or metadata["alpha"] != alpha: + raise ValueError("Unsupported or inconsistent tF table metadata.") + contents = root.joinpath(prefix + ".npz").read_bytes() + if hashlib.sha256(contents).hexdigest() != metadata["npz_sha256"]: + raise ValueError("tF table checksum does not match its metadata.") + with load(io.BytesIO(contents), allow_pickle=False) as data: + spline = PchipInterpolator(data["f"], data["regularized"], extrapolate=False) + return metadata["boundary"], metadata["plateau_f"], spline + + +@lru_cache(maxsize=32) +def _tf_calibration(alpha: float) -> Any: + try: + package = import_module("tf_inference") + except ImportError as exc: + raise ImportError( + "method='compute' requires the optional tf-inference package. " + "Install it from https://github.com/finite-sample/tf-inference." + ) from exc + return package.calibrate(alpha) + + +def _tf_critical_value( + f_stat: float, level: float = 0.95, *, method: str = "table" +) -> float: + """Evaluate a generated tF table or explicitly request numerical calibration.""" + if not isfinite(f_stat) or f_stat < 0: + raise ValueError("The first-stage F statistic must be finite and nonnegative.") + if not isfinite(level) or not 0.75 <= level <= 0.9999: + raise ValueError("level must be finite and between 0.75 and 0.9999.") + # Match confidence levels before subtraction to retain the exact bundled alpha. + alpha = _TF_LEVELS.get(level) + if method == "compute": + return float( + _tf_calibration(alpha if alpha is not None else 1 - level).critical_value( + f_stat + ) + ) + if method != "table": + raise ValueError("method must be 'table' or 'compute'.") + if alpha is None: + raise ValueError( + f"No bundled tF table for level={level}. " + "Use method='compute' with the optional tf-inference package." + ) + boundary, plateau_f, spline = _tf_table(alpha) + if f_stat <= boundary: + return inf + value = min(f_stat, plateau_f) + return sqrt(float(spline(value)) / (value - boundary)) diff --git a/linearmodels/iv/_tf_data/LICENSE b/linearmodels/iv/_tf_data/LICENSE new file mode 100644 index 0000000000..4b39e57eaa --- /dev/null +++ b/linearmodels/iv/_tf_data/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Gaurav Sood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/linearmodels/iv/_tf_data/README.rst b/linearmodels/iv/_tf_data/README.rst new file mode 100644 index 0000000000..0f94ff94c4 --- /dev/null +++ b/linearmodels/iv/_tf_data/README.rst @@ -0,0 +1,32 @@ +Generated tF calibration tables +=============================== + +The NPZ and JSON files are copied without modification from +https://github.com/finite-sample/tf-inference at commit +``ebaa0f8ff0286203b0a1488abdf3330ace84c4fe`` (version 0.1.0). +The accompanying LICENSE is that project's MIT license. + +Each NPZ stores the F grid and the regularized squared critical value +``Z(F) = (F - boundary) * critical(F)**2``. The matching JSON records the +boundary, calibrated plateau, construction settings, numerical size-search +diagnostics, midpoint interpolation error and SHA-256 checksum. Runtime lookup +verifies the checksum once, constructs a PCHIP interpolator for Z, and caches it. +F at or below the boundary returns infinity. Above it, cap F at ``plateau_f`` +and evaluate ``sqrt(Z(F) / (F - boundary))``. + +These are generated numerical approximations to Appendix B.3 of Lee, McCrary, +Moreira and Porter (2022), https://doi.org/10.1257/aer.20211063. They do not use +the rounded printed tables. Calibration uses a finite nuisance-parameter grid +and local optimization, not a certified supremum over the unbounded domain. +Interpolation is not claimed to preserve the printed table's conservative bound. + +The source package's ``validation/results.json`` records 4,032 held-out +rejection-probability checks and comparisons against refined curves. The largest +observed relative critical-value difference across the 18 levels was 1.76e-7. +These are numerical checks, not uniform error or coverage guarantees. + +To regenerate, check out that source commit, install it, run ``make tables`` +and ``make validate``, then copy its ``src/tf_inference/data/*.npz`` and +``src/tf_inference/data/*.json`` into this directory. Retain its license and +update this provenance record. The source package also exports CSV files for +other software; linearmodels needs only NPZ and JSON. diff --git a/linearmodels/iv/_tf_data/alpha-0.0001.json b/linearmodels/iv/_tf_data/alpha-0.0001.json new file mode 100644 index 0000000000..a48087c51a --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.0001.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.0001, + "boundary": 15.136705226623398, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 644.9077041789053, + "minimum_plateau_f": 644.9077041789053, + "property_c_endpoint": 644.9077041789053, + "table_relative_error_on_midpoints": 3.709627693915252e-08, + "npz_sha256": "8b4bcc9025761afc38e28a8ccccdb90f1a46cf783aab4fff0b610d25dce5b46b", + "csv_sha256": "647a218049bc7173324bf7b6f30e5044b718a5b3c2ded8edb928eef77fc0b528", + "diagnostics": { + "alpha": 0.0001, + "plateau_f": 644.9077041789053, + "plateau_critical": 4.357098832839683, + "property_c_endpoint": 644.9077041789053, + "maximum_rejection": 0.00010000000363025216, + "worst_strength": 2.0274492775642177, + "worst_rho": 1.0, + "integration_error": 2.328172135753514e-10, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 583, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.0001.npz b/linearmodels/iv/_tf_data/alpha-0.0001.npz new file mode 100644 index 0000000000..4786ae827e Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.0001.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.0005.json b/linearmodels/iv/_tf_data/alpha-0.0005.json new file mode 100644 index 0000000000..668f0b1924 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.0005.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.0005, + "boundary": 12.11566514639717, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 504.86144285989695, + "minimum_plateau_f": 504.86144285989695, + "property_c_endpoint": 504.86144285989695, + "table_relative_error_on_midpoints": 5.694683724577487e-08, + "npz_sha256": "749ee6b2bea22273f708c03a5d5986ba15eb25a38d7d6ea4cb1aa9dc87e56d47", + "csv_sha256": "23e1641f9ea5f98875d91981459d8c44a22a97754d9ee77018d1aea46d532611", + "diagnostics": { + "alpha": 0.0005, + "plateau_f": 504.86144285989695, + "plateau_critical": 3.855091873083001, + "property_c_endpoint": 504.86144285989695, + "maximum_rejection": 0.0005000000135663544, + "worst_strength": 14.432294512768058, + "worst_rho": 1.0, + "integration_error": 2.0845237272919904e-10, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 570, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.0005.npz b/linearmodels/iv/_tf_data/alpha-0.0005.npz new file mode 100644 index 0000000000..da5da82127 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.0005.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.001.json b/linearmodels/iv/_tf_data/alpha-0.001.json new file mode 100644 index 0000000000..cffa550ab8 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.001.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.001, + "boundary": 10.82756617066273, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 445.2701837491997, + "minimum_plateau_f": 445.2701837491997, + "property_c_endpoint": 445.2701837491997, + "table_relative_error_on_midpoints": 4.641803763583141e-08, + "npz_sha256": "50aed450311781e2056450cbab497851e95462e9a4bb87532fea5eb7812ed6cd", + "csv_sha256": "0e9000f339e4d981807e497adca058ed378847083194f052dafd278dc3733c87", + "diagnostics": { + "alpha": 0.001, + "plateau_f": 445.2701837491997, + "plateau_critical": 3.6204323588316405, + "property_c_endpoint": 445.2701837491997, + "maximum_rejection": 0.0010000000307756793, + "worst_strength": 17.849043880784276, + "worst_rho": 1.0, + "integration_error": 1.7355869932886103e-10, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 571, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.001.npz b/linearmodels/iv/_tf_data/alpha-0.001.npz new file mode 100644 index 0000000000..180a867265 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.001.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.002.json b/linearmodels/iv/_tf_data/alpha-0.002.json new file mode 100644 index 0000000000..df4c8791f1 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.002.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.002, + "boundary": 9.54953570608324, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 386.2528966656767, + "minimum_plateau_f": 386.2528966656767, + "property_c_endpoint": 386.2528966656767, + "table_relative_error_on_midpoints": 2.775246366049089e-08, + "npz_sha256": "cd8b9a3dc6888d13633a58e30be3cbd12bf828a16bb51838769bcb038775bd16", + "csv_sha256": "b66f2784972eb851d97b5dead0d3f05b5ceaa4219107fa470c540ce15b78f979", + "diagnostics": { + "alpha": 0.002, + "plateau_f": 386.2528966656767, + "plateau_critical": 3.371976233265817, + "property_c_endpoint": 386.2528966656767, + "maximum_rejection": 0.0020000001136703926, + "worst_strength": 1.3267222089378203, + "worst_rho": 1.0, + "integration_error": 1.5283012524224035e-10, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 595, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.002.npz b/linearmodels/iv/_tf_data/alpha-0.002.npz new file mode 100644 index 0000000000..02d6e1b8dd Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.002.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.005.json b/linearmodels/iv/_tf_data/alpha-0.005.json new file mode 100644 index 0000000000..7536e94bd0 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.005.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.005, + "boundary": 7.879438576622418, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 309.36809070892633, + "minimum_plateau_f": 309.36809070892633, + "property_c_endpoint": 309.36809070892633, + "table_relative_error_on_midpoints": 1.6886482434586014e-08, + "npz_sha256": "453c8a219b98333250f7307b42a64f9e5476b66b78502c36ffa5a67c7d4d50a4", + "csv_sha256": "60649dd8ec376d5201c32102a693df33abd8169a23ff02b6d38e05e54cff6ba1", + "diagnostics": { + "alpha": 0.005, + "plateau_f": 309.36809070892633, + "plateau_critical": 3.017771746115904, + "property_c_endpoint": 309.36809070892633, + "maximum_rejection": 0.0050000000398607225, + "worst_strength": 6.446398341160653, + "worst_rho": 1.0, + "integration_error": 3.3949487837702915e-10, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 568, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.005.npz b/linearmodels/iv/_tf_data/alpha-0.005.npz new file mode 100644 index 0000000000..f08742f054 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.005.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.01.json b/linearmodels/iv/_tf_data/alpha-0.01.json new file mode 100644 index 0000000000..cbe5a8e3b3 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.01.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.01, + "boundary": 6.634896601021215, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 252.3426585519165, + "minimum_plateau_f": 252.3426585519165, + "property_c_endpoint": 252.3426585519165, + "table_relative_error_on_midpoints": 9.533372979930732e-09, + "npz_sha256": "d444319030f459314897556314c21f5a16a105fed6e99dbd583932e7cd729b4a", + "csv_sha256": "a6ec44dec62f371dc198b690d246e64def7fc6b561cc68bae127d91952af18bf", + "diagnostics": { + "alpha": 0.01, + "plateau_f": 252.3426585519165, + "plateau_critical": 2.7254860602391733, + "property_c_endpoint": 252.3426585519165, + "maximum_rejection": 0.010000000115177063, + "worst_strength": 9.148826235761176, + "worst_rho": 1.0, + "integration_error": 6.816719357629461e-10, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 571, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.01.npz b/linearmodels/iv/_tf_data/alpha-0.01.npz new file mode 100644 index 0000000000..c1b40929bd Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.01.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.02.json b/linearmodels/iv/_tf_data/alpha-0.02.json new file mode 100644 index 0000000000..965f6d2368 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.02.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.02, + "boundary": 5.411894431054339, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 196.66906382025346, + "minimum_plateau_f": 196.66906382025346, + "property_c_endpoint": 196.66906382025346, + "table_relative_error_on_midpoints": 4.6527683927877206e-09, + "npz_sha256": "16e812914de0892f97c29a8616b5061b6627a1aeeec4efacde10fb93db3d47c5", + "csv_sha256": "4e2de9944569c04c61f8739ff89a38573c0c2605dbbd4306cb453b1bb58c98bc", + "diagnostics": { + "alpha": 0.02, + "plateau_f": 196.66906382025346, + "plateau_critical": 2.4061165164567275, + "property_c_endpoint": 196.66906382025346, + "maximum_rejection": 0.020000000247967272, + "worst_strength": 11.056813720041351, + "worst_rho": 1.0, + "integration_error": 1.2723247848305112e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 579, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.02.npz b/linearmodels/iv/_tf_data/alpha-0.02.npz new file mode 100644 index 0000000000..54b1ee021b Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.02.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.025.json b/linearmodels/iv/_tf_data/alpha-0.025.json new file mode 100644 index 0000000000..09016795e7 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.025.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.025, + "boundary": 5.023886187314887, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 179.11730244636962, + "minimum_plateau_f": 179.11730244636962, + "property_c_endpoint": 179.11730244636962, + "table_relative_error_on_midpoints": 5.826595206315233e-09, + "npz_sha256": "08e98b2c59db17a713e764eddf347cb4e8aaa0cbeb66afd330b8fe822d0b17ca", + "csv_sha256": "e4e7f0071ae46de94eea67cbd3224bd98518cf988f6d8994b1915b0e333ea90a", + "diagnostics": { + "alpha": 0.025, + "plateau_f": 179.11730244636962, + "plateau_critical": 2.2962406405146885, + "property_c_endpoint": 179.11730244636962, + "maximum_rejection": 0.02500000029315909, + "worst_strength": 10.841767301545556, + "worst_rho": 1.0, + "integration_error": 1.4357015459209582e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 590, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.025.npz b/linearmodels/iv/_tf_data/alpha-0.025.npz new file mode 100644 index 0000000000..21e13fa86e Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.025.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.03.json b/linearmodels/iv/_tf_data/alpha-0.03.json new file mode 100644 index 0000000000..49b679e98d --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.03.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.03, + "boundary": 4.7092922468851, + "epsilon": 0.0001, + "seeds": 256, + "points": 16385, + "plateau_f": 164.93910675626424, + "minimum_plateau_f": 164.93910675626424, + "property_c_endpoint": 164.93910675626424, + "table_relative_error_on_midpoints": 6.51903575565882e-09, + "npz_sha256": "db678696c411d189a3b4cbbc3390fa35a571a1974e9e9ca32e5de49486b9daaa", + "csv_sha256": "ec4bcc8271f160d44170e73f980a992a4f9d422d1162432e313fda648d08d74c", + "diagnostics": { + "alpha": 0.03, + "plateau_f": 164.93910675626424, + "plateau_critical": 2.203486777394789, + "property_c_endpoint": 164.93910675626424, + "maximum_rejection": 0.030000000293384902, + "worst_strength": 9.599309475806987, + "worst_rho": 1.0, + "integration_error": 1.7682211640171827e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 570, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.03.npz b/linearmodels/iv/_tf_data/alpha-0.03.npz new file mode 100644 index 0000000000..b5ecacf7f8 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.03.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.04.json b/linearmodels/iv/_tf_data/alpha-0.04.json new file mode 100644 index 0000000000..cf50915f6a --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.04.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.04, + "boundary": 4.217884587921399, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 134.28386729582988, + "minimum_plateau_f": 140.74917720163543, + "property_c_endpoint": 142.90845056072232, + "table_relative_error_on_midpoints": 5.987829676534773e-09, + "npz_sha256": "e44c711e11f1d078469d1f7a462f06e34fd9d6735e7a3a1c95345f2f36267357", + "csv_sha256": "2543c8ca9e1352507f275f5f5f43b8e700dc295694a214cb12bc9e98d5781415", + "diagnostics": { + "alpha": 0.04, + "plateau_f": 134.28386729582988, + "plateau_critical": 2.06224197132731, + "property_c_endpoint": 142.90845056072232, + "maximum_rejection": 0.040000000274591246, + "worst_strength": 7.6845569570804, + "worst_rho": 1.0, + "integration_error": 3.017180615740115e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 2335, + "adjustments": 3, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.04.npz b/linearmodels/iv/_tf_data/alpha-0.04.npz new file mode 100644 index 0000000000..599ec739c9 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.04.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.05.json b/linearmodels/iv/_tf_data/alpha-0.05.json new file mode 100644 index 0000000000..3e07dcbe26 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.05.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.05, + "boundary": 3.841458820694127, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 104.67075038975145, + "minimum_plateau_f": 104.67075038975145, + "property_c_endpoint": 126.15250281417146, + "table_relative_error_on_midpoints": 6.004694741434946e-09, + "npz_sha256": "02225e6c4cad30d01bf8f2246fa1e890d0aaf6c297ce4d7246e461c185d9926f", + "csv_sha256": "77cc409b90f85414f8b3ebd74a995435fcf99fd32fb4426429dc85c757cc58af", + "diagnostics": { + "alpha": 0.05, + "plateau_f": 104.67075038975145, + "plateau_critical": 1.9599639845400547, + "property_c_endpoint": 126.15250281417146, + "maximum_rejection": 0.050000000376763186, + "worst_strength": 8.54536496024176, + "worst_rho": 1.0, + "integration_error": 3.7223869854385816e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 497, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.05.npz b/linearmodels/iv/_tf_data/alpha-0.05.npz new file mode 100644 index 0000000000..b4152142bb Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.05.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.06.json b/linearmodels/iv/_tf_data/alpha-0.06.json new file mode 100644 index 0000000000..801600a999 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.06.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.06, + "boundary": 3.537384596462602, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 80.45037359373394, + "minimum_plateau_f": 80.45037359373394, + "property_c_endpoint": 112.71343131683145, + "table_relative_error_on_midpoints": 5.472060693456626e-09, + "npz_sha256": "471d61360a1ebe8418dd5f9fb519c2c474b290371c7dc57286ca95a1dc016d58", + "csv_sha256": "53e579c7dec45cc7adcdaf4094bdcaee2d6b8a7767a5cdd93710818975c5451d", + "diagnostics": { + "alpha": 0.06, + "plateau_f": 80.45037359373394, + "plateau_critical": 1.8807936081512513, + "property_c_endpoint": 112.71343131683145, + "maximum_rejection": 0.060000000280090685, + "worst_strength": 6.3520319767386235, + "worst_rho": 1.0, + "integration_error": 3.781321524850523e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 478, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.06.npz b/linearmodels/iv/_tf_data/alpha-0.06.npz new file mode 100644 index 0000000000..d45bb60d70 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.06.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.075.json b/linearmodels/iv/_tf_data/alpha-0.075.json new file mode 100644 index 0000000000..054a7c7197 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.075.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.075, + "boundary": 3.170053272036819, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 56.51596441692038, + "minimum_plateau_f": 56.51596441692038, + "property_c_endpoint": 96.62370750909679, + "table_relative_error_on_midpoints": 8.491234160246108e-09, + "npz_sha256": "a901e3f7ad066861f67a7da3f85575092d39cb21d6db86837444ca7552abe2cc", + "csv_sha256": "a638015c6a339bf762f26030a12a91d045515b49debd7d4c7786f5a331b5445a", + "diagnostics": { + "alpha": 0.075, + "plateau_f": 56.51596441692038, + "plateau_critical": 1.7804643416920258, + "property_c_endpoint": 96.62370750909679, + "maximum_rejection": 0.07500000022600083, + "worst_strength": 5.048989380267002, + "worst_rho": 1.0, + "integration_error": 5.527258430189328e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 491, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.075.npz b/linearmodels/iv/_tf_data/alpha-0.075.npz new file mode 100644 index 0000000000..3c1b9f1826 Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.075.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.1.json b/linearmodels/iv/_tf_data/alpha-0.1.json new file mode 100644 index 0000000000..45c05d2f02 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.1.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.1, + "boundary": 2.705543454095415, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 33.665956431489995, + "minimum_plateau_f": 33.665956431489995, + "property_c_endpoint": 76.57973915772868, + "table_relative_error_on_midpoints": 7.945314628443612e-09, + "npz_sha256": "20d9b6dfa98bd48fcf7801b77b759976c827eca151ca37ba8788d2051c305063", + "csv_sha256": "598c490ea2a29f4faf6237f78d530feb55a93cf7c3ba8f9cad7ee428effed593", + "diagnostics": { + "alpha": 0.1, + "plateau_f": 33.665956431489995, + "plateau_critical": 1.6448536269514729, + "property_c_endpoint": 76.57973915772868, + "maximum_rejection": 0.10000000015263304, + "worst_strength": 3.944604851264378, + "worst_rho": 1.0, + "integration_error": 4.723455547919271e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 488, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.1.npz b/linearmodels/iv/_tf_data/alpha-0.1.npz new file mode 100644 index 0000000000..631b4f486d Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.1.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.125.json b/linearmodels/iv/_tf_data/alpha-0.125.json new file mode 100644 index 0000000000..19af4a09c4 --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.125.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.125, + "boundary": 2.353525844604553, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 21.146296690664602, + "minimum_plateau_f": 21.146296690664602, + "property_c_endpoint": 61.70286761420511, + "table_relative_error_on_midpoints": 1.2136210703062034e-08, + "npz_sha256": "f79dadd8bca9e21fceb6edd090704753d37a17a4827e0d50c6eb0a1ecc33f4fb", + "csv_sha256": "177c82ff132562cf7e7c93a2280aa5c7ed7393643d968edf495762af07b83ae6", + "diagnostics": { + "alpha": 0.125, + "plateau_f": 21.146296690664602, + "plateau_critical": 1.5341205443525459, + "property_c_endpoint": 61.70286761420511, + "maximum_rejection": 0.12500000012889223, + "worst_strength": 1.697222136187667, + "worst_rho": 1.0, + "integration_error": 8.042012641059126e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 487, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.125.npz b/linearmodels/iv/_tf_data/alpha-0.125.npz new file mode 100644 index 0000000000..3c48f8882d Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.125.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.15.json b/linearmodels/iv/_tf_data/alpha-0.15.json new file mode 100644 index 0000000000..995714af4b --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.15.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.15, + "boundary": 2.0722508558222357, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 13.701434186863356, + "minimum_plateau_f": 13.701434186863356, + "property_c_endpoint": 50.090679226204614, + "table_relative_error_on_midpoints": 1.700124641068612e-08, + "npz_sha256": "f6ca5b843ea41172f8b2685a69a1f842ba794b796b49fa5dab9868ceaddb9d24", + "csv_sha256": "0de0f42f381d2e8226d110af6e415938c4160e9a65282fc2c336a1772abb5108", + "diagnostics": { + "alpha": 0.15, + "plateau_f": 13.701434186863356, + "plateau_critical": 1.4395314709384561, + "property_c_endpoint": 50.090679226204614, + "maximum_rejection": 0.15000000060557778, + "worst_strength": 1.7521367012976137, + "worst_rho": 1.0, + "integration_error": 8.49538670405967e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 495, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.15.npz b/linearmodels/iv/_tf_data/alpha-0.15.npz new file mode 100644 index 0000000000..7d3d2fa19a Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.15.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.2.json b/linearmodels/iv/_tf_data/alpha-0.2.json new file mode 100644 index 0000000000..76de72f25f --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.2.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.2, + "boundary": 1.642374415149816, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 6.154597216985797, + "minimum_plateau_f": 6.154597216985797, + "property_c_endpoint": 33.076172666345045, + "table_relative_error_on_midpoints": 3.43024904037037e-08, + "npz_sha256": "15c247da1ff6083427cceeea503dfef553bb9ab02b9680e000ca366cef15b8c9", + "csv_sha256": "21a4e6adacf207dcdaa8fe6d73d8dbc81096f2e6a64d810c22c0a9b2b2e7d7d0", + "diagnostics": { + "alpha": 0.2, + "plateau_f": 6.154597216985797, + "plateau_critical": 1.2815515655446006, + "property_c_endpoint": 33.076172666345045, + "maximum_rejection": 0.20000000034156185, + "worst_strength": 1.5027819415484973, + "worst_rho": 1.0, + "integration_error": 9.611396368631825e-09, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 487, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.2.npz b/linearmodels/iv/_tf_data/alpha-0.2.npz new file mode 100644 index 0000000000..033a0d567e Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.2.npz differ diff --git a/linearmodels/iv/_tf_data/alpha-0.25.json b/linearmodels/iv/_tf_data/alpha-0.25.json new file mode 100644 index 0000000000..7a2f22e4ba --- /dev/null +++ b/linearmodels/iv/_tf_data/alpha-0.25.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "alpha": 0.25, + "boundary": 1.3233036969314653, + "epsilon": 0.0001, + "seeds": 256, + "points": 16386, + "plateau_f": 3.265871233471566, + "minimum_plateau_f": 3.265871233471566, + "property_c_endpoint": 21.47050086071391, + "table_relative_error_on_midpoints": 6.784580430796439e-08, + "npz_sha256": "405358e50df8e28bc25f854ac3cac7348a858513f21a1a45c993b201deeb3910", + "csv_sha256": "b194848d420c216850ce87062511d2016c02a9907f35b64eadb4406a561431a0", + "diagnostics": { + "alpha": 0.25, + "plateau_f": 3.265871233471566, + "plateau_critical": 1.1503493803760076, + "property_c_endpoint": 21.47050086071391, + "maximum_rejection": 0.2500000001877195, + "worst_strength": 0.9813912207590972, + "worst_rho": 1.0, + "integration_error": 1.1004198018267793e-08, + "size_tolerance": 2e-07, + "strength_limit": 80, + "evaluations": 495, + "adjustments": 0, + "correlations": [ + 0.0, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 0.995, + 0.999, + 0.9999, + 1.0 + ], + "strengths": [ + 0.0, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 1.1518342801539727, + 1.3267222089378203, + 1.5281641204961829, + 1.7601918196888497, + 2.0274492775642177, + 2.3352855791718725, + 2.689861984039387, + 3.0982752420995436, + 3.5686996332026037, + 4.110550573095668, + 4.734673060398148, + 5.453558736288107, + 6.281595901289821, + 7.235357493180306, + 8.333932789813991, + 9.599309475806987, + 11.056813720041351, + 12.7356170720204, + 14.66932032246726, + 16.89662601397712, + 19.462113061840224, + 22.417128988859965, + 25.82081763200227, + 29.74130289014434, + 34.25705220531067, + 39.45844706710108, + 45.44959197352801, + 52.350398054120404, + 60.298983058441706, + 69.45443574513679, + 80.0 + ] + }, + "reference": "https://doi.org/10.1257/aer.20211063", + "algorithm": "Appendix B.3; regularized inverse map; Property C; finite size search" +} diff --git a/linearmodels/iv/_tf_data/alpha-0.25.npz b/linearmodels/iv/_tf_data/alpha-0.25.npz new file mode 100644 index 0000000000..fdce45836e Binary files /dev/null and b/linearmodels/iv/_tf_data/alpha-0.25.npz differ diff --git a/linearmodels/iv/results.py b/linearmodels/iv/results.py index 05ef6b9ded..52015fcbe1 100644 --- a/linearmodels/iv/results.py +++ b/linearmodels/iv/results.py @@ -17,6 +17,7 @@ c_, diag, empty, + isfinite, isnan, log, ndarray, @@ -31,8 +32,13 @@ from statsmodels.iolib.table import default_txt_fmt import linearmodels +from linearmodels.iv._tf import _tf_critical_value from linearmodels.iv._utility import annihilate, proj from linearmodels.iv.common import _kleibergen_paap, cragg_donald, cragg_donald_f +from linearmodels.iv.covariance import ( + HeteroskedasticCovariance, + HomoskedasticCovariance, +) from linearmodels.iv.data import IVData, IVDataLike from linearmodels.shared.base import _ModelComparison, _SummaryStr from linearmodels.shared.hypotheses import ( @@ -1312,6 +1318,120 @@ def __init__( super().__init__(results, model) self._kappa = results.get("kappa", 1) + def tf_conf_int(self, level: float = 0.95, *, method: str = "table") -> DataFrame: + """ + Construct a tF confidence interval for the endogenous coefficient. + + Parameters + ---------- + level : float + Confidence level, between 0.75 and 0.9999. The default is 0.95. + Bundled tables cover 0.75, 0.8, 0.85, 0.875, 0.9, 0.925, 0.94, + 0.95, 0.96, 0.97, 0.975, 0.98, 0.99, 0.995, 0.998, 0.999, + 0.9995 and 0.9999. + method : {"table", "compute"} + Use cached generated tables by default. "compute" explicitly runs + numerical calibration using the optional tf-inference package and + caches it by level. No interpolation across levels is performed. + + Returns + ------- + DataFrame + Lower and upper bounds for the endogenous regressor's coefficient. + The columns are ``lower`` and ``upper``. When the first-stage + statistic is at or below the squared normal critical value for + the requested level, the bounds are negative and positive infinity. + + Raises + ------ + ValueError + If the result is not from unweighted IV2SLS with one endogenous + regressor, one excluded instrument, unadjusted or robust covariance, + and ``debiased=False``, or the required estimates are not finite + with strictly positive variances; or if the level or method is + invalid, or a table is unavailable for the requested level. + ImportError + If "compute" is requested without the optional tf-inference package. + + See Also + -------- + conf_int + Construct conventional confidence intervals for all coefficients. + first_stage + Access first-stage regressions and diagnostics. + + Notes + ----- + The tF procedure of Lee et al. [1]_ adjusts the critical value using + the squared first-stage t statistic. It provides asymptotic inference + robust to weak identification under the single-instrument IV model's + assumptions. It does not address invalid instruments or guarantee + finite-sample coverage. + + The tables are generated by tf-inference from the numerical construction + in Appendix B.3, rather than from the printed rounded tables. At 95%, + the boundary is approximately 3.84145882; F strictly above it gives a + finite interval, including F between this boundary and 4. The plateau + is calibrated separately for each level. + + Calibration uses deterministic integration, a finite nuisance-parameter + grid and local searches. These numerical checks are not a proof of a + uniform size bound. Table interpolation is an approximation to the + computed curve, not the paper's conservative printed-table interpolation. + For custom calibration settings and diagnostics, use tf-inference + directly. This method does not define p-values. The coefficient estimate, + reported standard error and conventional inference are not changed. + + References + ---------- + .. [1] Lee, D. S., J. McCrary, M. J. Moreira, and J. Porter (2022). + "Valid t-Ratio Inference for IV." American Economic Review, + 112(10), 3260-3290. https://doi.org/10.1257/aer.20211063. + Author version: https://eml.berkeley.edu/~jmccrary/LMMP_2_15_22.pdf. + """ + if ( + self.method != "IV-2SLS" + or self.model.endog.shape[1] != 1 + or self.model.instruments.shape[1] != 1 + ): + raise ValueError( + "tF inference requires IV2SLS with one endogenous regressor " + "and one excluded instrument." + ) + if self.debiased or type(self._cov_estimator) not in ( + HomoskedasticCovariance, + HeteroskedasticCovariance, + ): + raise ValueError( + "tF inference requires unadjusted or robust covariance " + "with debiased=False." + ) + if (self.model.weights.ndarray != 1).any(): + raise ValueError("tF inference does not support nonconstant weights.") + + name = self.model.endog.cols[0] + first_stage = self.first_stage.individual[str(name)] + estimate = float(self.params.iloc[-1]) + variance = float(self.cov.to_numpy()[-1, -1]) + first_estimate = float(first_stage.params.iloc[-1]) + first_variance = float(first_stage.cov.to_numpy()[-1, -1]) + if ( + not isfinite([estimate, variance, first_estimate, first_variance]).all() + or variance <= 0 + or first_variance <= 0 + ): + raise ValueError( + "tF inference requires finite estimates and positive variances." + ) + first_t = float(first_stage.tstats.iloc[-1]) + critical = _tf_critical_value(first_t * first_t, level, method=method) + half_width = critical * float(self.std_errors.iloc[-1]) + return DataFrame( + [[estimate - half_width, estimate + half_width]], + index=self.params.index[-1:], + columns=["lower", "upper"], + ) + @cached_property def sargan(self) -> InvalidTestStatistic | WaldTestStatistic: r""" diff --git a/linearmodels/tests/iv/test_jive.py b/linearmodels/tests/iv/test_jive.py index 5e99a87a70..4497db9010 100644 --- a/linearmodels/tests/iv/test_jive.py +++ b/linearmodels/tests/iv/test_jive.py @@ -829,7 +829,11 @@ def test_results(): # JIVE is not a k-class estimator assert res.kappa is None for attr in dir(res): - if attr.startswith("_") or attr in ("test_linear_constraint", "wald_test"): + if attr.startswith("_") or attr in ( + "test_linear_constraint", + "wald_test", + "tf_conf_int", + ): continue value = getattr(res, attr) if callable(value): diff --git a/linearmodels/tests/iv/test_results.py b/linearmodels/tests/iv/test_results.py index 69dd6cce06..927976c597 100644 --- a/linearmodels/tests/iv/test_results.py +++ b/linearmodels/tests/iv/test_results.py @@ -22,7 +22,11 @@ def model(request): def result_checker(res): for attr in dir(res): - if attr.startswith("_") or attr in ("test_linear_constraint", "wald_test"): + if attr.startswith("_") or attr in ( + "test_linear_constraint", + "wald_test", + "tf_conf_int", + ): continue print(attr) if attr == "summary": diff --git a/linearmodels/tests/iv/test_tf.py b/linearmodels/tests/iv/test_tf.py new file mode 100644 index 0000000000..cfef2edd4c --- /dev/null +++ b/linearmodels/tests/iv/test_tf.py @@ -0,0 +1,457 @@ +from pathlib import Path +import shutil +from types import SimpleNamespace + +import numpy as np +from numpy.testing import assert_allclose, assert_array_equal +from pandas import DataFrame, Series +from pandas.testing import assert_frame_equal, assert_series_equal +import pytest +from scipy.special import ndtri + +from linearmodels.iv import IV2SLS, IVJIVE, IVLIML, _tf +from linearmodels.iv._tf import _tf_critical_value +from linearmodels.iv.results import FirstStageResults +from linearmodels.shared.exceptions import MissingValueWarning + + +@pytest.fixture +def data(): + rng = np.random.default_rng(98765) + n = 200 + raw = np.column_stack([np.ones(n), rng.standard_normal((n, 4))]) + q, _ = np.linalg.qr(raw) + w, z, v, e = q[:, :2], q[:, 2], q[:, 3], q[:, 4] + z, v, e = (a * np.sqrt(n) for a in (z, v, e)) + x = np.sqrt(10 / n) * z + v + u = 0.4 * np.sqrt(10) * (0.8 * v + 0.6 * e) + y = x + w @ np.array([0.5, 2.0]) + u + return y, w, x, z + + +@pytest.mark.parametrize( + ("f_stat", "printed"), + [(4, 18.656), (4.008, 18.236), (9.835, 3.463), (10.253, 3.385), (24.605, 2.461)], +) +def test_published_rounding_bounds(f_stat, printed): + # Table 3a rounds both F and the critical value upward to three decimals. + assert _tf_critical_value(f_stat) <= printed + 0.0001 + assert _tf_critical_value(f_stat - 0.001) >= printed - 0.0011 + + +def test_numerical_values(): + assert_allclose(_tf_critical_value(3.9), 30.92820746, rtol=1e-9) + assert_allclose(_tf_critical_value(10), 3.43035370, rtol=1e-9) + assert_allclose(_tf_critical_value(1e12), 1.95996398454, rtol=1e-11) + assert_allclose(_tf_critical_value(1e12, 0.99), 2.72548606, rtol=2e-9) + + +@pytest.mark.parametrize("alpha", [0.05, 0.01, 0.1, 0.0001, 0.25]) +def test_exact_boundary(alpha): + q = float(ndtri(alpha / 2) ** 2) + for f_stat in [0, np.nextafter(q, 0), q]: + assert _tf_critical_value(f_stat, 1 - alpha) == np.inf + f_stat = np.nextafter(q, np.inf) + critical = _tf_critical_value(f_stat, 1 - alpha) + assert np.isfinite(critical) + assert_allclose(critical**2 * (f_stat - q), q**3, rtol=1e-12) + + +@pytest.mark.parametrize("f_stat", [-1, -np.inf, np.inf, np.nan]) +def test_invalid_f(f_stat): + with pytest.raises(ValueError, match="finite and nonnegative"): + _tf_critical_value(f_stat) + + +def test_monotonicity_and_plateau(): + q = float(ndtri(0.025) ** 2) + f = q + np.geomspace(1e-12, 1e6, 1001) + critical = np.array([_tf_critical_value(x) for x in f]) + assert np.all(np.diff(critical) <= 0) + assert np.all(critical >= -ndtri(0.025) - 1e-14) + + +@pytest.mark.parametrize( + ("level", "critical"), [(0.95, 3.43035370), (0.99, 8.85766022)] +) +def test_hand_calculated_interval(data, level, critical): + y, w, x, z = data + res = IV2SLS(y, w, x, z).fit(cov_type="unadjusted") + # Orthogonal construction: first-stage F=10, beta=1, and IV SE=0.4. + assert_allclose(res.params.iloc[-1], 1, atol=1e-13) + assert_allclose(res.std_errors.iloc[-1], 0.4, rtol=1e-13) + assert_allclose( + res.tf_conf_int(level).iloc[0], [1 - 0.4 * critical, 1 + 0.4 * critical] + ) + assert res.conf_int().iloc[-1, 0] > 0 + assert res.tf_conf_int().iloc[0, 0] < 0 + + +@pytest.mark.parametrize( + "level", + [ + 0.75, + 0.8, + 0.85, + 0.875, + 0.9, + 0.925, + 0.94, + 0.95, + 0.96, + 0.97, + 0.975, + 0.98, + 0.99, + 0.995, + 0.998, + 0.999, + 0.9995, + 0.9999, + ], +) +def test_supported_levels(data, level): + y, w, x, z = data + result = IV2SLS(y, w, x, z).fit(cov_type="unadjusted") + critical = _tf_critical_value(10, level) + assert_allclose( + result.tf_conf_int(level).iloc[0], + [1 - 0.4 * critical, 1 + 0.4 * critical], + rtol=1e-12, + ) + assert np.isfinite(_tf_critical_value(20, level)) + + +def test_fit_below_old_cutoff_is_finite(data): + y, w, x, z = data + x = x + (np.sqrt(3.9 / len(y)) - np.sqrt(10 / len(y))) * z + result = IV2SLS(y, w, x, z).fit(cov_type="unadjusted") + first_t = result.first_stage.individual["endog"].tstats.iloc[-1] + assert_allclose(first_t**2, 3.9) + interval = result.tf_conf_int().iloc[0] + assert np.isfinite(interval).all() + width = (interval.iloc[1] - interval.iloc[0]) / 2 + assert_allclose(width / result.std_errors.iloc[-1], 30.92820746, rtol=1e-9) + + +@pytest.mark.parametrize("level", [0, 1, -1, 0.74999, 0.99999, np.nan, np.inf]) +def test_invalid_level(data, level): + y, w, x, z = data + with pytest.raises(ValueError, match="level must be finite"): + IV2SLS(y, w, x, z).fit().tf_conf_int(level) + + +def test_default_never_computes(data, monkeypatch): + def unexpected_import(name): + pytest.fail("Default lookup imported the optional calibration package") + + monkeypatch.setattr(_tf, "import_module", unexpected_import) + y, w, x, z = data + result = IV2SLS(y, w, x, z).fit() + assert np.isfinite(result.tf_conf_int(0.9)).all().all() + for level in [0.963, np.nextafter(0.95, 1)]: + with pytest.raises(ValueError, match="method='compute'"): + result.tf_conf_int(level) + + +@pytest.mark.parametrize("method", ["auto", "unknown"]) +def test_invalid_method(data, method): + y, w, x, z = data + with pytest.raises(ValueError, match="method must be"): + IV2SLS(y, w, x, z).fit().tf_conf_int(method=method) + + +def test_warm_table_no_io(monkeypatch): + _tf._tf_table.cache_clear() + expected = _tf_critical_value(10) + + def unexpected_work(*args, **kwargs): + pytest.fail("Warm lookup accessed files or rebuilt its interpolator") + + monkeypatch.setattr(_tf, "files", unexpected_work) + monkeypatch.setattr(_tf, "PchipInterpolator", unexpected_work) + monkeypatch.setattr(_tf.hashlib, "sha256", unexpected_work) + assert _tf_critical_value(10) == expected + + +def test_compute_requires_optional_package(data, monkeypatch): + def missing_package(name): + raise ModuleNotFoundError(name) + + _tf._tf_calibration.cache_clear() + monkeypatch.setattr(_tf, "import_module", missing_package) + y, w, x, z = data + with pytest.raises(ImportError, match="optional tf-inference"): + IV2SLS(y, w, x, z).fit().tf_conf_int(0.963, method="compute") + + +def test_explicit_calibration_cached_by_level(data, monkeypatch): + calls = [] + + def calibrate(alpha): + calls.append(alpha) + return SimpleNamespace(critical_value=lambda f_stat: 4.0) + + _tf._tf_calibration.cache_clear() + monkeypatch.setattr( + _tf, "import_module", lambda name: SimpleNamespace(calibrate=calibrate) + ) + y, w, x, z = data + result = IV2SLS(y, w, x, z).fit(cov_type="unadjusted") + try: + assert_allclose(result.tf_conf_int(0.963, method="compute"), [[-0.6, 2.6]]) + result.tf_conf_int(0.963, method="compute") + result.tf_conf_int(0.95, method="compute") + assert_allclose(calls, [0.037, 0.05]) + assert len(calls) == 2 + finally: + _tf._tf_calibration.cache_clear() + + +def test_optional_computation_with_real_package(data): + package = pytest.importorskip("tf_inference") + y, w, x, z = data + result = IV2SLS(y, w, x, z).fit(cov_type="unadjusted") + interval = result.tf_conf_int(0.963, method="compute") + expected = package.confidence_interval(1, 0.4, 10, 1 - 0.963, method="compute") + assert_allclose(interval.iloc[0], expected, rtol=1e-12) + + +def test_bundled_tables_match_source_package(): + package = pytest.importorskip("tf_inference") + rng = np.random.default_rng(12908) + for alpha in package.available_alphas(): + table = package.load_table(alpha) + f = table.boundary + np.exp(rng.uniform(np.log(1e-10), np.log(1e6), 100)) + actual = [_tf_critical_value(value, 1 - alpha) for value in f] + assert_allclose(actual, table.critical_value(f), rtol=1e-14) + + +@pytest.mark.parametrize("corruption", ["npz", "schema", "alpha"]) +def test_table_integrity(tmp_path, monkeypatch, corruption): + root = Path(_tf.__file__).parent / "_tf_data" + target = tmp_path / "_tf_data" + target.mkdir() + for suffix in [".npz", ".json"]: + shutil.copyfile( + root / ("alpha-0.05" + suffix), target / ("alpha-0.05" + suffix) + ) + if corruption == "npz": + path = target / "alpha-0.05.npz" + path.write_bytes(path.read_bytes() + b"corruption") + message = "checksum" + else: + path = target / "alpha-0.05.json" + content = path.read_text() + if corruption == "schema": + content = content.replace('"schema_version": 1', '"schema_version": 2') + else: + content = content.replace('"alpha": 0.05', '"alpha": 0.06') + path.write_text(content) + message = "metadata" + _tf._tf_table.cache_clear() + monkeypatch.setattr(_tf, "files", lambda name: tmp_path) + with pytest.raises(ValueError, match=message): + _tf_critical_value(10) + + +@pytest.mark.parametrize( + "cov_type", + [ + "unadjusted", + "robust", + "homoskedastic", + "heteroskedastic", + "homo", + "hccm", + "HomoskedasticCovariance", + "HeteroskedasticCovariance", + ], +) +@pytest.mark.parametrize("controls", [True, False]) +def test_independent_covariance_calculation(data, cov_type, controls): + y, w, x, z = data + if not controls: + w = np.empty((len(y), 0)) + res = IV2SLS(y, w, x, z).fit(cov_type=cov_type) + design = np.column_stack([w, z]) + inverse = np.linalg.inv(design.T @ design) + first = inverse @ design.T @ x + v = x - design @ first + z_resid = z - w @ np.linalg.lstsq(w, z, rcond=None)[0] + beta = z_resid @ y / (z_resid @ x) + u0 = y - beta * x + u = u0 - w @ np.linalg.lstsq(w, u0, rcond=None)[0] + if cov_type.lower() in ( + "unadjusted", + "homoskedastic", + "homo", + "homoskedasticcovariance", + ): + first_var = (v @ v / len(v)) * inverse[-1, -1] + variance = (u @ u / len(u)) * (z_resid @ z_resid) / (z_resid @ x) ** 2 + else: + scores = design * v[:, None] + first_var = (inverse @ scores.T @ scores @ inverse)[-1, -1] + variance = ((z_resid * u) @ (z_resid * u)) / (z_resid @ x) ** 2 + f_stat = first[-1] ** 2 / first_var + expected = beta + np.array([-1, 1]) * np.sqrt(variance) * _tf_critical_value(f_stat) + assert_allclose(res.tf_conf_int().iloc[0], expected, rtol=1e-12, atol=1e-12) + + +def test_formula_labels_and_retained_sample(data): + y, w, x, z = data + frame = DataFrame({"y": y, "w": w[:, 1], "x": x, "z": z}) + frame.index = np.arange(len(frame)) * 3 + frame = frame.iloc[::2] + res = IV2SLS.from_formula("y ~ 1 + w + [x ~ z]", frame).fit() + array_res = IV2SLS( + frame.y, np.column_stack([np.ones(len(frame)), frame.w]), frame.x, frame.z + ).fit() + assert_frame_equal(res.tf_conf_int(), array_res.tf_conf_int()) + assert list(res.tf_conf_int().index) == ["x"] + assert list(res.tf_conf_int().columns) == ["lower", "upper"] + + +def test_duplicate_control_and_endogenous_labels(data): + y, w, x, z = data + expected = IV2SLS(y, w, DataFrame({"x": x}), z).fit().tf_conf_int() + controls = DataFrame(w, columns=["x", "w"]) + res = IV2SLS(y, controls, DataFrame({"x": x}), z).fit() + assert_frame_equal(res.tf_conf_int(), expected) + + +@pytest.mark.parametrize("label", [7, ("endogenous", "x")]) +def test_nonstring_endogenous_label(data, label): + y, w, x, z = data + expected = IV2SLS(y, w, x, z).fit().tf_conf_int() + actual = IV2SLS(y, w, Series(x, name=label), z).fit().tf_conf_int() + assert_allclose(actual, expected) + assert list(actual.index) == [label] + + +@pytest.mark.parametrize("instrument_scale", [-1000, -1, 1e-6, 1e6]) +def test_instrument_scale_and_sign(data, instrument_scale): + y, w, x, z = data + expected = IV2SLS(y, w, x, z).fit().tf_conf_int() + actual = IV2SLS(y, w, x, z * instrument_scale).fit().tf_conf_int() + assert_allclose(actual, expected, rtol=1e-9, atol=1e-9) + + +@pytest.mark.parametrize( + ("outcome_scale", "endog_scale"), [(2, 1), (-2, 1), (1, -3), (1e-4, 1e4)] +) +def test_coefficient_scale(data, outcome_scale, endog_scale): + y, w, x, z = data + expected = IV2SLS(y, w, x, z).fit().tf_conf_int().to_numpy() + expected = np.sort(expected * outcome_scale / endog_scale, axis=1) + actual = IV2SLS(y * outcome_scale, w, x * endog_scale, z).fit().tf_conf_int() + assert_allclose(actual, expected, rtol=1e-8, atol=1e-12) + + +def test_row_permutation_and_no_mutation(data): + y, w, x, z = data + res = IV2SLS(y, w, x, z).fit() + params, se, pvalues, conventional = ( + res.params.copy(), + res.std_errors.copy(), + res.pvalues.copy(), + res.conf_int().copy(), + ) + expected = res.tf_conf_int() + actual = IV2SLS(y[::-1], w[::-1], x[::-1], z[::-1]).fit().tf_conf_int() + assert_allclose(actual, expected, rtol=1e-12, atol=1e-12) + assert_series_equal(res.params, params) + assert_series_equal(res.std_errors, se) + assert_series_equal(res.pvalues, pvalues) + assert_frame_equal(res.conf_int(), conventional) + + +def test_weak_first_stage(data): + y, w, x, z = data + weak_x = x - (np.sqrt(10 / len(y)) - 0.01) * z + res = IV2SLS(y, w, weak_x, z).fit(cov_type="unadjusted") + assert_array_equal(res.tf_conf_int(), [[-np.inf, np.inf]]) + + +@pytest.mark.parametrize( + ("model", "config"), + [(IVLIML, {}), (IVLIML, {"kappa": 1}), (IVLIML, {"fuller": 1}), (IVJIVE, {})], +) +def test_unsupported_estimator(data, model, config): + y, w, x, z = data + with pytest.raises(ValueError, match="requires IV2SLS"): + model(y, w, x, z, **config).fit().tf_conf_int() + + +def test_iv2sls_subclass(data): + class CustomIV2SLS(IV2SLS): + pass + + y, w, x, z = data + expected = IV2SLS(y, w, x, z).fit().tf_conf_int() + actual = CustomIV2SLS(y, w, x, z).fit().tf_conf_int() + assert_frame_equal(actual, expected) + + +@pytest.mark.parametrize("two_endog", [True, False]) +def test_unsupported_dimensions(data, two_endog): + y, w, x, z = data + instruments = np.column_stack([z, z**2]) + endog = np.column_stack([x, x**2]) if two_endog else x + with pytest.raises(ValueError, match="one endogenous regressor"): + IV2SLS(y, w, endog, instruments).fit().tf_conf_int() + + +@pytest.mark.parametrize( + ("cov_type", "config"), + [ + ("robust", {"debiased": True}), + ("unadjusted", {"debiased": True}), + ("clustered", {"clusters": np.repeat(np.arange(20), 10)}), + ("kernel", {"bandwidth": 2}), + ], +) +def test_unsupported_covariance(data, cov_type, config): + y, w, x, z = data + with pytest.raises(ValueError, match="unadjusted or robust covariance"): + IV2SLS(y, w, x, z).fit(cov_type=cov_type, **config).tf_conf_int() + + +def test_weights(data): + y, w, x, z = data + expected = IV2SLS(y, w, x, z).fit().tf_conf_int() + actual = IV2SLS(y, w, x, z, weights=np.full(len(y), 7)).fit().tf_conf_int() + assert_frame_equal(actual, expected) + with pytest.raises(ValueError, match="nonconstant weights"): + IV2SLS(y, w, x, z, weights=np.linspace(1, 2, len(y))).fit().tf_conf_int() + + +@pytest.mark.parametrize("variance", [0, -1, np.nan, np.inf]) +def test_invalid_structural_variance(data, variance): + y, w, x, z = data + res = IV2SLS(y, w, x, z).fit() + res.cov.iloc[-1, -1] = variance + with pytest.raises(ValueError, match="finite estimates and positive variances"): + res.tf_conf_int() + + +@pytest.mark.parametrize("variance", [0, -1, np.nan, np.inf]) +def test_invalid_first_stage_variance(data, variance, monkeypatch): + y, w, x, z = data + res = IV2SLS(y, w, x, z).fit() + first = res.first_stage.individual + first[res.model.endog.cols[0]].cov.iloc[-1, -1] = variance + monkeypatch.setattr(FirstStageResults, "individual", property(lambda self: first)) + with pytest.raises(ValueError, match="finite estimates and positive variances"): + res.tf_conf_int() + + +def test_missing_observations(data): + y, w, x, z = data + y, x = y.copy(), x.copy() + y[0], x[1] = np.nan, np.nan + with pytest.warns(MissingValueWarning): + res = IV2SLS(y, w, x, z).fit() + complete = IV2SLS(y[2:], w[2:], x[2:], z[2:]).fit() + assert_frame_equal(res.tf_conf_int(), complete.tf_conf_int())