ANALYSIS TOOL BOX

Descriptive Analytics

ConductPropensityScoreMatching

Create balanced treatment and control groups for causal inference from observational data.

causal-inferencepropensity-scorematchingobservational-study

Introduction

When you can't randomize — a training program was rolled out to volunteers, a policy applied to whoever opted in, a treatment given to whoever's doctor recommended it — how do you tell whether the outcome difference between treated and untreated groups reflects the treatment itself, or just who was more likely to get treated in the first place? ConductPropensityScoreMatching addresses that by estimating each subject's probability of receiving treatment (their propensity score) from observed covariates, then pairing treated subjects with control subjects who had a similar probability. The result is a matched dataset where treatment and control groups look comparable on the covariates you fed in, closer to what a randomized experiment would have produced.

Teaching Note

Propensity score matching is essential for:

  • Estimating causal effects in observational studies without randomization
  • Reducing selection bias and confounding in treatment effect analysis
  • Creating balanced comparison groups for A/B testing post-hoc analysis
  • Medical research when randomized controlled trials are unethical or impractical
  • Policy evaluation and program impact assessment
  • Marketing campaign effectiveness measurement
  • Educational intervention analysis
  • Economic and social science research requiring causal inference

Parameters

ParameterTypeDefaultDescription
dataframerequiredA pandas DataFrame containing subjects to match. Each row represents one subject with covariates, treatment assignment, and a unique identifier.
subject_id_column_namerequiredName of the column containing unique subject identifiers. Each value must be unique across all rows in the DataFrame.
list_of_column_names_to_base_matchingrequiredList of covariate column names to use for calculating propensity scores. These should be pre-treatment variables that influence both treatment assignment and outcomes.
grouping_column_namerequiredName of the column indicating treatment group membership. Must contain exactly two unique values (treatment and control).
control_group_namerequiredThe value in the grouping column that identifies the control group. All other values are treated as the treatment group.
max_matches_per_subject1Maximum number of control subjects to match with each treatment subject. Higher values increase statistical power but may reduce match quality. Defaults to 1.
balance_groupsTrueWhether to balance the propensity score model by weighting observations. Set to False if matching fails with the default setting. Defaults to True.
propensity_score_column_name'Propensity Score'Name for the new column containing propensity scores (probability of treatment). Defaults to 'Propensity Score'.
propensity_logit_column_name'Propensity Logit'Name for the new column containing propensity logits (log-odds of treatment). Defaults to 'Propensity Logit'.
matched_id_column_name'Matched ID'Name for the new column containing matched subject IDs. For subjects with multiple matches, multiple rows will be created. Defaults to 'Matched ID'.
random_seed412Random seed for reproducibility of the matching algorithm. Defaults to 412.

Returns

A pandas DataFrame — the original data with three additional columns: the propensity score column (probability of treatment, 0–1), the propensity logit column (log-odds of treatment), and the matched ID column (subject ID(s) of matched control/treatment subjects). Subjects without matches will have NaN in the matched ID column; subjects with multiple matches will appear in multiple rows.

Example

python
from analysistoolbox.descriptive_analytics import ConductPropensityScoreMatching
import pandas as pd

# Medical trial: match patients who received treatment with similar control patients
patient_df = pd.DataFrame({
    'patient_id': range(1, 101),
    'age': [25 + i for i in range(100)],
    'severity_score': [50 + i % 30 for i in range(100)],
    'comorbidities': [i % 3 for i in range(100)],
    'received_treatment': ['Treatment' if i % 3 == 0 else 'Control' for i in range(100)]
})
matched_df = ConductPropensityScoreMatching(
    patient_df,
    subject_id_column_name='patient_id',
    list_of_column_names_to_base_matching=['age', 'severity_score', 'comorbidities'],
    grouping_column_name='received_treatment',
    control_group_name='Control',
    max_matches_per_subject=1
)
# Compare outcomes within matched pairs to estimate the treatment effect