Releases: facebookresearch/balance
Release list
0.21.0 (2026-06-02)
New Features
balance.stats_and_plots.love_plot.love_plot,BalanceDFCovars.love_plot(),
andBalanceDFCovars.plot(dist_type="love_plot")— visual
covariate-imbalance diagnostic in the spirit of R'scobalt::love.plot.
Supports interactive Plotly figures (the new default), static
seaborn/matplotlib axes (library="seaborn"), and LLM-friendly ASCII
(library="balance"). With both series it draws the canonical
before-vs-after scatter; with onlybefore, a single-series scatter with
optional threshold reference line. The ASCII backend renders both series on
a shared axis witho/*markers, wider defaultbar_width=50, and
direction legends. New options includeline=(toggle connectors) and
order_by={"diff", "before", "after", "alphabetical", "none"}; the default
order_by="diff"surfaces regressions at the top.
BalanceDFCovars.love_plot(metric=...)dispatches acrossmetric∈
{"asmd"(default),"kld","emd","cvmd","ks"}, with the
threshold default resolving to the cobalt 0.1 cutoff for ASMD only.
.plot(dist_type="love_plot", library=...)(or the"love"alias) routes
covariate views to the same diagnostic.- Rake now supports fit-time metadata persistence and
predict_weights()
reconstruction.rake(..., store_fit_metadata=True)stores contingency-table artifacts
and fit-time metadata required to rebuild weights later.BalanceFrame.fit(method="rake")enablesstore_fit_metadata=Trueby
default so fitted rake models can be reused with
BalanceFrame.predict_weights()without refitting.- In-place replay (
predict_weights()with nodata=) works with any
transformations, including the rake defaulttransformations="default". - Transfer scoring (
predict_weights(data=...)) requires deterministic
transformations: raisesValueErrorfor models fitted with
transformations="default"and for explicit dicts that directly
reference data-dependent helpers (quantize,fct_lump). Pass
deterministic transformations at fit time or re-fit on the scoring data.
- Poststratify now supports transfer scoring with
predict_weights(data=...).
BalanceFrame.fit(method="poststratify", store_fit_metadata=True)stores
the transformation origin needed to safely replay fitted cell ratios on a
new sample/target pair:predict_weights(data=holdout_bf)applies stored
ratios to the holdout sample's design weights and rescales to the holdout
target's total weight. Same restriction as rake transfer scoring: rejects
transformations="default"and directquantize/fct_lumpreferences.
Pre-0.21.0 pickles lacktransformations_originand must be re-fit for
transfer scoring; in-placepredict_weights()continues to work. balance.interop.diff_diff— thin adapter to
diff-diff (>=3.3.0,<4) for
survey-weighted Difference-in-Differences. Providesto_survey_design(),
to_panel_for_did(),fit_did(), andas_balance_diagnostic(). Install
viapip install "balance[did]". The submodule is lazy-imported, so
import balancestill works cleanly when diff-diff isn't installed — the
import guard rewrites theImportErrorto point users at thebalance[did]
extra. Shared adapter helpers (active_weight_column,
drop_history_columns,validate_row_count,attach_balance_provenance)
live inbalance/interop/_common.pyand column-name conventions in
balance/interop/conventions.pyso a futurebalance.interop.svyadapter
can reuse them.balance.stats_and_plots.weights_stats.kish_deff_stats— bundled
Kish-design-effect diagnostic returning aKishStats(deff, ess, essp)
namedtuple. Computesdesign_effectonce and derives ESS and ESSP from it,
avoiding three separate Deff computations when all three are needed.
kish_ess(w)andkish_essp(w)are also exposed as ergonomic singletons
over the existingdesign_effect.BalanceFrame._design_effect_diagnostics
now routes throughkish_deff_statsso the canonical Kish identities live
in one place.BalanceFrame.adjustment_historyrecords compound adjustment steps.
Sequentialadjust()/set_fitted_model()workflows now keep a
chronological, best-effort read-only copy of each adjustment step while
preservingmodelas the latest fitted model for backwards compatibility.
Baseline resets such asset_as_pre_adjust()clear the history together
with the current model.- CLI
--formulanow accepts JSON lists for model-matrix formula lists.
In addition to a single formula string, CLI users can pass values such as
--formula='["age", "gender"]', which are parsed and forwarded as
list[str]to IPW/CBPS model-matrix construction. Malformed, empty, or
non-string JSON lists now fail during argument parsing.
Bug Fixes
rake()now correctly incorporates per-row design weights in final
weights. Previously, every unit in the same raking cell received the same
weightm_fit[c] / m_sample[c], ignoring its own design weight. The correct
formulaw_final_i = w_design_i × m_fit[c] / m_sample[c]is now applied,
matchingpoststratifysemantics and ensuring weighted marginals recover
the target distribution when design weights are non-uniform. No-op when
design weights are uniform (the common case).rake()now gracefully handles single-variable adjustments. When
rake(...)resolves to exactly one adjustment variable, it logs a warning
and delegates topoststratify(...)instead of raising an assertion. This
preserves passthrough behaviour for transformations, NA handling, trimming
controls, and fit-metadata persistence while making
BalanceFrame.fit(method="rake")more robust for one-variable inputs. In
this delegated path, model metadata recordsmethod='poststratify'while
returned weights keep the canonicalrake_weightname.- CLI
--num_lambdasnow parses as a positive integer. Fractional,
zero, negative, and non-numeric values fail fast during argument parsing
instead of being accepted after coercion/truncation or failing later
during IPW adjustment. - Validation-path cleanup in
asmd,poststratify, andrakeremoves
redundant/unreachable branches with no behavior loss:asmd(...)uses a single authoritative invalid-std_typeerror path
(Unknown std_type ...).poststratify(..., store_fit_metadata=True)drops an unreachable
"missing stored training weights" guard, and predict-time ratio-column
collisions now use deterministic suffix-based naming (_cell_ratio,
_cell_ratio_tmp,_cell_ratio_tmp2, ...).rake._predict_weights_from_model(...)uses already-validated fit-time
target weights directly for non-transfer replay.
- Security:
wsupdated from 8.20.0 to 8.20.1 in website dependencies.
Fixes CVE-2026-45736 (GHSA-58qx-3vcg-4xpx): uninitialized memory disclosure
inwebsocket.close()when aTypedArrayis passed as the reason argument.
Documentation
- README cross-link to diff-diff. New "Design-based inference" parent
section in
README.md
introduces the diff-diff integration above the API tour with a canonical
Sample.from_frame→set_target→adjust→fit_didsnippet. The
Docusaurus tutorials index and the website landing page
(HomepageFeatures.js) gain matching cross-references;
.github/copilot-instructions.mdgets a new review-checklist bullet for
changes that touchbalance/interop/diff_diff.py. - Survey-weighted DiD tutorial. New
tutorials/balance_diff_diff_brfss.ipynbwalks through a BRFSS-style
staggered-adoption smoking-ban DiD use case end-to-end: load synthetic
survey microdata viadd.generate_survey_did_data, reweight to ACS
demographic marginals viabalance.ipw, aggregate to a state-quarter panel
viato_panel_for_did, fit Callaway-Sant'Anna doubly-robust DiD via
fit_did, run HonestDiD sensitivity, build a combined diagnostic via
as_balance_diagnostic, and contrast with the unweighted estimate.
Self-contained and deterministic — CI re-executes via nbconvert. Committed
with cleared outputs;deploy-website.ymlre-runs and bakes outputs into
the rendered Docusaurus pages.
Code Quality & Refactoring
- All-zero weight inputs to
_check_weights_series_are_validnow emit a
UserWarning(whenrequire_positive=False, the default). Previously,
weighted statistics over an all-zero weight vector silently producedNaN/
inf(sum(w*x)/sum(w) = 0/0). Callers that already passed
require_positive=True(e.g.design_effect,nonparametric_skew,
prop_above_and_below,weighted_median_breakdown_point) keep their
ValueErrorbehaviour. This affects internal callers like
descriptive_stats→asmd, which previously masked the failure mode. - Removed the scheduled migration
FutureWarnings fromSampleFrame.weight_column,
SampleFrame.id_column, andBalanceFrame.id_column; the accessors continue to return
column names, whileweight_seriesandid_seriesreturn data. - Diagnostics construction now wires
adjustment_failuremetadata from model
outputs when available (instead of hardcoding success), and supports an
optionaladjustment_failure_reasondiagnostics row for richer failure
reporting in downstream tooling. - Plotly distribution plotting now gracefully handles missing notebook mime
rendering dependencies (nbformat) by skipping the interactive plot with a
warning, avoiding runtime crashes in non-notebook/test environments.
Tests
- Expanded targeted test coverage for predict-time metadata validation,
replay/transfer edge cases...
0.20.0 (2026-04-26)
Breaking Changes
-
id_columnnow returns the column name (str) onSampleFrame,
BalanceFrame, andSample. Previously it returned ID data
(pd.Series), which was inconsistent withweight_column(which returned
a name after 0.19.0). Useid_seriesfor ID data,weight_seriesfor
weight data. The accessor naming convention is now consistent:*_column→ column name (str):id_column,weight_column*_series→ column data (pd.Series):id_series,weight_seriesdf_*→ DataFrame:df_covars,df_weights,df_outcomes
Both
id_columnandweight_columnemitFutureWarnings pointing at the
new data-returning accessors; warnings will be removed after 2026-06-01.
TheBalanceDFSourceprotocol was updated accordingly (id_column→
id_series); custom implementations must rename this property. -
Unknown kwargs to
poststratify(...)now raiseTypeErrorinstead of
being silently ignored, to catch typos.store_fit_metadatamust be a
boolean.
New Features
-
Added sklearn-style
fit/predict_weightsworkflow onBalanceFrame- New entry points
BalanceFrame.fit(...),design_matrix(on=..., data=...),
predict_proba(on=..., output=..., data=...), andpredict_weights(data=...)
enable training a weighting model on one BalanceFrame and applying it to new
data viadata=...for one-liner holdout scoring
(fitted.predict_weights(data=holdout_bf)). - Supports IPW, CBPS, and poststratify methods. Each stores the fit-time
metadata needed to reconstruct weights (training design weights, trimming
options, CBPS coefficients, poststratify cell-ratio tables, NA handling). BalanceFrame.set_fitted_model(fitted)applies a fitted model from one
BalanceFrame to another for holdout scoring workflows.
- New entry points
-
Added formula support in
poststratifypoststratify(...)andBalanceFrame.adjust(method="poststratify", ...)
now acceptformula=(string or list) as an alternative tovariables=.- Only interaction-style operators are supported:
:(interaction),
.(all common columns),-(exclude), and optional leading~.
Additive+and*are explicitly rejected because post-stratification
defines cells by the joint distribution —a + b,a * b, anda:b
would all produce identical cells, and rejecting+/*prevents users
from silently writing what looks like a main-effects model. - Strict formula validation: empty/non-string entries, unknown variables,
transformed terms, and passing bothvariablesandformulaall raise
explicitValueError. Note: raking operates on marginals, so+/*
will be meaningful when raking gains its ownformula=argument.
-
Added
BalanceFrame.set_as_pre_adjust()to lock in the current
responder state as the new pre-adjust baseline. Supportsinplace=False
(default, immutable) andinplace=True. Clears the adjustment model and
unadjusted link since the object is no longer considered adjusted.
Code Quality & Refactoring
-
Safer target replacement on adjusted objects —
BalanceFrame.set_target(...)
now warns when replacing the target on an adjusted object in-place, since
this resets responder weights to pre-adjust values and drops the current
adjustment result. -
Modernized weight dtype checks for pandas 3.0 compatibility —
SampleFrame.set_weights()paths now use explicit exact-float64checks
instead of deprecated float-dtype helpers, and always coerce assigned
weights to exactfloat64. -
Cleaned up legacy Python 2
__future__compatibility imports in
weighting methods — replaced obsolete imports with
from __future__ import annotationsinadjust_null,cbps,
poststratify, andrake. Note: with this future import enabled,
runtime annotation introspection changes (__annotations__become
postponed/stringized).
Tests
- Added coverage for the new fit/predict_weights workflow (IPW, CBPS,
poststratify), pickle/deepcopy roundtrips of fitted BalanceFrames,
raw-covariate fit-matrix persistence (use_model_matrix=False),
near-separation stability, empty-input validation, formula parsing edge
cases (interaction syntax, dot expansion, explicit~formulas,
validation failures),set_as_pre_adjust()behavior (copy / in-place /
inherited-view sync), target-replacement warning emission, weight-column
casting when active weight dtype is non-float/float32, and chained
IPW→poststratify adjustment behavior.
Contributors
@talgalili, @sahil350 ,@neuralsorcerer
Full Changelog
0.19.0 (2026-04-06)
Highlights
This is a major architecture release. balance now has two new foundational
classes — SampleFrame and BalanceFrame — that cleanly separate data
representation from adjustment logic. The existing Sample API is fully backward
compatible; Sample now inherits from both new classes
(Sample → BalanceFrame → SampleFrame) and all existing code continues to work
unchanged. The new classes can also be used directly for a more explicit,
composable workflow — see the new tutorial notebook for a complete walkthrough.
For full technical details — including ASCII diagrams of the class hierarchy,
internal structure, column classification, object lifecycle, linked-samples
expansion, data flow, and the Sample.__new__ guard — see
docs/architecture/architecture_0_19_0.md.
Breaking Changes
- Removed
Sample.design_effect()— usesample.weights().design_effect()instead.
Deprecated since 0.18.0. - Removed
Sample.design_effect_prop()— usesample.weights().design_effect_prop()instead.
Deprecated since 0.18.0. - Removed
Sample.plot_weight_density()— usesample.weights().plot()instead.
Deprecated since 0.18.0. - Removed
Sample.covar_means()— usesample.covars().mean()instead
(with.rename(index={'self': 'adjusted'}).reindex(['unadjusted', 'adjusted', 'target']).Tfor the same format).
Deprecated since 0.18.0. - Removed
Sample.outcome_sd_prop()— usesample.outcomes().outcome_sd_prop()instead.
Deprecated since 0.18.0. - Removed
Sample.outcome_variance_ratio()— usesample.outcomes().outcome_variance_ratio()instead.
Deprecated since 0.18.0.
New Features
-
Added
SampleFrame— DataFrame container with explicit column-role metadata
(sample_frame.py). Holds a DataFrame and tracks which columns are covariates,
weights, outcomes, predicted outcomes, and ignored.- Factory methods:
from_frame()(with explicitcovar_columnsparameter
and auto-detection of id/weight columns) andfrom_csv(). - DataFrame view properties:
df_covars,df_outcomes,df_weights,
df_ignored,id_column,weight_series. - Column-role list properties:
covar_columns,weight_columns_all,
outcome_columns,predicted_outcome_columns,ignored_columns. - Weight management:
add_weight_column(),set_active_weight(),
rename_weight_column(),set_weight_metadata()/weight_metadata()for
provenance tracking. - Comprehensive input validation (null/negative/non-numeric weights, null IDs,
duplicate IDs, overlapping column roles). - See
docs/architecture/architecture_0_19_0.md
for column classification rules, auto-detection logic, and internal structure.
- Factory methods:
-
Added
BalanceFrame— adjustment orchestrator for survey weighting
(balance_frame.py). Pairs a responderSampleFramewith a targetSampleFrame
for reweighting.- Public constructor:
BalanceFrame(sample=..., target=...). Target is optional —
BalanceFrame(sample=sf)creates a target-less instance. - Core API:
set_target(),adjust(),covars()/weights()/outcomes()
(BalanceDF views),summary(),diagnostics(). adjust(method="ipw")returns a new BalanceFrame (immutable pattern) with
adjusted weights. Supports"ipw","cbps","rake","poststratify",
"null", and custom callables.- Convenience properties:
df(responder-only DataFrame, mirrorsSample.df),
df_all(combined responder + target + unadjusted with"source"column),
has_target,is_adjusted,model. - Covariate overlap validation at construction and
set_target(). to_csv(),to_download(),keep_only_some_rows_columns()for export
and filtering.- See
docs/architecture/architecture_0_19_0.md
for property delegation, adjustment flow, and linked-samples expansion.
- Public constructor:
-
Compound/sequential adjustments —
adjust()can now be called multiple
times on the same object. Each call uses the current (previously adjusted)
weights as design weights, compounding adjustments. For example, run IPW first
to correct broad imbalances, then rake on a specific variable for fine-tuning.
The active weight column always keeps its original name (e.g.,"weight");
the original unadjusted baseline is always preserved for diagnostics
(asmd_improvement()shows total improvement across all steps). See
docs/architecture/architecture_0_19_0.md
for the weight history tracking mechanism (weight_pre_adjust,
weight_adjusted_Ncolumns). -
Samplerefactored to inherit fromSampleFrameandBalanceFrame—
Sampleis now a thin facade (~242 lines) via multiple inheritance
(Sample → BalanceFrame → SampleFrame). All adjustment, diagnostics, and
data-access logic lives in the base classes. No public API changes — all
existingSamplemethods continue to work identically. -
Sample.is_adjustedis now a@propertyreturning_CallableBool— works
both assample.is_adjusted(property) andsample.is_adjusted()(legacy
method call)._CallableBoolalso supports arithmetic via__mul__/__rmul__. -
Added bidirectional conversion between Sample, SampleFrame, and BalanceFrame
SampleFrame.from_sample(sample)/Sample.to_sample_frame(): convert a
Sample to a SampleFrame with proper column-role mapping.BalanceFrame.from_sample(sample)/Sample.to_balance_frame(): convert a
Sample (with target) to a BalanceFrame, preserving adjustment state.BalanceFrame.to_sample(): convert a BalanceFrame back to a Sample.
-
Added formula support to
Sample.covars()for downstream diagnosticsSample.covars()now accepts aformulaargument and stores it on the
returnedBalanceDFCovarsobject.BalanceDFCovars.kld()now honors formula-driven model matrices (including
interactions such as"age_group * gender") when a formula is provided via
covars(formula=...).- Formula settings are now propagated to linked covariate views (
target,
unadjusted) so comparative diagnostics run on consistent design matrices.
Code Quality & Refactoring
-
Defined
BalanceDFSourceprotocol and decoupledBalanceDFfromSample
— AddedBalanceDFSourceruntime-checkable protocol that bothSampleand
SampleFramesatisfy, enablingBalanceDFto work with either. Removed the
hardfrom balance.sample_class import Sampleimport frombalancedf_class.py.
Seedocs/architecture/architecture_0_19_0.md
for protocol members and satisfaction diagram. -
Extracted
_build_summary()and_build_diagnostics()intosummary_utils.py
— Standalone functions accepting plain DataFrames/Series, enabling code reuse
acrossSampleandBalanceFramewithout circular imports. -
BalanceDF.__init__(): added optionallinksparameter for explicit link
injection, allowing BalanceDF to work with sources that do not carry mutable
_links(e.g.SampleFrame). -
Typing modernization —
Dict→dict,Tuple→tuple,List→list
in annotations.cast()replaced with_assert_type()where applicable.
Internal naming standardized acrossBalanceDFhelpers (snake_case convention).
Tutorials
- Added
balance_quickstart_new_api.ipynb— end-to-end tutorial demonstrating the
new SampleFrame/BalanceFrame API. Mirrors the originalbalance_quickstart.ipynb
step-by-step but uses only the new classes (noSample). Covers: loading data,
creating SampleFrames, building a BalanceFrame, adjusting (IPW + CBPS), inspecting
diagnostics (summary, ASMD, covariate means, design effect), visualization
(plotly, seaborn KDE, ASCII plots), outcome analysis, transformations, compound
adjustments, filtering rows/columns, and exporting to CSV.
Documentation
- Added
ARCHITECTURE.md— new top-level architecture document covering the class hierarchy,
5-step workflow, key classes, weighting methods, supporting modules, and file layout.
CLAUDE.mdnow links to this file instead of duplicating architecture content. - Added
docs/architecture/architecture_0_19_0.md— detailed technical record of the 0.19.0
architecture with ASCII diagrams covering: class hierarchy before/after, SampleFrame internals,
BalanceFrame internal structure, object lifecycle state transitions, BalanceDF linked-samples
expansion, BalanceDFSource protocol, BalanceDF class hierarchy, data flow, _links graph, and
Sample.new guard. - Updated
README.md— added "Developer and AI assistant resources" section linking to
ARCHITECTURE.mdandCLAUDE.md.
LLM/GenAI
- Updated
CLAUDE.mdproject context files for Claude Code users, covering architecture,
build/test instructions (Meta and open-source), code conventions, and pre-submit checklist. - Updated
.github/copilot-instructions.mdreview checklist to add missing conventions
(MIT license header,from __future__ import annotations, factory pattern, seed fixing,
deprecation style).
Tests
- Added comprehensive test suites for the new classes:
test_balance_frame.py: ~120 tests covering construction, adjustment (all
methods), covars/weights/outcomes integration, summary/diagnostics, analytics,
df/export/filter, missing dat...
0.18.0 (2026-03-24)
New Features
- Implemented
r_indicator()with validated sample-variance formula- Added a public
r_indicator(sample_p, target_p)implementation in
weighted_comparisons_statsusing the documented Eq. 2.2.2 formulation
over concatenated propensity vectors and explicit input-size validation. - Added validation for non-finite and out-of-range propensity values,
and expanded unit coverage for formula correctness and edge cases. - Added
BalanceDFWeights.r_indicator()as a convenience wrapper, so
sample.weights().r_indicator()computes the r-indicator directly.
- Added a public
Deprecations
Sample.design_effect()is deprecated — usesample.weights().design_effect()instead.
The method already exists onBalanceDFWeights; theSamplemethod now emits a
DeprecationWarningand delegates. Will be removed in balance 0.19.0.Sample.design_effect_prop()is deprecated — usesample.weights().design_effect_prop()instead.
New method added toBalanceDFWeights. Will be removed in balance 0.19.0.Sample.plot_weight_density()is deprecated — usesample.weights().plot()instead.
Will be removed in balance 0.19.0.Sample.covar_means()is deprecated — usesample.covars().mean()instead
(with.rename(index={'self': 'adjusted'}).reindex([...]).Tfor the same format).
Will be removed in balance 0.19.0.Sample.outcome_sd_prop()is deprecated — usesample.outcomes().outcome_sd_prop()instead.
New method added toBalanceDFOutcomes. Will be removed in balance 0.19.0.Sample.outcome_variance_ratio()is deprecated — usesample.outcomes().outcome_variance_ratio()instead.
New method added toBalanceDFOutcomes. Will be removed in balance 0.19.0.
LLM/GenAI
- Added
CLAUDE.mdproject context files for Claude Code users, covering architecture,
build/test instructions (Meta and open-source), code conventions, and pre-submit checklist. - Updated
.github/copilot-instructions.mdreview checklist to reduce duplication with
CLAUDE.mdand add missing conventions (MIT license header,from __future__ import annotations,
factory pattern, seed fixing, deprecation style).
Bug Fixes
prepare_marginal_dist_for_raking/_realize_dicts_of_proportions: fixed memory explosion from LCM expansion- When proportions had high decimal precision or many covariates were passed,
the LCM of the individual per-variable array lengths could reach tens of
millions (or more), causing OOM crashes. - Both functions now accept a
max_lengthparameter (default10000). When
the natural LCM exceedsmax_length, the output is capped atmax_length
rows and counts are allocated via the Hare-Niemeyer (largest remainder)
method, which guarantees the total stays exactlymax_lengthwith minimal
rounding error per category. - A warning is logged whenever the cap is applied.
- A new internal helper
_hare_niemeyer_allocationimplements the allocation logic.
- When proportions had high decimal precision or many covariates were passed,
Contributors
Full Changelog: 0.17.0...0.18.0
0.17.0 (2026-03-17)
Breaking Changes
- CLI: unmentioned columns now go to
ignore_columnsinstead ofoutcome_columns- Previously, when
--outcome_columnswas not explicitly set, all columns that
were not the id, weight, or a covariate were automatically classified as
outcome columns. Now those columns are placed intoignore_columnsinstead. - Columns that are explicitly mentioned — the id column, weight column,
covariate columns, and outcome columns — are not ignored.
- Previously, when
New Features
- ASCII comparative histogram and plot improvements
- Added
ascii_comparative_histfor comparing multiple distributions against a
baseline using inline visual indicators (█,▒,▐,░). - Comparative ASCII plots now order datasets as population → adjusted → sample.
ascii_plot_distaccepts a newcomparativekeyword (defaultTrue) to
toggle between comparative and grouped-bar histograms for numeric variables.
- Added
Code Quality & Refactoring
- Moved dataset loading implementations out of
balance.datasets.__init__- Refactored
load_sim_data,load_cbps_data, andload_datainto
balance.datasets.loading_dataand re-exported them from
balance.datasetsto preserve the public API while keeping module
responsibilities focused.
- Refactored
Documentation
- ASCII plot documentation and tutorial examples
- Added rendered text-plot examples to ASCII plot docstrings and documented
library="balance"support. Updatedbalance_quickstart.ipynbwith
adjusted vs unadjusted ASCII plot examples.
- Added rendered text-plot examples to ASCII plot docstrings and documented
- Improved
keep_columnsdocumentation- Updated docstrings for
has_keep_columns(),keep_columns(), and the
--keep_columnsargument to clarify that keep columns control which columns
appear in the final output CSV. Keep columns that are not id, weight,
covariate, or outcome columns will be placed intoignore_columnsduring
processing but are still retained and available in the output.
- Updated docstrings for
- Clarified
_prepare_input_model_matrixargument docs- Updated docstrings in
balance.utils.model_matrixwith
explicit descriptions forsample,target,variables, andadd_na
behavior when preparing model-matrix inputs.
- Updated docstrings in
Bug Fixes
- Weight diagnostics now consistently accept DataFrame inputs
design_effect,nonparametric_skew,prop_above_and_below, and
weighted_median_breakdown_pointnow explicitly normalize DataFrame inputs
to their first column before computation, matching validation behavior and
returning scalar/Series outputs consistently.
- Model-matrix robustness improvements
_make_df_column_names_unique()now avoids suffix collisions when columns
likea,a_1, and repeatedanames appear together, renaming
duplicates deterministically to prevent downstream clashes._prepare_input_model_matrix()now raises a deterministicValueError
when the input sample has zero rows, instead of relying on an assertion.
- Stabilized
prop_above_and_below()return pathsprop_above_and_below()now builds concatenated outputs only from present
Series objects and returnsNonewhen bothbelowandaboveareNone,
avoiding ambiguous concat inputs while preserving existing behavior for valid
threshold sets.
- Validated and normalized comma-separated CLI column arguments
- CLI column-list arguments now trim surrounding whitespace and reject empty
entries (for example,"id,,weight") with clearValueErrormessages,
preventing malformed column specifications from silently propagating. - Applied to
--covariate_columns,--covariate_columns_for_diagnostics,
--batch_columns,--keep_columns, and--outcome_columnsparsing.
- CLI column-list arguments now trim surrounding whitespace and reject empty
Tests
- Added end-to-end adjustment test with ASCII plot output and expanded ASCII plot edge-case coverage
TestAsciiPlotsAdjustmentEndToEndruns the full adjustment pipeline and
asserts exact expected ASCII output. Added tests forascii_plot_distwith
comparative=Falseand mixed categorical+numeric routing.
- Expanded warning coverage for
Sample.from_frame()ID inference- Added assertions that validate all three expected warnings are emitted when inferring an
idcolumn and default weights, including ID guessing, ID string casting, and automatic weight creation.
- Added assertions that validate all three expected warnings are emitted when inferring an
- Expanded IPW helper and diagnostics test coverage
- Added tests for
link_transform()andcalc_dev()to validate behavior
for extreme probabilities and finite 10-fold deviance summaries. - Refactored diagnostics tests to use a shared IPW setup helper, added
edge-case assertions for solver/penalty values, NaN coercion of non-scalar
inputs, and now assert labels match fitted model parameters.
- Added tests for
- Expanded
prop_above_and_below()edge-case coverage- Added focused tests for empty threshold iterables, mixed
Nonethreshold groups in dict mode, and explicit all-Nonethreshold handling across return formats.
- Added focused tests for empty threshold iterables, mixed
- Added unit coverage for CLI I/O and empty-batch handling
- Added focused tests for
BalanceCLI.process_batch()empty-sample failure payloads,load_and_check_input()CSV loading paths, andwrite_outputs()delimiter-aware output writing for both adjusted and diagnostics files.
- Added focused tests for
Contributors
@sahil350 , @neuralsorcerer, @talgalili
Full Changelog
0.16.0 (2026-02-09)
New Features
- Outcome weight impact diagnostics
- Added paired outcome-weight impact tests (
y*w0vsy*w1) with confidence intervals. - Exposed in
BalanceDFOutcomes,Sample.diagnostics(), and the CLI via
--weights_impact_on_outcome_method.
- Added paired outcome-weight impact tests (
- Pandas 3 support
- Updated compatibility and tests for pandas 3.x
- Categorical distribution metrics without one-hot encoding
- KLD/EMD/CVMD/KS on
BalanceDF.covars()now operate on raw categorical variables
(with NA indicators) instead of one-hot encoded columns.
- KLD/EMD/CVMD/KS on
- Misc
- Raw-covariate adjustment for custom models
Sample.adjust()now supports fitting models on raw covariates (without a model matrix)
for IPW viause_model_matrix=False. String, object, and boolean columns are converted
to pandasCategoricaldtype, allowing sklearn estimators with native categorical
support (e.g.,HistGradientBoostingClassifierwithcategorical_features="from_dtype")
to handle them correctly. Requires scikit-learn >= 1.4 when categorical columns are
present.
- Validate weights include positive values
- Added a guard in weight diagnostics to error when all weights are zero.
- Support configurable ID column candidates
Sample.from_frame()andguess_id_column()now accept candidate ID column names
when auto-detecting the ID column.
- Formula support for BalanceDF model matrices
BalanceDF.model_matrix()now accepts aformulaargument to build
custom model matrices without precomputing them manually.
- Raw-covariate adjustment for custom models
Bug Fixes
- Removed deprecated setup build
- Replaced deprecated
setup.pywithpyproject.tomlbuild in CI to avoid build failure.
- Replaced deprecated
- Hardened ID column candidate validation
guess_id_column()now ignores duplicate candidate names and validates that candidates are non-empty strings.
- Hardened pandas 3 compatibility paths
- Updated string/NA handling and discrete checks for pandas 3 dtypes, and refreshed tests to accept string-backed dtypes.
Packaging & Tests
- Pandas 3.x compatibility
- Expanded the pandas dependency range to allow pandas 3.x releases.
- Direct util imports in tests
- Refactored util test modules to import helpers directly from their modules instead of via
balance_util.
- Refactored util test modules to import helpers directly from their modules instead of via
Breaking Changes
- Require positive weights for weight diagnostics that normalize or aggregate
design_effect,nonparametric_skew,prop_above_and_below, and
weighted_median_breakdown_pointnow raise aValueErrorwhen all weights
are zero.- Migration: ensure your weights include at least one positive value
before calling these diagnostics, or catch theValueErrorif all-zero
weights are possible in your workflow.
Contributors
@neuralsorcerer, @talgalili (with code/methodological review by @talsarig)
Full Changelog: 0.15.0...0.16.0
0.15.0 (2026-01-20)
New Features
- Added EMD/CVMD/KS distribution diagnostics
BalanceDFnow exposes Earth Mover's Distance (EMD), Cramér-von Mises distance (CVMD), and Kolmogorov-Smirnov (KS) statistics for comparing adjusted samples to targets.- These diagnostics support weighted or unweighted comparisons, apply discrete/continuous formulations, and respect
aggregate_by_main_covarfor one-hot categorical aggregation.
- Exposed outcome columns selection in the CLI
- Added
--outcome_columnsto choose which columns are treated as outcomes
instead of defaulting to all non-id/weight/covariate columns. Remaining columns are moved toignored_columns.
- Added
- Improved missing data handling in
poststratify()poststratify()now acceptsna_actionto either drop rows with missing
values or treat missing values as their own category during weighting.- Breaking change: the default behavior now fills missing values in
poststratification variables with"__NaN__"and treats this as a distinct
category during weighting. Previously, missing values were not handled
explicitly, and their treatment depended on pandasgroupbyandmerge
defaults. To approximate the legacy behavior where missing values do not
form their own category, passna_action="drop"explicitly.
- Added formula support for
descriptive_statsmodel matricesdescriptive_stats()now accepts aformulaargument that is always
applied to the data (including numeric-only frames), letting callers
control which terms and dummy variables are included in summary statistics.
Documentation
- Documented the balance CLI
- Added full API docstrings for
balance.cliand a new CLI tutorial notebook.
- Added full API docstrings for
- Created Balance CLI tutorial
- Added CLI command echoing, a
load_data()example, and richer diagnostics exploration with metric/variable listings and a browsable diagnostics table. https://import-balance.org/docs/tutorials/balance_cli_tutorial/
- Added CLI command echoing, a
- Synchronized docstring examples with test cases
- Updated user-facing docstrings so the documented examples mirror tested inputs
and outputs.
- Updated user-facing docstrings so the documented examples mirror tested inputs
Code Quality & Refactoring
- Added warning when the sample size of 'target' is much larger than 'sample' sample size
Sample.adjust()now warns when the target exceeds 100k rows and is at
least 10x larger than the sample, highlighting that uncertainty is
dominated by the sample (akin to a one-sample comparison).
- Split util helpers into focused modules
- Broke
balance.utilintobalance.utilssubmodules for easier navigation.
- Broke
Bug Fixes
- Updated
Sample.__str__()to format weight diagnostics likeSample.summary()- Weight diagnostics (design effect, effective sample size proportion, effective sample size)
are now displayed on separate lines instead of comma-separated on one line. - Replaced "eff." abbreviations with full "effective" word for better readability.
- Improves consistency with
Sample.summary()output format.
- Weight diagnostics (design effect, effective sample size proportion, effective sample size)
- Numerically stable CBPS probabilities
- The CBPS helper now uses a stable logistic transform to avoid exponential
overflow warnings during probability computation in constraint checks.
- The CBPS helper now uses a stable logistic transform to avoid exponential
- Silenced pandas observed default warning
- Explicitly sets
observed=Falsein weighted categorical KLD calculations
to retain current behavior and avoid future pandas default changes.
- Explicitly sets
- Fixed
plot_qq_categoricalto respect theweightedparameter for target data- Previously, the target weights were always applied regardless of the
weighted=Falsesetting, causing inconsistent behavior between sample
and target proportions in categorical QQ plots.
- Previously, the target weights were always applied regardless of the
- Restored CBPS tutorial plots
- Re-enabled scatter plots in the CBPS comparison tutorial notebook while
avoiding GitHub Pages rendering errors and pandas colormap warnings. https://import-balance.org/docs/tutorials/comparing_cbps_in_r_vs_python_using_sim_data/
- Re-enabled scatter plots in the CBPS comparison tutorial notebook while
- Clearer validation errors in adjustment helpers
trim_weights()now accepts list/tuple inputs and reports invalid types explicitly.apply_transformations()raises clearer errors for invalid inputs and empty transformations.
- Fixed
model_matrixto drop NA rows when requestedmodel_matrix(add_na=False)now actually drops rows containing NA values while preserving categorical levels, matching the documented behavior.- Previously,
add_na=Falseonly logged a warning without dropping rows; code relying on the old behavior may now see fewer rows and should either handle missingness explicitly or useadd_na=True.
Tests
- Aligned formatting toolchain between Meta internal and GitHub CI
- Added
["fbcode/core_stats/balance"]override to Meta's internaltools/lint/pyfmt/config.tomlto useformatter = "black"andsorter = "usort". - This ensures both internal (
pyfmt/arc lint) and external (GitHub Actions) environments use the same Black 25.1.0 formatter, eliminating formatting drift. - Updated CI workflow, pre-commit config, and
requirements-fmt.txtto useblack==25.1.0.
- Added
- Added Pyre type checking to GitHub Actions via
.pyre_configuration.externaland a newpyrejob in the workflow. Tests are excluded due to external typeshed stub differences; library code is fully type-checked. - Added test coverage workflow and badge to README via
.github/workflows/coverage.yml. The workflow collects coverage using pytest-cov, generates HTML and XML reports, uploads them as artifacts, and displays coverage metrics. A coverage badge is now shown in README.md alongside other workflow badges. - Improved test coverage for edge cases and error handling paths
- Added targeted tests for previously uncovered code paths across the library, addressing edge cases including empty inputs, verbose logging, error handling for invalid parameters, and boundary conditions in weighting methods (IPW, CBPS, rake).
- Tests exercise defensive code paths that handle empty DataFrames, NaN convergence values, invalid model types, and non-convergence warnings.
- Split test_util.py into focused test modules
- Split the large
test_util.pyfile (2325 lines) into 5 modular test files that mirror thebalance/utils/structure:test_util_data_transformation.py- Tests for data transformation utilitiestest_util_input_validation.py- Tests for input validation utilitiestest_util_model_matrix.py- Tests for model matrix utilitiestest_util_pandas_utils.py- Tests for pandas utilities (including high cardinality warnings)test_util_logging_utils.py- Tests for logging utilities
- This improves test organization and makes it easier to locate tests for specific utilities.
- Split the large
Contributors
Full Changelog: 0.14.0...0.15.0
0.14.0 (2025-12-14)
New Features
- Enhanced adjusted sample summary output
- Richer
Sample.summary()diagnostics- Adjusted sample summary now groups covariate diagnostics, reports design
effect alongside ESSP/ESS, and surfaces weighted outcome means when
available.
- Adjusted sample summary now groups covariate diagnostics, reports design
- Warning of high-cardinality categorical features in
.adjust() - Ignored column handling for Sample inputs
Sample.from_frameacceptsignore_columnsfor columns that should remain
on the dataframe but be excluded from covariates and outcome statistics.
Ignored columns appear inSample.dfand can be retrieved via
Sample.ignored_columns().
Code Quality & Refactoring
- Consolidated diagnostics helpers
- Added
_concat_metric_val_var()helper andbalance.util._coerce_scalar
for robust diagnostics row construction and scalar-to-float conversion. - Breaking change:
Sample.diagnostics()for IPW now always emits
iteration/intercept summaries plus hyperparameter settings.
- Added
Bug Fixes
- Early validation of null weight inputs
Sample.from_framenow raisesValueErrorwhen weights containNone,
NaN, orpd.NAvalues with count and preview of affected rows.
- Percentile weight trimming across platforms
trim_weights()now computes thresholds via percentile quantiles with
explicit clipping bounds for consistent behavior across Python/NumPy
versions.- Breaking change: percentile-based clipping may shift by roughly one
observation at typical limits.
- IPW diagnostics improvements
- Fixed
multi_classreporting, normalized scalar hyperparameters to floats,
removed deprecatedpenaltyargument warnings, and deduplicated metric
entries for stable counts across sklearn versions.
- Fixed
Tests
- Added Windows and macOS CI testing support
- Expanded GitHub Actions to run on
ubuntu-latest,macos-latest, and
windows-latestfor Python 3.9-3.14. - Added
tempfile_path()context manager for cross-platform temp file
handling and configured matplotlib Agg backend viaconftest.py.
- Expanded GitHub Actions to run on
Contributors
@neuralsorcerer, @talgalili, @wesleytlee
Full Changelog
0.13.0 (2025-12-02)
New Features
- Propensity modeling beyond static logistic regression
ipw()now accepts any sklearn classifier via themodelargument,
enabling the use of models like random forests and gradient boosting while
preserving all existing trimming and diagnostic features. Dense-only
estimators and models without linear coefficients are fully supported.
Propensity probabilities are stabilized to avoid numerical issues.- Allow customization of logistic regression by passing a configured
:class:~sklearn.linear_model.LogisticRegressioninstance through the
modelargument. Also, the CLI now accepts
--ipw_logistic_regression_kwargsJSON to build that estimator directly for
command-line workflows.
- Covariate diagnostics
- Added KL divergence calculations for covariate comparisons (numeric and
one-hot categorical), exposed viaBalanceDF.kld()alongside linked-sample
aggregation support.
- Added KL divergence calculations for covariate comparisons (numeric and
- Weighting Methods
rake()andpoststratify()now honourweight_trimming_mean_ratioand
weight_trimming_percentile, trimming and renormalising weights through the
enhancedtrim_weights(..., target_sum_weights=...)API so the documented
parameters work as expected
(#147).
Documentation
- Added comprehensive post-stratification tutorial notebook
(balance_quickstart_poststratify.ipynb)
(#141,
#142,
#143). - Expanded poststratify docstring with clear examples and improved statistical
methods documentation
(#141). - Added project badges to README for build status, Python version support, and
release tracking
(#145). - Added IPW quickstart tutorial showcasing default logistic regression and
custom sklearn classifier usage in (balance_quickstart.ipynb). - Shorten the welcome message (for when importing the package).
Code Quality & Refactoring
-
Raking algorithm refactor
- Removed
ipfndependency and replaced with a vectorized NumPy
implementation (_run_ipf_numpy) for iterative proportional fitting,
resulting in significant performance improvements and eliminating external
dependency (#135).
- Removed
-
IPW method refactoring
- Reduced Cyclomatic Complexity Number (CCN) by extracting repeated code
patterns into reusable helper functions:_compute_deviance(),
_compute_proportion_deviance(),_convert_to_dense_array(). - Removed manual ASMD improvement calculation and now uses existing
compute_asmd_improvement()fromweighted_comparisons_stats.py
- Reduced Cyclomatic Complexity Number (CCN) by extracting repeated code
-
Type safety improvements
- Migrated 32 Python files from
# pyre-unsafeto# pyre-strictmode,
covering core modules, statistics, weighting methods, datasets, and test
files - Modernized type hints to PEP 604 syntax (
X | Yinstead ofUnion[X, Y])
across 11 files for improved readability and Python 3.10+ alignment - Type alias definitions in
typing.pyretainUnionsyntax for Python 3.9
compatibility - Enhanced plotting function type safety with
TypedDictdefinitions and
proper type narrowing - Replaced assert-based type narrowing with
_verify_value_type()helper for
better error messages and pyre-strict compliance
- Migrated 32 Python files from
-
Renamed BalanceDF to BalanceDF****
- BalanceCovarsDF to BalanceDFCovars
- BalanceOutcomesDF to BalanceDFOutcomes
- BalanceWeightsDF to BalanceDFWeights
Bug Fixes
- Utility Functions
- Fixed
quantize()to preserve column ordering and use proper TypeError
exceptions (#133)
- Fixed
- Statistical Functions
- Fixed division by zero in
asmd_improvement()whenasmd_mean_beforeis
zero, now returns0.0for 0% improvement
- Fixed division by zero in
- CLI & Infrastructure
- Replaced deprecated argparse FileType with pathlib.Path
(#134)
- Replaced deprecated argparse FileType with pathlib.Path
- Weight Trimming
- Fixed
trim_weights()to consistently returnpd.Serieswith
dtype=np.float64and preserve original index across both trimming methods - Fixed percentile-based winsorization edge case:
_validate_limit()now
automatically adjusts limits to prevent floating-point precision issues
(#144) - Enhanced documentation for
trim_weights()and_validate_limit()with
clearer examples and explanations
- Fixed
Tests
- Enhanced test coverage for weight trimming with
test_trim_weights_return_type_consistencyand 11 comprehensive tests for
_validate_limit()covering edge cases, error conditions, and boundary
conditions
Contributors
@neuralsorcerer, @talgalili, @wesleytlee
Full Changelog: 0.12.1...0.13.0
0.12.1 (2025-11-03)
New Features
- Added a welcome message when importing the package.
Welcome to balance (Version 0.12.1)!
An open-source Python package for balancing biased data samples.📖 Documentation: https://import-balance.org/
🛠️ Get Help / Report Issues: https://github.com/facebookresearch/balance/issues/
📄 Citation:
Sarig, T., Galili, T., & Eilat, R. (2023).
balance - a Python package for balancing biased data samples.
https://arxiv.org/abs/2307.06024Tip: You can access this information at any time with balance.help()
Documentation
- Added 'CHANGELOG' to the docs website. https://import-balance.org/docs/docs/CHANGELOG/
Bug Fixes
- Fixed plotly figures in all the tutorials. https://import-balance.org/docs/tutorials/
Contributors
Full Changelog: 0.12.0...0.12.1