Model-Assisted Joint Analysis

Model-assisted joint analysis fits a nonparametric joint distribution over retained public and hidden cells, then uses posterior/bootstrap draws to rerun the audit. It complements adversarial Q-based ambiguity by summarizing outcomes under sampled cell laws.

Fit A Joint Distribution

import updatesupport as us

joint = us.fit_joint_distribution(
    rows_or_frame,
    public=["AGE_BAND", "EDU_BAND", "SEX"],
    hidden=["AGE_BAND", "EDU_BAND", "SEX", "OCC_MAJOR", "WKHP_BAND", "RAC1P"],
    target="income_over_threshold",
    weight="sample_weight",
    method="bayesian_bootstrap",
)

The fitted model stores retained hidden cells, their empirical joint probabilities, and their hidden-cell target values. The default method="bayesian_bootstrap" draws new cell masses from a Dirichlet distribution centered on the empirical cell probabilities. Use method="empirical" when you want deterministic draws equal to the fitted empirical joint law. Use method="bootstrap" for an ordinary multinomial nonparametric bootstrap over retained hidden cells.

You can inspect or reuse draws directly:

draw = joint.draw(seed=123)
records = draw.records()

The records are weighted hidden-cell rows with generated target and weight columns, so they can be fed back into the usual report helpers.

For update-support audits, the most direct draw is usually a hidden-composition draw:

draw = joint.hidden_composition_draw(seed=123)

That draw preserves the fitted public law and resamples hidden-cell shares inside each public fiber. Plain joint.draw(...) samples the full joint law, so public bucket masses can move too.

Posterior / Bootstrap Uncertainty Report

Use hidden_composition_uncertainty(...) when you want a standalone uncertainty report over hidden composition:

uncertainty = us.hidden_composition_uncertainty(
    rows_or_frame,
    public=["AGE_BAND", "EDU_BAND", "SEX"],
    hidden=["AGE_BAND", "EDU_BAND", "SEX", "OCC_MAJOR", "WKHP_BAND", "RAC1P"],
    target="income_over_threshold",
    weight="sample_weight",
    method="bayesian_bootstrap",
    draws=500,
    seed=123,
    q=us.q_tv_budget(0.10),
    ambiguity_limit=0.015,
    confidence_level=0.90,
)

print(uncertainty.to_markdown())

The report summarizes posterior/bootstrap uncertainty over:

  • the observed aggregate value under sampled hidden-cell masses,

  • lower and upper hidden-composition interval endpoints,

  • ambiguity width,

  • public adequacy rate,

  • claim failure rate against ambiguity_limit.

By default, hidden_composition_uncertainty(...) uses preserve_public_law=True. This keeps the public bucket distribution fixed and resamples hidden composition within each public fiber. Set preserve_public_law=False when you intentionally want full joint public/hidden composition uncertainty.

It also emits structured tables:

uncertainty.to_tables()["metric_summaries"]
uncertainty.to_tables()["draws"]
uncertainty.to_tables()["joint_cells"]

Audit A Claim With Joint Draws

claim = us.claim(
    estimate_name="Income-threshold target rate",
    public=["AGE_BAND", "EDU_BAND", "SEX"],
    hidden=["AGE_BAND", "EDU_BAND", "SEX", "OCC_MAJOR", "WKHP_BAND", "RAC1P"],
    target="income_over_threshold",
    weight="sample_weight",
    q_presets=[us.q_tv_budget(0.10), us.q_chi_square_budget(0.25)],
    candidate_refinements=["OCC_MAJOR", "WKHP_BAND", "RAC1P"],
    ambiguity_limit=0.015,
)

verdict = us.audit_claim(
    rows_or_frame,
    claim,
    joint_model=joint,
    joint_draws=500,
    joint_seed=123,
)

print(verdict.to_markdown())

The claim report adds a Model-Assisted Joint Analysis section with:

  • number of successful draws,

  • failure rate against the claim’s ambiguity_limit,

  • public adequacy rate,

  • ambiguity range and mean ambiguity across draws,

  • per-draw status rows.

If joint_model is omitted but joint_draws is positive, audit_claim(...) fits the joint model from the same data using the claim’s public, hidden, target, weight, and min_cell_weight settings.

Claim-level model-assisted analysis uses hidden-composition draws with the public law preserved, matching the main reporting-stability question. Use the standalone uncertainty report with preserve_public_law=False for monitoring questions where future public bucket mix is also allowed to vary.

Interpretation

Read the outputs as three distinct layers:

  • Observed-support ambiguity: adversarial Q-based interval on the retained observed support.

  • Model-assisted hidden-composition draws: plausible within-public-fiber compositions according to the fitted nonparametric joint model.

  • Statistical uncertainty: external standard errors or intervals supplied by an upstream estimator.

The model-assisted layer introduces assumptions through the fitted joint cell law and the chosen effective sample size. It is useful for plausibility, future-composition stress tests, sparse-cell sensitivity, and monitoring, but it should not be described as a distribution-free robustness guarantee.