Repository navigation
Releases: jtuenner/pymatchit
Release list
Release v0.6.1: Optimal Full Matching and mahvars Caliper Fix
What's New in v0.6.1
This release fixes two matching bugs. Results change for full matching, and for nearest neighbor matching that combines mahvars with a caliper; other methods are unaffected.
Changed results
- Full matching is now optimal: it finds the subclasses with the smallest total distance between treated and control units, as R's
optmatchdoes. A subclass is one treated unit with one or more controls, or one control with one or more treated units. Before, every unit of the smaller group was first paired with a unit of its own, which forced distant pairs when the groups overlap little: on the Lalonde data the standardized mean difference ofblackafter matching was 1.02 and is now 0.02. min_controls_per_subclassandmax_controls_per_subclassare part of the optimization instead of being applied afterwards. Controls thatmax_controls_per_subclassleaves no room for stay unmatched, as few as possible. If there are too few controls to give every treated unitmin_controls_per_subclassof its own, subclasses are merged, now with a warning.- Nearest neighbor matching with
mahvarsand a caliper applies the caliper to the propensity score. It was compared with the Mahalanobis distance instead, which left most units unmatched and allowed pairs that were far apart on the propensity score. Optimal, full and genetic matching were not affected.
Fixed
- A caliper on the distance measure with
distance="mahalanobis"(which estimates no propensity score), and invalidmin_controls_per_subclassormax_controls_per_subclassvalues, raise an error that says what to change.
Release v0.6.0: R MatchIt Parity, Option Warnings, and Balanced CBPS
What's New in v0.6.0
This release aligns pymatchit with R MatchIt (checked against version 4.8.1) and fixes several cases where options were silently ignored. Results change for some methods; see "Changed results" below before upgrading an existing analysis.
New
- Genetic matching supports
exact,antiexact,mahvarsandm_order. - Full matching supports
antiexactand covariate-specific calipers (caliper={"age": 2}). - Cardinality matching supports
exact(each stratum is solved separately),ratioandmahvars:ratio=kselects the largest balanced sample with k controls per treated unit. Leavingratiounset keeps profile matching, where the whole treated group is retained.mahvarspairs the selected units, andmatches()returns the pairs.
- GLM links
cloglogandcauchit, pluslinear.probit.linearis accepted as an alias forlinear.logit. - Optimal matching supports
antiexactandmahvars. estimand="ATC"is implemented for all methods. For nearest, optimal and genetic matching the control group becomes the focal group, andmatches()is keyed by control unit.
Unused options now warn
An option the chosen method cannot use (for example caliper with method="subclass") is ignored with a warning that names the option, as in R. Before, most were ignored silently. This lets you switch method without rewriting the call. To turn these warnings into errors, use warnings.simplefilter("error").
Contradictory requests raise an error: estimand="ATE" with nearest, optimal or genetic matching; an unknown link; m_order="largest" without a propensity score; mahvars with cardinality matching but no ratio.
Changed results
link:logitandprobitnow match on the predicted probability, as in R. Uselink="linear.logit"for the previous behaviour (matching on the linear predictor). Calipers are in standard deviations of this measure, so their width changes too.- User-supplied distances (
distance=an array) are used as-is; they are no longer logit-transformed. - Nearest matching with
ratio > 1assigns matches in rounds: every unit gets a first match before any gets a second. Control weights are 1/k per match, where k is the number of matches the treated unit actually received. - Calipers with
exact=use the full-sample standard deviation, not each stratum's. - Matching order:
m_orderdefaults tolargestwhen a propensity score is available (smallestforATC) and to row order otherwise.mahvarsmatching is ordered by the propensity score. - Genetic matching uses a weighted distance on standardized variables, including the propensity score, and optimizes the matching it actually returns.
- CBPS is the just-identified estimator, which balances the covariates exactly, and follows
estimand. - Cardinality matching is solved exactly as a mixed-integer program.
std_tolsuses the same standardization assummary(). - Full matching seeds subclasses with an optimal 1:1 assignment and enforces calipers and
min/max_controls_per_subclass. - Subclassification bins on the focal group's scores (treated for ATT, control for ATC, everyone for ATE).
- CEM bins numeric covariates with Sturges' rule by default instead of 5 bins.
- KS statistics for the matched sample use the matching weights.
Fixed
- Optimal matching crashed when a caliper excluded pairs.
- Genetic matching with a caliper crashed on non-default indexes and dropped most treated units when matching without replacement.
m_order="random"reseeded NumPy's global random state.matches()returned missing values for string-labelled indexes.
Signature
ratiodefaults toNone(1:1 for pair matching; profile matching for cardinality). It was1.m_orderdefaults toNone. It was"largest".
Development
- Tests run on Python 3.9–3.14 and on the oldest and newest supported pandas. The code is formatted with black.
Thanks to Nick Eubank (@nickeubank) for the bug review and the pull requests behind most of this release, and to @codeteme for the report on control weights.
Release v0.5.0: Advanced Matching Algorithms, Estimators, and Visual Diagnostics
What's New in v0.5.0
This release significantly expands pymatchit-causal's capabilities, adding major new matching algorithms, additional machine learning distance estimators, and a completely overhauled visual diagnostic suite.
✨ New Matching Algorithms
- Full Matching (
method='full'): Optimal subclassification that minimizes globally calculated total distance within subclasses. - Genetic Matching (
method='genetic'): Uses an evolutionary algorithm to find optimal covariate weights that maximize balance prior to matching. - Cardinality Matching (
method='cardinality'): Finds the largest possible subset of the data where treated and control groups satisfy user-specified balance constraints.
🚀 Expanded Distance Metrics
In addition to GLM and Mahalanobis distance, we now officially support and document:
- CBPS (Covariate Balancing Propensity Score): Jointly optimizes prediction accuracy and covariate balance.
- New ML Estimators: Added support for
decisiontree,adaboost, and penalized approaches (lasso,ridge,elasticnet). - User-Supplied Scores: You can now pass a pre-computed
numpyarray orpandasSeries directly into the distance argument.
📊 Publication-Ready Diagnostics
- Jitter Plots (
m.plot(type='jitter')): Intuitively verify propensity score distributions and overlap across matched vs. unmatched units. - QQ Plots (
m.plot(type='qq')): Compare quantile distributions of treated and control groups covariate by covariate. - Visual Cohesion: Overhauled the plotting engine. Love plots, ECDF plots, Propensity densities, and Jitter plots now share a consistent, modern visual aesthetic right out of the box.
Release v0.2.0: Optimal Matching, Covariate Calipers & Subclass Inference
Release v0.2.0: Optimal Matching, Covariate Calipers & Subclass Inference
We are thrilled to announce a major update to pymatchit-causal! This release brings significant new matching algorithms, critical tools for downstream statistical inference, massive performance optimizations, and a visual overhaul of our diagnostic plots.
Thank you to the community for the excellent GitHub issues and feedback that drove this release.
🚀 New Features
- Optimal Matching (
method='optimal'): You can now perform globally optimal matching. Unlike greedy nearest-neighbor matching, optimal matching minimizes the total absolute distance across all matched pairs in the dataset, ensuring the best possible overall balance. - Covariate-Specific Calipers: The
caliperargument now accepts a dictionary, allowing you to set strict, independent thresholds for specific variables alongside the propensity score. Example:caliper={'distance': 0.1, 'age': 2}ensures matches are within 0.1 standard deviations of the propensity score and strictly within 2 years of age. - Subclass Identifiers for Downstream Inference: The
matched_datadataframe now automatically exports asubclasscolumn. This assigns a unique integer ID to each matched pair or stratum, which is essential for calculating cluster-robust standard errors in your final effect estimation models (mirroring the behavior of the RMatchItpackage). - Matching Order Control (
m_order): Added the ability to dictate the sequence of greedy matching. You can now specifym_order="largest","smallest", or"random".
⚡ Performance & Visual Upgrades
-
$O(N)$ Nearest Neighbor Matching: Completely refactored the underlying KD-Tree logic for Nearest Neighbor matching without replacement. The algorithm now fits the tree globally once (rather than per-unit), eliminating infinite hangs on large datasets and matching thousands of rows in milliseconds. -
Publication-Ready "Nature-Style" Love Plots: The
love_plothas received a massive visual upgrade. It now features a clean, minimalist aesthetic (no heavy gridlines, optimized data-ink ratio, and improved scatter rendering) specifically designed to look beautiful in high-impact scientific journals.
🐛 Bug Fixes & Stability
- Categorical/String Variable Diagnostics: Fixed a critical bug where running
.summary()or.plot()on formulas containing string variables would cause aTypeError. Categorical variables are now safely routed through Patsy to generate properly evaluated dummy matrices (e.g.,race[T.Hispanic]). - Fractional Weight Variance: Fixed an issue in
diagnostics.pythat occasionally produced zero or negative variances when calculating weighted statistics for ratios > 1. It now uses the robust unbiased reliability weights variance formula. - Caliper Scaling: Fixed the caliper threshold calculation to properly scale based on the global standard deviation of the distance measure, preventing overly restrictive drops.
- eCDF Guardrails: Added explicit data-type validation to
ecdf_plot. Attempting to plot a categorical variable will now gracefully raise an informativeTypeErrorinstead of causing a cascading Seaborn crash. - NaN Detection: Added explicit, clean guardrails to catch missing values (NaNs) in covariates before they hit the formula parser, providing users with clear instructions to impute or drop missing data.
- Pandas Int64 Casting: Fixed an issue where casting index columns to Pandas nullable integers would silently fail when exporting matches in
wideformat.
*** To upgrade:
pip install --upgrade pymatchit-causalpymatchit-causal: Propensity Matching for Python based on the R MatchIt package.
Summary
pymatchit is a Python library designed to preprocess observational data for causal inference by balancing covariates between treated and control groups. It implements Propensity Score Matching (PSM) and other matching techniques to reduce selection bias, mirroring the workflow of the widely standard R package MatchIt [MatchIt:2011]. The library provides a familiar formula-based API (e.g., treat ~ age + educ) while leveraging the speed and ecosystem of Python's scikit-learn and pandas. The package is distributed via PyPI as pymatchit-causal.
Statement of Need
In observational studies, estimating the causal effect of a treatment (e.g., a job training program) on an outcome (e.g., wages) is challenging because treated subjects often differ systematically from control subjects. Propensity Score Matching is a common technique to address this by constructing a "synthetic" control group that statistically resembles the treated group [Rosenbaum:1983].
While R users have long relied on MatchIt for this task, the Python ecosystem has lacked a dedicated, unified library for matching that offers:
- R-style Formula Syntax: Allowing users to define causal relationships clearly and concisely.
- Diverse Distance Metrics: Beyond simple logistic regression,
pymatchitsupports modern machine learning estimators (Random Forest, Gradient Boosting, Neural Networks) viascikit-learnfor robust propensity score estimation. - Standardized Diagnostics: Generating "Love plots" (standardized mean difference plots) and Empirical Cumulative Distribution Function (ECDF) plots is critical for verifying balance but often requires writing extensive custom boilerplate code in Python.
pymatchit fills this gap, enabling data scientists to perform rigorous causal matching and balance assessment entirely within the Python environment without needing to switch languages.
Functionality
The package is designed around a three-step workflow that separates design (matching) from analysis:
-
Estimation & Matching: Users initialize a
MatchItobject and fit a model using R-style formulas. The library calculates propensity scores and matches units using algorithms like Nearest Neighbor.from pymatchit import MatchIt, load_lalonde # Load the standard Lalonde dataset df = load_lalonde() # Match using Nearest Neighbor with a Caliper # We use Logistic Regression (GLM) for the propensity score m = MatchIt(df, method='nearest', distance='glm', caliper=0.2) m.fit("treat ~ age + educ + race + married + nodegree + re74 + re75")
-
Diagnostics: Users can immediately visualize covariate balance to ensure the matching procedure was successful.
# Generate Love Plot to check Standardized Mean Differences (SMD) m.plot(type='balance') # Check distributional balance using ECDF plots m.plot(type='ecdf', variable='age')
-
Analysis: The matched dataset (with weights) can be extracted for downstream analysis.
# Return the matched dataset with a 'weights' column matched_data = m.matched_data
The package currently supports Nearest Neighbor, Exact, and Subclassification matching, utilizing the scikit-learn ecosystem for underlying distance estimation.
Validation against R MatchIt
To ensure reliability, pymatchit was validated against the original R MatchIt package using the standard LaLonde dataset [Lalonde:1986]. We performed a nearest-neighbor match (ratio=1, caliper=0.2) using logistic regression for propensity score estimation in both languages.
As shown in Figure 1, the covariate balance results—measured by Standardized Mean Differences (SMD)—are nearly identical. Both implementations successfully reduced the imbalance (red circles) to below 0.1 standard deviations (blue circles) for most covariates.
Covariate Balance Comparison: Python (pymatchit) vs R (MatchIt). The reduction in Standardized Mean Difference is consistent across both implementations.
We further analyzed the specific units selected by the greedy matching algorithm (Figure 2). While there is substantial overlap (Green Xs, 89 units), there are minor discrepancies in unit selection (Orange circles vs. Blue crosses).
Unit Selection Comparison. Discrepancies occur due to floating-point precision differences in Propensity Score estimation between Python's statsmodels and R's glm.

These discrepancies are expected and stem from minor floating-point differences between Python's statsmodels (used by pymatchit) and R's glm engine. In greedy nearest-neighbor matching, even a pymatchit remains statistically equivalent to the R reference implementation.
Acknowledgements
This project builds upon the methodological foundations laid by the authors of the R MatchIt package: Daniel Ho, Kosuke Imai, Gary King, and Elizabeth Stuart.
v0.1.1: pymatchit-causal. Propensity Score Matching (PSM) & Causal Inference in Python
pymatchit-causal is a robust Python port of the standard R package MatchIt. It brings state-of-the-art preprocessing for parametric causal inference to the Python ecosystem, allowing researchers to balance covariates in observational studies effectively and easily create Propensity Score Matching.
🚀 Key Features
- Matching Algorithms: Support for Nearest Neighbor (Greedy), Exact Matching, Subclassification, and Coarsened Exact Matching (CEM).
- Flexible Distance Estimation: Estimate propensity scores using GLM (Logistic Regression), Mahalanobis Distance, or Machine Learning methods (Random Forest, Gradient Boosting, Neural Networks, Lasso/Ridge/ElasticNet) via scikit-learn integration.
- Scientific Validation: Verified against R's
MatchItpackage to ensure statistical consistency and correctness. - Publication-Ready Diagnostics: Generate Love Plots (Standardized Mean Differences), Propensity Score Density Plots, and ECDF Plots using Seaborn.
- Robustness: Includes strict input validation, missing data handling warnings, and "Common Support" discarding logic.
📦 Installation
pip install pymatchit-causal📚 Quick Start
from pymatchit import MatchIt, load_lalonde
# Load standard dataset
df = load_lalonde()
# Perform Nearest Neighbor Matching with Caliper
m = MatchIt(df, method='nearest', distance='glm', caliper=0.2)
m.fit("treat ~ age + educ + race + married + re74 + re75")
# Check Balance
m.plot(type='balance')
# Get Matched Data
matched_data = m.matches()📄 Citation
If you use this software in your research, please cite:
Tünnermann, J. (2025). pymatchit-causal: Propensity Score Matching and Causal Inference in Python (Version 0.1.0) [Computer software]. https://github.com/jtuenner/pymatchit
@software{pymatchit-causal,
author = {Tünnermann, Jonas},
title = {pymatchit: Propensity Score Matching and Causal Inference in Python},
url = {https://github.com/jtuenner/pymatchit},
version = {0.1.0},
year = {2025}
}