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), 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, 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, and extracting the matched dataset.

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=0.2,                # Drop matches > 0.2 std devs apart
    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.
final_analysis_set = m.matched_data

print(final_analysis_set.head())
# Now you can perform a weighted regression or T-test on 'recovery_time'

API Reference

The MatchIt Class

class MatchIt(
    data: pd.DataFrame,
    method: str = "nearest",
    distance: str = "glm",
    link: str = "logit",
    replace: bool = False,
    caliper: float = None,
    ratio: int = 1,
    estimand: str = "ATT",
    exact: Union[str, List[str]] = None,
    subclass: int = 6,
    discard: str = "none",
    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.
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 None The maximum allowed distance between matches, expressed in standard deviations of the distance measure. 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.
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. 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.
  3. 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.
  4. 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. (2025). pymatchit: Propensity Score Matching and Causal Inference in Python (Version 0.1.2). Zenodo. https://doi.org/10.5281/zenodo.17839552

BibTeX:

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

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.1.3.tar.gz (36.7 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.1.3-py3-none-any.whl (31.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pymatchit_causal-0.1.3.tar.gz
  • Upload date:
  • Size: 36.7 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.1.3.tar.gz
Algorithm Hash digest
SHA256 6f48f3d453994b10cabd62f1e3d21824f6a0e8081c3be3a027a693caa1b7f902
MD5 41d3b7cb15827cdc4ed79e8f95ed4e2d
BLAKE2b-256 8bc7099258d005db16b790f85a3c7e99391870c3640c48e628cbfdd678154ea9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pymatchit_causal-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ca793ce422b76cf45be1415f20e3c645bd8ec34003f30778edf2877f9454c828
MD5 8f2ea3160ab49bb95a61aae09bbb4af4
BLAKE2b-256 d807cb722ee73fcf1911683a3db20913b9bb894da813abe06b8e5cab3bd0349f

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