twinfit 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- twinfit/__init__.py +8 -0
- twinfit/indirect.py +125 -0
- twinfit-0.0.1.dist-info/METADATA +76 -0
- twinfit-0.0.1.dist-info/RECORD +7 -0
- twinfit-0.0.1.dist-info/WHEEL +5 -0
- twinfit-0.0.1.dist-info/licenses/LICENSE +21 -0
- twinfit-0.0.1.dist-info/top_level.txt +1 -0
twinfit/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""twinfit: bias correction of a fit through a simulator of the data.
|
|
2
|
+
|
|
3
|
+
The first release provides the plug-in and the solved (indirect inference) corrections, see ``twinfit.indirect``.
|
|
4
|
+
"""
|
|
5
|
+
from .indirect import Correction, plugin_correction, solved_correction
|
|
6
|
+
|
|
7
|
+
__all__ = ["Correction", "plugin_correction", "solved_correction", "__version__"]
|
|
8
|
+
__version__ = "0.0.1"
|
twinfit/indirect.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Bias correction of a fit through a simulator of the data.
|
|
2
|
+
|
|
3
|
+
A fit maps a record to an estimate. When the estimate is biased, because the record is short, the fitted model is approximate or the
|
|
4
|
+
errors are not those the fit assumes, a simulator of the record that is right where the fit is wrong can measure that bias. Two
|
|
5
|
+
corrections are provided, both for a user's fit and a user's simulator.
|
|
6
|
+
|
|
7
|
+
``plugin_correction``
|
|
8
|
+
One step. The bias is measured by simulating at the estimate and fitting the simulations, and it is subtracted.
|
|
9
|
+
|
|
10
|
+
``solved_correction``
|
|
11
|
+
Indirect inference with the fit as its auxiliary statistic (Gourieroux, Monfort and Renault 1993). The parameters are found at
|
|
12
|
+
which the mean fit of the simulations equals the fit of the data. The same random draws are used at every trial (common random
|
|
13
|
+
numbers), so the equation is smooth in the parameters whenever the fit is.
|
|
14
|
+
|
|
15
|
+
The simulator is a callable ``simulate(theta, rng)`` returning one record, where ``rng`` is a ``numpy.random.Generator``. A
|
|
16
|
+
deterministic simulator, one that ignores ``rng``, gives the binding function itself and needs only ``n_sim=1``. The fit is a
|
|
17
|
+
callable ``fit(record)`` returning the estimate, a scalar or a one-dimensional array of the same length as ``theta``.
|
|
18
|
+
|
|
19
|
+
With ``n_sim`` simulations the solved estimate carries, besides the sampling error of the data, a simulation error that inflates
|
|
20
|
+
its variance by about ``1 + 1/n_sim`` when the simulator draws errors of the data's law (Gourieroux, Monfort and Renault 1993).
|
|
21
|
+
|
|
22
|
+
Reference: C. Gourieroux, A. Monfort and E. Renault (1993), Indirect inference, Journal of Applied Econometrics 8, S85-S118,
|
|
23
|
+
doi:10.1002/jae.3950080507.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
from scipy import optimize
|
|
31
|
+
|
|
32
|
+
__all__ = ["Correction", "plugin_correction", "solved_correction"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Correction:
|
|
37
|
+
"""The result of a correction.
|
|
38
|
+
|
|
39
|
+
estimate: the corrected parameters.
|
|
40
|
+
raw: the estimate that was corrected.
|
|
41
|
+
bias: the mean fit of the simulations minus the parameters they were simulated at, evaluated at the corrected estimate for the
|
|
42
|
+
solved form and at the raw estimate for the plug-in form.
|
|
43
|
+
jacobian: the derivative of the mean fit of the simulations with respect to the parameters at the corrected estimate (solved
|
|
44
|
+
form only, otherwise None). It carries a covariance of the raw estimate to the corrected one, see ``delta_covariance``.
|
|
45
|
+
converged: whether the solver met its tolerance (always True for the plug-in form).
|
|
46
|
+
n_sim: the number of simulations.
|
|
47
|
+
message: the solver's message.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
estimate: np.ndarray
|
|
51
|
+
raw: np.ndarray
|
|
52
|
+
bias: np.ndarray
|
|
53
|
+
jacobian: np.ndarray | None = None
|
|
54
|
+
converged: bool = True
|
|
55
|
+
n_sim: int = 0
|
|
56
|
+
message: str = field(default="")
|
|
57
|
+
|
|
58
|
+
def delta_covariance(self, raw_covariance):
|
|
59
|
+
"""The covariance of the corrected estimate from that of the raw one, through the delta method: J^-1 C J^-T."""
|
|
60
|
+
if self.jacobian is None:
|
|
61
|
+
raise ValueError("delta_covariance needs the solved form's jacobian")
|
|
62
|
+
c = np.atleast_2d(np.asarray(raw_covariance, float))
|
|
63
|
+
jinv = np.linalg.inv(self.jacobian)
|
|
64
|
+
return jinv @ c @ jinv.T
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _seeds(seed, n_sim):
|
|
68
|
+
if n_sim < 1:
|
|
69
|
+
raise ValueError("n_sim must be at least 1")
|
|
70
|
+
return np.random.SeedSequence(seed).spawn(n_sim)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _mean_fit(theta, simulate, fit, seeds):
|
|
74
|
+
values = [np.atleast_1d(np.asarray(fit(simulate(theta, np.random.default_rng(s))), float)) for s in seeds]
|
|
75
|
+
out = np.mean(values, axis=0)
|
|
76
|
+
if out.shape != np.shape(theta):
|
|
77
|
+
raise ValueError(f"the fit returned shape {out.shape} for parameters of shape {np.shape(theta)}")
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _as_parameters(estimate):
|
|
82
|
+
theta = np.atleast_1d(np.asarray(estimate, float)).copy()
|
|
83
|
+
if theta.ndim != 1:
|
|
84
|
+
raise ValueError("the estimate must be a scalar or a one-dimensional array")
|
|
85
|
+
return theta
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def plugin_correction(estimate, simulate, fit, n_sim=200, seed=0):
|
|
89
|
+
"""Subtract the bias measured at the estimate: theta_hat - (mean fit of simulations at theta_hat - theta_hat)."""
|
|
90
|
+
raw = _as_parameters(estimate)
|
|
91
|
+
bias = _mean_fit(raw, simulate, fit, _seeds(seed, n_sim)) - raw
|
|
92
|
+
return Correction(estimate=raw - bias, raw=raw, bias=bias, n_sim=n_sim, message="plug-in")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _jacobian(theta, simulate, fit, seeds, rel_step):
|
|
96
|
+
k = theta.size
|
|
97
|
+
jac = np.empty((k, k))
|
|
98
|
+
for j in range(k):
|
|
99
|
+
h = rel_step * max(1.0, abs(theta[j]))
|
|
100
|
+
up, down = theta.copy(), theta.copy()
|
|
101
|
+
up[j] += h
|
|
102
|
+
down[j] -= h
|
|
103
|
+
jac[:, j] = (_mean_fit(up, simulate, fit, seeds) - _mean_fit(down, simulate, fit, seeds)) / (2 * h)
|
|
104
|
+
return jac
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def solved_correction(estimate, simulate, fit, n_sim=200, seed=0, x0=None, tol=1e-10, rel_step=1e-6, maxfev=2000):
|
|
108
|
+
"""Solve mean fit of simulations at theta = estimate for theta, with common random numbers.
|
|
109
|
+
|
|
110
|
+
The search starts at ``x0`` when given and otherwise at the plug-in correction, which is exact when the bias does not depend on
|
|
111
|
+
the parameters.
|
|
112
|
+
"""
|
|
113
|
+
raw = _as_parameters(estimate)
|
|
114
|
+
seeds = _seeds(seed, n_sim)
|
|
115
|
+
start = _as_parameters(x0) if x0 is not None else raw - (_mean_fit(raw, simulate, fit, seeds) - raw)
|
|
116
|
+
|
|
117
|
+
def equation(theta):
|
|
118
|
+
return _mean_fit(theta, simulate, fit, seeds) - raw
|
|
119
|
+
|
|
120
|
+
sol = optimize.root(equation, start, method="hybr", tol=tol, options={"maxfev": maxfev})
|
|
121
|
+
theta = np.asarray(sol.x, float)
|
|
122
|
+
jac = _jacobian(theta, simulate, fit, seeds, rel_step)
|
|
123
|
+
bias = _mean_fit(theta, simulate, fit, seeds) - theta
|
|
124
|
+
return Correction(estimate=theta, raw=raw, bias=bias, jacobian=jac, converged=bool(sol.success), n_sim=n_sim,
|
|
125
|
+
message=str(sol.message))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: twinfit
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Bias correction of a fit through a simulator of the data (indirect inference)
|
|
5
|
+
Author: Michelangelo Dondi
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: indirect inference,bias correction,simulation,fitting,systematic errors
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy>=1.21
|
|
18
|
+
Requires-Dist: scipy>=1.7
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest; extra == "test"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# twinfit
|
|
24
|
+
|
|
25
|
+
Bias correction of a fit through a simulator of the data.
|
|
26
|
+
|
|
27
|
+
A fit maps a record to an estimate. When the estimate is biased, because the record is short, the fitted model is approximate or
|
|
28
|
+
the errors are not those the fit assumes, a simulator that is right where the fit is wrong can measure the bias. twinfit applies the
|
|
29
|
+
same fit to simulated records and corrects the estimate in one of two ways.
|
|
30
|
+
|
|
31
|
+
- `plugin_correction` measures the bias by simulating at the estimate, and subtracts it.
|
|
32
|
+
- `solved_correction` finds the parameters at which the mean fit of the simulations equals the fit of the data. This is indirect
|
|
33
|
+
inference with the fit as its auxiliary statistic (Gourieroux, Monfort and Renault 1993). The same random draws are used at every
|
|
34
|
+
trial, so the equation is smooth whenever the fit is.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install twinfit
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Example
|
|
43
|
+
|
|
44
|
+
The maximum likelihood estimate of a Gaussian variance from ten draws is biased low by a factor 9/10. The solved correction removes
|
|
45
|
+
that bias in expectation. A single record keeps its sampling error, so one corrected estimate can still sit far from the truth.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import numpy as np
|
|
49
|
+
from twinfit import solved_correction
|
|
50
|
+
|
|
51
|
+
def simulate(theta, rng): # one record at the parameters theta
|
|
52
|
+
return np.sqrt(theta[0]) * rng.standard_normal(10)
|
|
53
|
+
|
|
54
|
+
def fit(record): # the fit to correct
|
|
55
|
+
return np.var(record)
|
|
56
|
+
|
|
57
|
+
record = simulate([2.0], np.random.default_rng(1))
|
|
58
|
+
out = solved_correction(fit(record), simulate, fit, n_sim=400)
|
|
59
|
+
print(out.estimate, out.converged)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The result also carries the derivative of the mean fit with respect to the parameters, so that
|
|
63
|
+
`out.delta_covariance(C)` carries a covariance `C` of the raw estimate to the corrected one.
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
Version 0.0.1 is the first release. Further tools are in preparation.
|
|
68
|
+
|
|
69
|
+
## Reference
|
|
70
|
+
|
|
71
|
+
C. Gourieroux, A. Monfort and E. Renault (1993). Indirect inference. *Journal of Applied Econometrics* 8, S85-S118.
|
|
72
|
+
doi:10.1002/jae.3950080507
|
|
73
|
+
|
|
74
|
+
## Licence
|
|
75
|
+
|
|
76
|
+
MIT.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
twinfit/__init__.py,sha256=6jfhWHYAOupfRBIZI3yQRYffUjyUSKki2hAUxYA0tmM,365
|
|
2
|
+
twinfit/indirect.py,sha256=bg-TOA-37dXDzcwmATBWD1duiyEFcoG7ztQUhRaeJyY,5787
|
|
3
|
+
twinfit-0.0.1.dist-info/licenses/LICENSE,sha256=I_TdB-aoWaTP4wtJ2fQ3gxEfCdiaAFczigeRA2OVz7s,1075
|
|
4
|
+
twinfit-0.0.1.dist-info/METADATA,sha256=QVQZNFeRb2mcMQvN4fauPijdHv_pk0C7VNGT4WaANaA,2812
|
|
5
|
+
twinfit-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
twinfit-0.0.1.dist-info/top_level.txt,sha256=qNvGOE9vh4UGR8BxLrIRjMKIvovl_U7y-5JlhOftQfA,8
|
|
7
|
+
twinfit-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michelangelo Dondi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
twinfit
|