fips.Posterior.recovery#
- Posterior.recovery(world=None, *, truth=None, W=None, level=0.95)[source]#
Return how well the estimate recovers the truth when the data come from world: exactly, without sampling.
The exact answer of the Monte Carlo loop over
world.simulate(rng)andwith_data(). The estimate is linear in the observations, \(\hat{x} = x_0 + K (z - H x_0 - c)\), with the gain \(K\) of this posterior (its problem’s \(H\), \(c\), \(S_0\) and \(S_z / \gamma\)). When the world makes the data, with a truth \(x \sim N(\mu, P)\) (its prior, or a given truth with \(P = 0\)) and observations \(z = H_w x + c_w + e\), \(e \sim N(0, R_w)\) (its forward operator, offset and model-data mismatch, not divided by \(\gamma\)), the error \(\hat{x} - x\) is Gaussian, with\[b = (x_0 - \mu) + K (H_w \mu + c_w - H x_0 - c), \qquad V = (K H_w - I) P (K H_w - I)^T + K R_w K^T.\]For an aggregation \(W\), \(W b\) and \(W V W^T\), from \(W K\) (
k x m) and \(W K H_w - W\) (k x n), with \(P\) and \(R_w\) applied tokcolumns in their own forms (through the factors of a Kronecker covariance): nothing \(n \times n\) or \(m \times m\) beyond what the posterior holds. With the world the problem itself and \(\gamma = 1\), the bias is zero and \(V\) is \(\hat{S}\) (in Joseph form), sostdequalsposterior_std.- Parameters:
world (
Problem|None) – The problem that makes the data, with exactly this posterior’s state and observation labels (in any order); its own observations are not used. By default the posterior’s problem. A world on other state labels (the whole state where the solve was restricted) is not taken: useworld.simulateandwith_data()in a loop.truth (
Vector|None) – A fixed truth, on the state’s labels in any order, instead of the world’s prior (another inventory, 1.5 times the prior). Only the observation error is then random.W (
Operator|None) – An aggregation, ascov()takes it: one row per total. By default, one row per state element.level (
float) – The probability of the posterior’s interval thatcoverageis computed for, \(\hat{x} \pm z_{level} \hat{\sigma}\).
- Returns:
One row per aggregate (or state element), on the aggregation’s rows (or the state’s labels):
prior(\(W x_0\)),truth(\(W \mu\)),estimate(the expected \(W \hat{x}\)),bias(estimate - truth),std(the standard deviation of the error, \(\sqrt{\operatorname{diag} W V W^T}\)),posterior_std(what the posterior claims, \(\sqrt{\operatorname{diag} W \hat{S} W^T}\)) andcoverage(the probability that the level interval of the posterior holds the truth, for an error \(N(\text{bias}, \text{std}^2)\)). A variance below zero by roundoff gives a standard deviation of 0; one further below zero, which only a covariance that is not positive semidefinite gives, gives NaN (and a NaNcoverage), with a warning logged. Roundoff issqrt(eps)(1.5e-8) times the largest variance the row could have under covariances with the same variances, \((\sum_i |x_i| \sigma_i)^2\).- Return type:
- Raises:
TypeError – If the method’s estimate is not linear in the observations (its solution does not set
estimate_is_linear), or world is not a Problem, or truth not a Vector.ValueError – If the world’s state or observations, the truth or the columns of W do not have exactly this posterior’s labels (the message names those on one side only), or level is not between 0 and 1.
See also
fips.Problem.simulateOne draw of a truth and its observations.
with_dataThe posterior of one draw’s observations.
covThe posterior covariance, of the state or of aggregates.
Examples
Paper 1’s experiment: a truth of 1.5 times the prior, observed without noise, and the fraction of the true change of each monthly total that the inversion recovers. The expected estimate is the one from noise-free observations, since the noise has mean zero;
stdis the spread the noise adds.>>> import numpy as np >>> import pandas as pd >>> import fips >>> state = pd.MultiIndex.from_product( ... [pd.Index([1, 2], name="month"), pd.Index(["a", "b"], name="cell")] ... ) >>> prior = fips.Vector.from_series(pd.Series(1.0, index=state), block="flux") >>> obs = fips.Vector.from_series( ... pd.Series(0.0, index=pd.RangeIndex(6, name="obs_id")), block="ch4" ... ) >>> rng = np.random.default_rng(0) >>> problem = fips.Problem( ... prior=prior, ... prior_cov=fips.Independent().with_std(0.5), ... obs=obs, ... obs_cov=fips.Independent().with_std(1.0), ... forward=fips.Operator( ... rng.uniform(0.0, 1.0, (6, 4)), obs.space, prior.space ... ), ... ) >>> post = fips.solve(problem) >>> monthly = fips.aggregate.sum(prior.space, by="month") >>> truth = fips.Vector(prior.values * 1.5, prior.space) >>> table = post.recovery(truth=truth, W=monthly) >>> table = table.droplevel("block") >>> table.round(3).reset_index() month prior truth estimate bias std posterior_std coverage 0 1 2.0 3.0 2.667 -0.333 0.309 0.528 0.988 1 2 2.0 3.0 2.538 -0.462 0.285 0.586 0.992 >>> moved = table["estimate"] - table["prior"] >>> needed = table["truth"] - table["prior"] >>> (moved / needed).round(3).tolist() # the recovered fraction [0.667, 0.538]
In its own world (the default), an inversion at \(\gamma = 1\) has no bias, and its intervals hold the truth as often as they claim:
>>> honest = post.recovery(W=monthly).droplevel("block") >>> honest[["bias", "std", "posterior_std", "coverage"]].round(3).reset_index() month bias std posterior_std coverage 0 1 0.0 0.528 0.528 0.95 1 2 0.0 0.586 0.586 0.95