from _api_doc_utils import *BalancingWeights
Calibration weights for covariate balance
1 Where it fits
Group: Causal inference
BalancingWeights chooses weights for a source sample so weighted source covariate means match a target sample:
\[ \sum_i w_i x_i / \sum_i w_i \approx \sum_j q_j x_j / \sum_j q_j. \]
The objective can be quadratic or entropy-like, with optional lower/upper bounds and ridge stabilization.
2 Calibration system and numerical objective
Let \(\bar x_T\) be the optionally weighted target mean and define calibration rows
\[ z_i=(1,\ x_i-\bar x_T)'. \]
Weights are normalized implicitly by solving \(Z'w=e_1\). For entropy calibration, the dual map is
\[ w_i(\beta)= \begin{cases} \exp(z_i'\beta), & \text{without baseline weights},\\ q_i\exp(z_i'\beta-1), & \text{with normalized baseline }q_i, \end{cases} \]
clipped to the configured box when the bounded phase is needed. For quadratic calibration it is
\[ w_i(\beta)= \begin{cases} z_i'\beta, & \text{without baseline weights},\\ q_i(1+z_i'\beta), & \text{with baseline }q_i, \end{cases} \]
again clipped to the weight box. Thus the objective name selects the primal calibration geometry and corresponding dual weight map; it is not the literal function passed to the optimizer.
For allowed L2 imbalance \(\delta\), define
\[ s(\beta_{-0}) = \begin{cases} \delta\,\beta_{-0}/\|\beta_{-0}\|_2,&\|\beta_{-0}\|_2>0,\\ 0,&\text{otherwise}. \end{cases} \]
The numerical solver minimizes
\[ \frac12\|r(\beta)\|_2^2, \qquad r(\beta)=Z'w(\beta)-e_1+(0,s(\beta_{-0})')'. \]
Gauss-Newton uses the analytic residual Jacobian with a small \(10^{-8}\) normal-equation ridge and backtracking. Automatic mode tries Gauss-Newton first and falls back to BFGS only when needed. Entropy fits begin with relaxed bounds and rerun with clipping if the first solution violates the requested box.
3 Diagnostics and inference
There is no outcome model, treatment-effect estimand, standard error, or covariance calculation. The fitted object returns normalized weights, original-scale mean differences, effective sample size
\[ \mathrm{ESS}=\frac{1}{\sum_i w_i^2}, \]
the dual coefficient, and solver diagnostics. A successful fit must satisfy calibration residual tolerance, weight sum and bounds, and the L2 mean-difference tolerance.
With autoscaling, covariates are min-max scaled using source-sample minima and ranges before solving. The final success check also compares the unscaled mean-difference norm to the same L2 tolerance. Consequently, success can be false even when the scaled calibration system converged; the reported original-scale mean differences should be inspected directly.
4 Performance and numerical behavior
For \(n\) source rows and \(p\) covariates, each residual/Jacobian construction forms dense weighted cross-products with about \(O(np^2)\) work and \(O(np+p^2)\) storage; the Gauss-Newton system costs up to \(O(p^3)\). BFGS fallback adds repeated residual and Jacobian evaluations. Tight box constraints or an infeasible balance tolerance can leave a finite set of weights with success false rather than raising. Constant source columns are mapped to zero under autoscaling, and target values outside the source range are not clipped.
5 Python API
Constructor: cm.BalancingWeights
Use fit(covariates, target_covariates, baseline_weights=None, target_weights=None). get_weights() returns the fitted source weights. summary() reports balance diagnostics, the dual coefficients, effective sample size, and solver status.
print(inspect.signature(cm.BalancingWeights))(objective='quadratic', solver='auto', autoscale=False, min_weight=0.0, max_weight=1.0, l2_norm=0.0, max_iterations=200, tolerance=1e-08)
cls = cm.BalancingWeights
display(HTML(html_table(["Public method"], public_methods(cls))))| Public method |
|---|
fit(self, /, covariates, target_covariates, baseline_weights=None, target_weights=None) |
get_weights(self, /) |
summary(self, /) |
6 Minimal example
rng = np.random.default_rng(10)
x0 = rng.normal(size=(180, 3))
x1 = rng.normal(loc=np.array([0.4, -0.2, 0.1]), size=(80, 3))
model = cm.BalancingWeights(objective='quadratic', max_iterations=500)
model.fit(x0, x1)
print(model.summary()['mean_diff'])
print(model.summary()['effective_sample_size'])
print(model.get_weights()[:5])[1.11022302e-16 5.55111512e-17 1.17961196e-16]
155.1166258737379
[0.00415802 0.00646452 0.00614945 0.00537598 0.00450626]
7 summary() contract
The table below is generated by fitting the live class in this repository and then inspecting summary(). Shapes are shown because most values are plain NumPy arrays or scalars.
rng = np.random.default_rng(110)
x0 = rng.normal(size=(80, 3))
x1 = rng.normal(loc=np.array([0.4, -0.2, 0.1]), size=(40, 3))
model = cm.BalancingWeights()
model.fit(x0, x1)
summary = model.summary()
display(HTML(html_table(["summary() key", "shape"], summary_shape_rows(summary))))| summary() key | shape |
|---|---|
weights |
(80,) |
beta |
(4,) |
objective |
() |
solver |
() |
success |
() |
nit |
() |
criterion |
() |
residual_norm |
() |
weight_sum |
() |
weighted_mean |
(3,) |
target_mean |
(3,) |
mean_diff |
(3,) |
l2_diff |
() |
max_abs_diff |
() |
effective_sample_size |
() |
min_weight |
() |
max_weight |
() |
l2_norm |
() |