Representation adequacy and transport-stability auditing in Python
Project description
updatesupport
Audit whether the categories in your report are stable enough for the estimate you are reporting.
updatesupport quantifies hidden-composition ambiguity: how much an aggregate
rate, effect, or risk metric could move if the public buckets in a report stayed
fixed but the hidden mix inside those buckets changed.
It is useful when a dashboard, model-review pack, policy table, or causal analysis reports a number by coarse public categories and you need to know:
Are these categories enough to support this reported estimate?
Why It Exists
Many reports publish estimates by coarse buckets:
age band x education band x sex
product x region x FICO band x LTV band
segment x channel
Those buckets may hide subgroups with different target rates or effects. A
confidence interval answers sampling uncertainty. A model metric answers model
fit. updatesupport answers a different review question:
Holding the reported public mix fixed, how far could the aggregate move under plausible hidden subgroup shifts?
The output is a review-ready Markdown audit with:
- claim-level pass/fail/inconclusive verdicts
- observed estimate
- hidden-composition stress interval
- transport ambiguity, the interval width
- estimator-uncertainty-aware interval when hidden-cell standard errors are supplied
- public adequacy flag
- public cells driving the ambiguity
- refinement recommendations
- sensitivity checks
- public-representation frontier search for small stable reporting designs
This is not a replacement for causal identification, model validation, or sampling uncertainty. It is an audit layer for the reporting representation.
Why the Math Is Sound
updatesupport reduces the review question to a finite optimization problem.
Given hidden cells D, public buckets pi(D), supplied hidden-cell target
values h, and an explicit admissible distribution class Q, it computes:
inf/sup sum_d h(d) q(d)
subject to q in Q and fixed public law pi#q
For saturated reweighting this has a closed-form public-fiber range formula.
For linear Q it is solved as a linear program. For convex transport budgets
such as TV, chi-square, KL, and Wasserstein, it is solved as a convex CVXPY
problem with a linear objective.
The interval is statistically meaningful as a partial-identification or
sensitivity interval conditional on the retained support, target values, and
chosen Q. It is not a confidence interval. If hidden-cell target standard
errors are supplied, reports can add an estimator-uncertainty-aware outer
interval, but causal, survey-design, and broader model uncertainty still belong
to the upstream statistical workflow. With CVXPY-compatible Q sets, reports can
also include an SOCP confidence-core diagnostic showing whether all admissible
composition-specific confidence bands have a common overlap.
The core solver target is fixed after compilation. Most tabular reports compile
to the linear plug-in aggregate sum_d h(d) q(d); RatioTarget covers
supported fixed ratio targets; MomentTransformTarget covers affine moment
transforms, one-sided convex/concave CVXPY endpoints, and conservative monotone
moment bounds; and ProcedureTarget handles representation-dependent reporting
procedures by recompiling the target for each representation before solving.
Other nonlinear targets that depend directly on q still need an explicit
reformulation or a dedicated target-functional backend.
See docs/mathematical-statistical-soundness.md for the full assumptions, formulas, backend checks, and limitations.
Quickstart
pip install updatesupport
or, in a uv project:
uv add updatesupport
import updatesupport as us
report = us.public_descent_report(
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",
candidate_refinements=["OCC_MAJOR", "WKHP_BAND", "RAC1P"],
min_cell_weight=25,
q="saturated",
title="Income-Threshold Representation Audit",
)
print(report.to_markdown())
The report asks:
If we only report by
AGE_BAND x EDU_BAND x SEX, how much could the aggregate income-threshold rate move if occupation, work hours, and race composition changed inside those public cells?
The stress interval is not a confidence interval. It is a partial-identification / sensitivity interval for hidden composition.
A Concrete Result
In the Folktables ACSIncome demo, the observed target rate is 12.37%. When the
public mix by age band, education band, and sex is held fixed, hidden subgroup
composition can move the target rate from:
11.79% to 13.44%
The transport ambiguity is 1.65 percentage points. In plain English:
The coarse public categories almost determine the aggregate income-threshold rate in this sample, but not quite. Hidden occupational and household differences still matter.
See the full analyst interpretation in docs/folktables-acs-income-interpretation.md.
Main Workflows
1. Verify a Reporting Claim
Use ReportingClaim when you want one review artifact that certifies the
claim, breaks it with a hidden-composition witness, or proposes a stable repair.
claim = us.ReportingClaim(
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,
bucket_budget=40,
statistical_interval=(0.119, 0.128),
)
verdict = us.verify_claim(rows_or_frame, claim)
print(verdict.to_markdown())
The verifier separates the reported estimate, statistical uncertainty, hidden-composition ambiguity, public-refinement repair, counterexample witness, and limitations. See docs/reporting-claims.md.
For model-assisted plausibility checks, fit a nonparametric public/hidden joint distribution and run the claim across sampled hidden compositions:
joint = us.fit_joint_distribution(
rows_or_frame,
public=claim.public,
hidden=claim.hidden,
target=claim.target,
weight=claim.weight,
)
verdict = us.verify_claim(
rows_or_frame,
claim,
joint_model=joint,
joint_draws=500,
joint_seed=123,
)
Use hidden_composition_uncertainty(...) when you want the posterior/bootstrap
draw summary as its own report. By default it preserves the observed public
bucket mix and resamples hidden composition inside each public bucket.
See docs/model-assisted-joint-analysis.md.
2. Audit a Public Report
Use public_descent_report(...) when you already know the public buckets,
hidden refinements, and target metric.
report = us.public_descent_report(
rows_or_frame,
public=["segment"],
hidden=["segment", "region", "tenure_band"],
target="outcome_rate",
candidate_refinements=["region", "tenure_band"],
q=us.q_bounded_shift(0.5),
)
Use AuditSpec when the audit configuration should be serialized, reviewed, or
rerun:
spec = us.AuditSpec(
public=["segment"],
hidden=["segment", "region", "tenure_band"],
target="outcome_rate",
candidate_refinements=["region", "tenure_band"],
q={"name": "bounded_shift", "radius": 0.5},
)
run = spec.run(rows_or_frame)
print(run.to_markdown())
See docs/audit-specs.md.
Reports also expose structured JSON and tables:
run.to_json()
run.to_tables()
run.to_dataframes() # requires pandas
See docs/structured-exports.md.
Reports include pre-solve data diagnostics for sparse cells, dropped mass, singleton public fibers, constant-target fibers, and skipped refinement candidates. See docs/data-diagnostics.md.
When you need to inspect the actual lower-vs-upper endpoint worlds behind an
ambiguity result, use report.witness_report() or us.witness_report(...) to
see which hidden cells move while the public distribution stays fixed.
3. Run Robustness Checks
Use sensitivity_report(...) when the conclusion should be checked across Q
presets, sparse-cell thresholds, or alternate hidden-state definitions.
sensitivity = us.sensitivity_report(
rows_or_frame,
public=["AGE_BAND", "SEX"],
hidden=["AGE_BAND", "SEX", "OCC_MAJOR", "WKHP_BAND"],
target="income_over_threshold",
min_cell_weights=[1, 10, 25],
q_presets=["saturated", us.q_bounded_shift(0.5), "observed"],
)
See docs/transport-presets.md for guidance on Q presets and docs/representation-adequacy.md for interpretation rules.
4. Certify a Stable Public Segmentation
Use certify_public_representation(...) when you want a review-ready decision:
the smallest evaluated public bucket design that keeps ambiguity below a
declared threshold, with search assumptions and limitations attached.
certificate = us.certify_public_representation(
rows_or_frame,
base_public=["AGE_BAND", "EDU_BAND"],
hidden=["AGE_BAND", "EDU_BAND", "SEX", "OCC_MAJOR", "WKHP_BAND"],
target="income_over_threshold",
candidate_refinements=["SEX", "OCC_MAJOR", "WKHP_BAND"],
q_presets=["saturated", us.q_bounded_shift(0.5), "observed"],
min_cell_weights=[1, 10, 25],
ambiguity_limit=0.01,
bucket_budget=40,
search="exhaustive",
)
print(certificate.to_markdown())
Use public_representation_frontier(...) when you want the exploratory Pareto
frontier behind the certificate. The frontier compares public-cell count, added
public columns, and ambiguity across the stress grid. See
docs/public-representation-frontier.md.
5. Audit Causal or Uplift Reports
Use a causal inference library such as EconML,
DoWhy,
DoubleML, or
CausalML to estimate effects
first. Then pass row-level or subgroup-level effects into updatesupport.
suite = us.causal_reporting_stability(
df,
public=["AGE_BAND", "SEX"],
hidden=["AGE_BAND", "SEX", "OCC_MAJOR", "WKHP_BAND", "RAC1P"],
effect="tau_hat",
effect_standard_error="tau_se",
weight="sample_weight",
candidate_refinements=["OCC_MAJOR", "WKHP_BAND", "RAC1P"],
q=us.q_bounded_shift(0.5),
statistical_estimate=ate_hat,
statistical_interval=(ci_low, ci_high),
statistical_method="causal estimator bootstrap",
)
This keeps causal estimate, statistical uncertainty, hidden-composition ambiguity, estimator-uncertainty-aware adjusted ambiguity, and public refinement recommendations separate.
For causal/model-review stress tests based on hidden balance drift, use
q=us.q_covariate_balance(epsilon, moments) to bound
||standardized_hidden_moment_shift||_2.
See docs/causal-library-integration.md.
6. Financial Model-Risk Plugin
Financial model-risk use cases live in the separate plugin package:
pip install "updatesupport[finance]"
# or
uv add "updatesupport[finance]"
It adds finance-oriented metrics such as expected loss and default rate without cluttering the core package.
See packages/updatesupport-finance/README.md.
Install Extras
pip install "updatesupport[cvxpy]" # TV, chi-square, KL, Wasserstein, custom CVXPY Q
pip install "updatesupport[scip]" # CVXPY plus PySCIPOpt for solver="SCIP" and MIP presets
pip install "updatesupport[examples]" # Folktables, pandas, plotting examples
pip install "updatesupport[causal]" # causal-effect examples
pip install "updatesupport[dowhy]" # CausalRefutation conversion
pip install "updatesupport[finance]" # financial model-risk plugin package
The same extras work with uv add:
uv add "updatesupport[cvxpy]"
uv add "updatesupport[scip]"
uv add "updatesupport[examples]"
uv add "updatesupport[causal]"
uv add "updatesupport[dowhy]"
uv add "updatesupport[finance]"
The causal docs cover EconML, DoWhy, DoubleML, and CausalML handoffs.
For common examples and optional integrations:
pip install "updatesupport[examples,causal,dowhy,cvxpy,finance]"
# or
uv add "updatesupport[examples,causal,dowhy,cvxpy,finance]"
updatesupport[scip] is intentionally separate because solver wheels and
platform requirements are heavier than the default CVXPY workflow. It enables
SCIP-backed mixed-integer presets such as q_fiber_support_floor(...).
Examples
Run the no-download Folktables smoke demo from a source checkout:
uv run python examples/folktables_acs.py --synthetic
Run the real Folktables ACSIncome example:
uv run --extra examples python examples/folktables_acs.py \
--task income \
--states CA \
--year 2018 \
--sample 50000 \
--min-cell-weight 25
Generate the benchmark gallery:
uv run --extra examples --extra causal python examples/benchmark_gallery.py
Run the finance plugin example:
uv run --package updatesupport-finance python \
packages/updatesupport-finance/examples/model_risk_portfolio.py
See docs/benchmark-gallery.md for reproducible case studies.
Source Development
git clone https://github.com/nahuaque/updatesupport.git
cd updatesupport
uv sync --all-packages --group dev --extra cvxpy --extra examples
uv run pytest
Documentation
- Representation adequacy guide
- Audit specs
- Structured exports
- Data diagnostics
- Mathematical and statistical soundness
- Public representation frontier
- Representation stability certificates
- Transport preset guide
- Using
updatesupportwith causal inference libraries - Benchmark gallery
- Theory and backend reference
- Extension and plugin architecture
- Release guide
- Folktables ACSIncome result interpretation
- Update-Relevant Support: Hume's Missing Descent
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file updatesupport-0.1.2.tar.gz.
File metadata
- Download URL: updatesupport-0.1.2.tar.gz
- Upload date:
- Size: 431.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1082385b1d83f2a4b0680411697c81227fa766f674c278cb26897ab86a95fa8
|
|
| MD5 |
34ec0b0ee143643d794a78beb86010c1
|
|
| BLAKE2b-256 |
cce2d0750d50b0823e226b1728b94ba31afae1185492eebace3918cd2a896c54
|
File details
Details for the file updatesupport-0.1.2-py3-none-any.whl.
File metadata
- Download URL: updatesupport-0.1.2-py3-none-any.whl
- Upload date:
- Size: 144.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25e7f21e2a7b6768dd09b8cc05e4ea09c1207b2b18b35bc7928db8cdc7a9d65e
|
|
| MD5 |
64cfce9a4f82682e4ba30f879894dd4d
|
|
| BLAKE2b-256 |
ee90425e5c5d013ee6316a39e6306438d08eb87bf32f49981840af92893d978e
|