Approach: fit a linear regression of outcome on treatment, segment indicators, and treatment×segment interactions; compute clustered (by user) robust standard errors; extract segment-level treatment effects = coef(treatment) + coef(treatment:segment) with CIs via linear combinations.python
import pandas as pd
import statsmodels.formula.api as smf
import statsmodels.api as sm
import numpy as np
# df columns: user_id, outcome, treatment (0/1), segment (categorical, e.g., country/device)
df['segment'] = df['segment'].astype('category')
# use treatment as numeric 0/1
model = smf.ols('outcome ~ treatment * C(segment)', data=df).fit(
cov_type='cluster', cov_kwds={'groups': df['user_id']}
)
# base treatment effect is coef of treatment (for baseline segment)
params = model.params
cov = model.cov_params()
# get list of segments and baseline
segments = df['segment'].cat.categories.tolist()
baseline = segments[0]
results = []
for seg in segments:
if seg == baseline:
# effect = coef(treatment)
coef_name = 'treatment'
eff = params[coef_name]
se = np.sqrt(cov.loc[coef_name, coef_name])
else:
inter_name = f'treatment:C(segment)[T.{seg}]'
eff = params['treatment'] + params.get(inter_name, 0.0)
# variance of sum: Var(treat)+Var(inter)+2*Cov
var = (cov.loc['treatment','treatment'] +
cov.loc.get(inter_name, inter_name) +
2*cov.loc.get('treatment', inter_name))
se = np.sqrt(var)
ci_low = eff - 1.96*se
ci_high = eff + 1.96*se
results.append({'segment': seg, 'effect': eff, 'se': se, 'ci_low': ci_low, 'ci_high': ci_high})
seg_table = pd.DataFrame(results)
print(seg_table)
Key concepts:- Interaction terms allow heterogeneous treatment effects by segment.- Clustered SEs by user account for intra-user correlation (repeated observations).Assumptions:- Linearity and additive effects within the model form.- No unobserved confounding (treatment as-good-as-random or controlled covariates).- Sufficient cluster count and cluster sizes for reliable clustered SEs.Limitations:- If treatment assignment is biased, estimates are biased—consider IVs or propensity-score adjustments.- Small number of clusters undermines clustered SE validity (use wild bootstrap if few clusters).- Nonlinear heterogeneous effects not captured—consider subgroup-specific models or hierarchical/mixed models for shrinkage.