Skip to main content

tvbounds

Sensitivity analysis and bounds under total variation neighborhoods, the companion Python module to Palomba (2026), "Sensitivity Analysis in Population Shares." It is a function-by-function port of the R package of the same name: the same functions, the same options, the same manual (docs/tvbounds-manual.pdf), and the same numbers.

Installation

pip install tvbounds

Only numpy, pandas, and matplotlib are required at runtime. The structural-counterfactual solver additionally needs the juliacall package (pip install "tvbounds[julia]"), a Julia installation, and the Artelys KNITRO solver (see below).

KNITRO requirement for structural counterfactuals

tvbounds_counterfactual() solves its optimization problems in Julia through the commercial Artelys KNITRO solver, so it requires Julia (>= 1.9), the juliacall Python package, and a valid KNITRO license. Students can request a free one-year KNITRO license through Artelys' academic program on the same page. The package checks for KNITRO once per Python session, on the first call. The Julia solver module is the one shipped with the R package, byte for byte.

The Julia dependencies (KNITRO.jl, ForwardDiff, Optim, ...) are declared in tvbounds/juliapkg.json and installed by juliacall into its own Julia environment on first use. KNITRO.jl downloads the KNITRO library through KNITRO_jll, whose newest release may be a KNITRO version that your license does not cover (KNITRO licenses are bound to a release date; a license for KNITRO 15.1 rejects KNITRO 16 with error -520). In that case pin the release your license covers before the first call, for example

python -m juliapkg add KNITRO_jll --uuid 0e6b36f8-8e90-4eb5-b54e-06f667ea875c --version "=15.1.0"

or point KNITRO.jl at a local installation through the KNITRODIR environment variable, as described in the KNITRO.jl documentation.

Quick tour

The commands below reproduce, in brief, what the manual develops in detail.

Randomized experiments with attrition

import numpy as np
import pandas as pd
from tvbounds import tvbounds_attrition, tvbounds_summary

## a small experiment with village-level assignment and selective attrition
rng = np.random.default_rng(20260820)
n = 500
village = np.repeat(np.arange(1, 51), 10)
d = rng.binomial(1, 0.5, 50)[village - 1]
x = rng.binomial(1, 0.4, n)
ability = rng.normal(size=n)
s = (rng.uniform(size=n) < 1 / (1 + np.exp(-(0.2 + 1.2 * d + 0.5 * ability)))).astype(int)
y = np.where(s == 1, 1 + 0.35 * d + 0.5 * x + ability + 0.5 * rng.normal(size=n), np.nan)
rct = pd.DataFrame({"y": y, "d": d, "s": s, "x": x, "village": village})

## total variation bounds with a bootstrap confidence band
fit_tv = tvbounds_attrition(rct,
    outcome="y", treatment="d", response="s",
    delta=np.linspace(0, 1, 21), B=200, seed=1)
print(fit_tv)
fit_tv.plot()

## contamination neighborhood: weakly tighter bounds, same endpoints
fit_ct = tvbounds_attrition(rct,
    outcome="y", treatment="d", response="s",
    delta=np.linspace(0, 1, 21), neighborhood="contamination",
    bootstrap=False)

## cluster bootstrap and covariate-pooled bounds
fit_cl = tvbounds_attrition(rct,
    outcome="y", treatment="d", response="s",
    delta=np.linspace(0, 1, 21), B=200, cluster="village", seed=1)
fit_x = tvbounds_attrition(rct,
    outcome="y", treatment="d", response="s", covariates="x",
    delta=np.linspace(0, 1, 21), B=200, seed=1)

## summary measures: breakdown budgets, shadow price, robustness
## standard error, certification frontier
print(fit_tv.summary())
print(tvbounds_summary(fit_tv, delta=0.1))

Recentered instrumental variables

import numpy as np
from tvbounds import tvbounds_riv

## a shift-share design: exposure shares W, S counterfactual shock draws
rng = np.random.default_rng(1901)
n, K, S = 150, 10, 80
W = rng.exponential(size=(n, K)) ** 2
W = W / W.sum(axis=1, keepdims=True)
g0 = rng.normal(loc=0.3, size=K)
G = rng.normal(size=(K, S))
z = W @ g0                                 # realized formula instrument
Fmat = W @ G                               # n x S counterfactual draws
x = z + rng.normal(size=n)
y = 0.5 * x + 0.4 * (W @ rng.normal(size=K)) + 0.5 * rng.normal(size=n)

## bounds under both neighborhoods (deterministic; no inference by design)
riv_tv = tvbounds_riv(y, x, z, Fmat, delta=np.linspace(0, 1, 101))
riv_ct = tvbounds_riv(y, x, z, Fmat, delta=np.linspace(0, 1, 101),
                      neighborhood="contamination")
print(riv_tv)
riv_tv.plot()

## first-stage breakdown budget and its censoring flag
riv_tv.details["delta_fs"], riv_tv.details["delta_fs_censored"]

Counterfactuals in structural models (requires KNITRO)

import tvbounds
from tvbounds import tvbounds_counterfactual, tvbounds_control

## a toy model shipped with the package: moments in Julia, Bundle signature
toy = tvbounds.julia_file("examples", "toy.jl")

fit_cf = tvbounds_counterfactual(
    moments    = (toy, "tvb_toy_moments!"),   # Julia file + function name
    d          = 1,                           # number of moment conditions
    theta_lb   = 0.4, theta_ub = 0.6,         # box for the structural parameter
    delta      = [0.05, 0.1, 0.25, 0.5, 1],   # budgets (strictly positive)
    divergence = "TVmix",
    side       = "both",
    M          = 5000, u_dim = 1,             # scrambled-Halton draws
    theta_init = 0.5,
    seed       = 1234,
    control    = tvbounds_control(maxsolves = 5))

fit_cf.bounds
fit_cf.plot()

Agreement with the R package

Seeded bootstraps use an R-compatible random-number stream, so every number a seeded call returns (bounds, standard errors, percentile bands, breakdown budgets, summary measures) reproduces the R package's output at machine precision. The validation/ folder of the source repository holds the cross-checks against the R package and against the empirical results of the paper. Two platform notes: the last-bit agreement relies on the arithmetic of R's compiled kernels (fused multiply-adds on Apple Silicon builds; other R builds may differ in the last unit in place of a standard error or a breakdown budget), and covariate cells are ordered as R orders factor levels in an ICU (en_US) locale, so string-valued covariates under a C locale in R may list the strata in a different order (the bounds themselves are unaffected beyond the last bit).

Citation

If you use tvbounds, please cite:

Palomba, F. (2026). "Sensitivity Analysis in Population Shares." Working paper.

License

MIT © Filippo Palomba. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tvbounds-0.1.1.tar.gz (597.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tvbounds-0.1.1-py3-none-any.whl (126.3 kB view details)

Uploaded Python 3

File details

Details for the file tvbounds-0.1.1.tar.gz.

File metadata

  • Download URL: tvbounds-0.1.1.tar.gz
  • Upload date:
  • Size: 597.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.17

File hashes

Hashes for tvbounds-0.1.1.tar.gz
Algorithm Hash digest
SHA256 16fbff1242d1f6d0e43fadbd0a9ace6ebd44c230b34d25c3aaaf2b457bbaf1d4
MD5 af545f05c9231fd6826f196435e94237
BLAKE2b-256 d48d5c9a9ad97a7f85f22c3aa3ea5cf137a355c017120dad3ff5a728aaaad1bf

See more details on using hashes here.

File details

Details for the file tvbounds-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: tvbounds-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 126.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.17

File hashes

Hashes for tvbounds-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d6ff3fbc822d811d1403d8c9284c1f5864a7ac411818cdb6e7d2321d3ca47b15
MD5 1480255ee01607164af7c6004fe945a0
BLAKE2b-256 f195d3c6dc243458be693f532b351f2cf07023a24f1a0940cb675a541629a1a0

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.2

2 files

This release

0.1.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page