Skip to main content

Propensity Score Matching (PSM), Coarsened Exact Matching (CEM), and Causal Inference in Python. A port of R's MatchIt.

Project description

pymatchit-causal: Propensity Score Matching in Python

DOI

Scalable Causal Inference, Propensity Score Matching (PSM), and Coarsened Exact Matching (CEM).

pymatchit-causal is a Python port of the standard R package MatchIt. It allows data scientists to preprocess data for causal inference by balancing covariates between treated and control groups using state-of-the-art matching methods.

Why use pymatchit?

If you are looking for Propensity Score Matching in Python, this library provides a robust, "R-style" workflow including:

  • Propensity Score Estimation: Logistic Regression (GLM), Random Forest, GBM, Neural Networks.
  • Matching Algorithms: Nearest Neighbor (Greedy), Optimal Matching, Exact, Subclassification, and Coarsened Exact Matching (CEM).
  • Diagnostics: Publication-ready Love Plots (Covariate Balance), Propensity Density Plots, and ECDF plots.

Features

  • Matching Methods: Nearest Neighbor, Optimal Matching, Exact, Coarsened Exact Matching (CEM), Subclassification.
  • Distance Metrics: Logistic Regression (GLM), Mahalanobis, Random Forest, GBM, Neural Networks, etc.
  • Diagnostics: Love Plots, ECDF Plots, Propensity Score Density Plots, and Summary Tables (SMD, Variance Ratios).
  • Parity: Designed to mirror the R MatchIt API (matchit(formula, data, method=...)).

Table of Contents

  1. Installation
  2. Example Workflow (Excel Data)
  3. API Reference
  4. Matching Methods Details
  5. Distance Measures

Installation

pip install pymatchit-causal

Dependencies: numpy, pandas, scipy, statsmodels, matplotlib, scikit-learn, seaborn, patsy.


Example Workflow (Excel Data)

This example demonstrates a full analysis pipeline: loading data from Excel, configuring matching, assessing balance, extracting the matched dataset, and performing downstream inference.

Scenario: You have an Excel file healthcare_data.xlsx with a binary treatment variable took_drug, an outcome recovery_time, and confounders like age, severity, and income.

1. Load Data

import pandas as pd
from pymatchit import MatchIt

# Load your dataset
df = pd.read_excel("healthcare_data.xlsx")

# Preview data
# Columns: [patient_id, took_drug, recovery_time, age, severity, income, gender]
print(df.head())

2. Initialize and Match

We will use Nearest Neighbor matching using a Random Forest to estimate the propensity score, applying a caliper to ensure good matches.

# Initialize the matching model
m = MatchIt(
    data=df,
    method='nearest',           # 1:1 Nearest Neighbor matching
    distance='randomforest',    # Use Random Forest for Propensity Scores
    distance_options={'n_estimators': 500}, # Pass kwargs to sklearn
    caliper={'distance': 0.1, 'age': 2},    # PS within 0.1 SDs, Age within 2 years
    replace=False,              # Match without replacement
    random_state=42             # Reproducibility
)

# Fit the model using an R-style formula
# Format: treatment_variable ~ covariate1 + covariate2 + ...
m.fit("took_drug ~ age + severity + income + gender")

3. Assess Balance (Diagnostics)

Before analyzing the outcome, verify that the treatment and control groups are balanced.

# 1. Statistical Summary
# Check Standardized Mean Differences (SMD) and Variance Ratios
summary = m.summary()

# 2. Visual Inspection: Love Plot
# Displays covariate balance before (red) and after (blue) matching.
# Ideally, all blue dots should be close to 0.
m.plot(type='balance', threshold=0.1)

# 3. Visual Inspection: Propensity Overlap
# Check if treated and control groups share common support
m.plot(type='propensity')

# 4. Visual Inspection: ECDF Plot
# Check distributional balance for continuous variables (e.g., age)
m.plot(type='ecdf', variable='age')

4. Extract Matched Data

If balance is satisfactory, extract the data for analysis.

# Get the final dataset containing only matched units (with weights)
matched_df = m.matches(format='long') 

# Merge back with original data to get outcomes if needed, 
# or use m.matched_data which retains the original columns + weights + subclass.
final_analysis_set = m.matched_data

print(final_analysis_set.head())

5. Downstream Inference and Subclasses

After running .fit(), the matched_data dataframe automatically includes a subclass column. This integer ID groups matched units together (e.g., a treated unit and its matched control).

This is essential for calculating cluster-robust standard errors in your final effect estimation:

import statsmodels.formula.api as smf

# Example: Weighted Least Squares with Cluster-Robust Standard Errors
model = smf.wls("recovery_time ~ took_drug", data=final_analysis_set, weights=final_analysis_set['weights'])
results = model.fit(cov_type='cluster', cov_kwds={'groups': final_analysis_set['subclass']})
print(results.summary())

API Reference

The MatchIt Class

class MatchIt(
    data: pd.DataFrame,
    method: str = "nearest",
    distance: str = "glm",
    link: str = "logit",
    replace: bool = False,
    caliper: Union[float, Dict[str, float]] = None,
    ratio: int = 1,
    estimand: str = "ATT",
    exact: Union[str, List[str]] = None,
    subclass: int = 6,
    discard: str = "none",
    m_order: str = "largest",
    cutpoints: Dict = None,
    distance_options: Dict = None,
    random_state: int = None
)

Parameters

Parameter Type Default Description
data pd.DataFrame Required The input dataset containing treatment, outcome, and covariates.
method str "nearest" The matching algorithm to use.
nearest: Nearest Neighbor (Greedy) matching.
optimal: Optimal matching that minimizes the global total distance across all matched pairs.
exact: Exact matching on all covariates.
subclass: Subclassification (Stratification).
cem: Coarsened Exact Matching.
distance str "glm" The method used to estimate propensity scores or distance.
glm: Logistic Regression (standard PSM).
mahalanobis: Mahalanobis distance (no PS estimation).
ML Methods: randomforest, gbm, neuralnet, decisiontree, adaboost, lasso, ridge, elasticnet.
link str "logit" The link function for the distance measure.
logit: Log-odds (linear logit). Recommended for PSM.
linear.logit: Same as logit.
probit: Probit regression (only for GLM).
linear: Raw probabilities (0-1).
replace bool False Whether to match with replacement. If True, control units can be matched to multiple treated units.
caliper float or dict None The maximum allowed distance between matches. If a float, acts as a global threshold on the distance measure in standard deviations. If a dict, sets covariate-specific limits (e.g. {'distance': 0.1, 'age': 2}). Matches exceeding this are dropped.
ratio int 1 The number of control units to match to each treated unit (e.g., 2 for 1:2 matching).
estimand str "ATT" The target causal estimand.
ATT: Average Treatment Effect on the Treated.
ATE: Average Treatment Effect (entire population).
exact list None A list of column names (e.g. ['gender']) to enforce exact matching on. Matching will occur within strata defined by these variables.
subclass int 6 Number of subclasses to create when using method='subclass'.
discard str "none" Logic to discard units outside common support.
none: Keep all units.
treated: Drop treated units outside control range.
control: Drop control units outside treated range.
both: Drop units from both groups outside the intersection.
m_order str "largest" The order matches are generated ('largest', 'smallest', 'random', 'data').
cutpoints dict None For method='cem'. Defines cutpoints for continuous variables. Example: {'age': 4, 'income': [0, 20k, 50k, 100k]}.
distance_options dict None Keyword arguments passed directly to the underlying scikit-learn estimator (e.g., {'n_estimators': 100} for Random Forest).

Class Methods

fit(formula: str)

Executes the matching process.

  • formula: A string in the format treatment ~ cov1 + cov2 + .... The variable on the left is treated as the binary treatment indicator. Variables on the right are the covariates used for matching.

summary(print_output: bool = True)

Calculates balance statistics (Means, Standardized Mean Difference, Variance Ratios) for both matched and unmatched samples.

  • Returns: A dictionary containing unmatched stats dataframe, matched stats dataframe, and sample_sizes.

plot(type: str, ...)

Visualizes the matching results.

  • type='balance': Draws a "Love Plot" of Standardized Mean Differences.
    • Optional: threshold (vertical line, e.g., 0.1), var_names (dict to rename vars for display), colors.
  • type='propensity': Plots Kernel Density Estimates (KDE) of propensity scores for treated vs. control, before and after matching.
  • type='ecdf': Plots Empirical Cumulative Distribution Functions for a specific continuous variable.
    • Required: variable (name of the column to plot).

matches(format: str = 'long')

Retrieves the map of matched units.

  • format='long': Returns a DataFrame with treated_index and control_index (one row per match).
  • format='wide': Returns a DataFrame where each row is a treated unit and columns control_1, control_2... contain indices of matched controls.

Matching Methods Details

  1. Nearest Neighbor (method='nearest'):

    • Greedy matching. For each treated unit, selects the closest control unit based on the distance measure.
    • Supports caliper to prune bad matches.
    • Supports exact argument for stratified matching (e.g., match nearest neighbor, but only within the same 'Gender' group).
  2. Optimal Matching (method='optimal'):

    • Minimizes the global total distance across all matched pairs.
  3. Exact Matching (method='exact'):

    • Matches units that have identical values for all covariates in the formula.
    • Often results in many discarded units if covariates are continuous.
  4. Subclassification (method='subclass'):

    • Divides the sample into subclasses (bins) based on propensity score quantiles.
    • Weights are calculated to balance the subclasses. Robust for estimating ATE.
  5. Coarsened Exact Matching (method='cem'):

    • Coarsens continuous variables into bins (defined by cutpoints) and matches exactly on these coarsened bins.
    • Very fast and reduces model dependence.

Distance Measures

The distance parameter controls how similarity is calculated.

  • glm: Default. Fits a Logistic Regression (or Probit if link='probit') to estimate propensity scores.
  • mahalanobis: Calculates the Mahalanobis distance between units based on covariates. Note: Does not produce a propensity score property, so propensity plots will not be available unless a separate score is estimated.
  • Machine Learning Estimators:
    • randomforest: Uses RandomForestClassifier.
    • gbm: Uses GradientBoostingClassifier.
    • decisiontree: Uses DecisionTreeClassifier.
    • neuralnet: Uses MLPClassifier.
    • lasso / ridge / elasticnet: Uses LogisticRegression with penalties.
    • adaboost: Uses AdaBoostClassifier.

Citation

If you use pymatchit-causal in your research, please cite it:

Tünnermann, J. (2026). pymatchit: Propensity Score Matching and Causal Inference in Python (Version 0.2.1). Zenodo. https://doi.org/10.5281/zenodo.17839522

BibTeX:

@software{pymatchit_causal,
  author       = {Jonas Tünnermann},
  title        = {pymatchit: Propensity Score Matching and Causal Inference in Python},
  year         = 2026,
  publisher    = {Zenodo},
  version      = {0.2.1},
  doi          = {10.5281/zenodo.17839522},
  url          = {[https://doi.org/10.5281/zenodo.17839522](https://doi.org/10.5281/zenodo.17839522)}
}

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

pymatchit_causal-0.2.2.tar.gz (39.0 kB view details)

Uploaded Source

Built Distribution

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

pymatchit_causal-0.2.2-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

File details

Details for the file pymatchit_causal-0.2.2.tar.gz.

File metadata

  • Download URL: pymatchit_causal-0.2.2.tar.gz
  • Upload date:
  • Size: 39.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for pymatchit_causal-0.2.2.tar.gz
Algorithm Hash digest
SHA256 174e5887b533e70d3fb7af657b4d2cc08b726ef661be8080af4004118752b601
MD5 d8393948d77d65a37c952a8fcb51ec89
BLAKE2b-256 8faef5e1162dbfbc49030323db4d1aa6c4be43f30a00cdda5dd9d97ebd2582a5

See more details on using hashes here.

File details

Details for the file pymatchit_causal-0.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for pymatchit_causal-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a6e5ae28bad31f1a9a8f98b502f8ccae91ba703961a31bb0d775e015a4e69891
MD5 7c9728036daa0149a584823e501deecf
BLAKE2b-256 08de0d6a1362db5135a21c8957d4e0b410147462c0981e1b24b6a26b540f8c7b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page