Analysis must preserve the integrity of randomization through proper inference and transparent reporting.
import statsmodels.formula.api as smf
import pandas as pd
# Verify data structure
required = {'outcome', 'treatment', 'unit_id'}
assert required.issubset(df.columns), f"Missing columns: {required - set(df.columns)}"
# CRITICAL: Use cluster-robust or HC3-robust standard errors
if 'cluster_id' in df.columns:
# Cluster-robust (for cluster-randomized trials)
model = smf.ols("outcome ~ treatment", data=df).fit(
cov_type='cluster',
cov_kwds={'groups': df['cluster_id']}
)
se_type = "Cluster-robust"
else:
# HC3-robust (for individual randomization)
model = smf.ols("outcome ~ treatment", data=df).fit(cov_type='HC3')
se_type = "HC3-robust"
# Extract results
itt_effect = model.params['treatment']
itt_se = model.bse['treatment']
itt_pval = model.pvalues['treatment']
ci_low, ci_high = model.conf_int().loc['treatment']
# Context for interpretation
control_mean = df[df['treatment'] == 0]['outcome'].mean()
control_sd = df[df['treatment'] == 0]['outcome'].std()
effect_size = itt_effect / control_sd
print("INTENTION-TO-TREAT (ITT) ANALYSIS")
print("=" * 60)
print(f"Outcome: {df.columns[df.columns.get_loc('outcome')]}")
print(f"N: {len(df)} ({df['treatment'].sum()} treatment, {(~df['treatment'].astype(bool)).sum()} control)")
print(f"Standard errors: {se_type}")
print(f"\nITT Effect: {itt_effect:.4f}")
print(f"Std. Error: {itt_se:.4f}")
print(f"95% CI: [{ci_low:.4f}, {ci_high:.4f}]")
print(f"p-value: {itt_pval:.4f}")
print(f"\nControl mean: {control_mean:.4f}")
print(f"Effect size: {effect_size:.3f} SD")
print(f"Relative effect: {100*itt_effect/control_mean:.1f}%")
return model,
</code_template>
</implementation_pattern>
<examples>
<example context="balance_check" difficulty="basic">
<description>Verify randomization balance before analyzing outcomes</description>
<code>
```python
@app.cell
def check_balance(df):
# Balance checks verify randomization succeeded
# Large imbalances may require controls or suggest implementation issues
import pandas as pd
import numpy as np
from scipy import stats
# Baseline covariates (measured before randomization)
baseline_vars = ['age', 'education', 'income', 'baseline_outcome']
results = []
for var in baseline_vars:
# Means by treatment group
control_mean = df[df['treatment'] == 0][var].mean()
treat_mean = df[df['treatment'] == 1][var].mean()
# Normalized difference (Imbens & Rubin 2015)
control_var = df[df['treatment'] == 0][var].var()
treat_var = df[df['treatment'] == 1][var].var()
norm_diff = (treat_mean - control_mean) / np.sqrt((control_var + treat_var) / 2)
# T-test
t_stat, p_val = stats.ttest_ind(
df[df['treatment'] == 0][var].dropna(),
df[df['treatment'] == 1][var].dropna()
)
results.append({
'Variable': var,
'Control': f"{control_mean:.3f}",
'Treatment': f"{treat_mean:.3f}",
'Norm Diff': f"{norm_diff:.3f}",
'p-value': f"{p_val:.3f}"
})
balance_table = pd.DataFrame(results)
print("BALANCE TABLE")
print("=" * 60)
print(balance_table.to_string(index=False))
# Joint F-test
from statsmodels.formula.api import ols
formula = "treatment ~ " + " + ".join(baseline_vars)
joint_model = ols(formula, data=df).fit()
print(f"\nJoint F-test: F={joint_model.fvalue:.3f}, p={joint_model.f_pvalue:.4f}")
# Flag concerns
large_diffs = sum(abs(float(r['Norm Diff'])) > 0.25 for r in results)
if large_diffs > 0:
print(f"\nNote: {large_diffs} variables have |norm diff| > 0.25")
print("Consider including these as controls in regression")
return balance_table,
1---2name: rct-data-analysis3description: Analyze data from randomized controlled trials. Use when user mentions: treatment effects, ITT analysis, LATE, TOT, compliance analysis, attrition, balance checks, heterogeneous effects, RCT results, experimental analysis.4---56<skill_content>78<overview>9RCT analysis leverages randomization to identify causal effects with minimal assumptions. Intention-to-treat (ITT) provides policy-relevant estimates of assignment effects. Local average treatment effects (LATE/IV) recover efficacy for compliers. Balance checks verify randomization, attrition analysis guards against selection bias, and heterogeneity analysis reveals for whom treatment works.1011Analysis must preserve the integrity of randomization through proper inference and transparent reporting.12</overview>1314<mandatory_requirements>1516<requirement priority="critical">17 <name>Report ITT as Primary Result</name>18 <description>Intention-to-treat analysis MUST be reported as the main result, even with non-compliance</description>19 <rationale>ITT maintains randomization, provides policy-relevant parameter (effect of offering treatment), and prevents selection bias from endogenous compliance (Angrist & Pischke 2009)</rationale>20 <consequence>Selection bias, loss of causal interpretation, inability to make policy recommendations</consequence>21</requirement>2223<requirement priority="critical">24 <name>Cluster-Robust Standard Errors</name>25 <description>Use cluster-robust (or HC3-robust) standard errors, never classical SEs</description>26 <rationale>Clustered/robust SEs account for within-cluster correlation and heteroskedasticity. Classical SEs severely underestimate uncertainty (Bertrand et al. 2004)</rationale>27 <consequence>Type I error rates of 45% instead of 5%, false positives, invalid inference</consequence>28</requirement>2930<requirement priority="critical">31 <name>Balance Table Before Outcomes</name>32 <description>Check and report covariate balance on baseline characteristics before analyzing outcomes</description>33 <rationale>Balance checks verify randomization worked. Large imbalances suggest implementation problems or sampling variation requiring controls</rationale>34 <consequence>Hidden confounding, invalid attribution of effects, inability to diagnose randomization failures</consequence>35</requirement>3637<requirement priority="high">38 <name>Address Attrition Explicitly</name>39 <description>Report attrition rates by treatment arm, test for differential attrition, bound effects if substantial</description>40 <rationale>Differential attrition creates selection bias that undermines randomization. Must be addressed or results are invalid (Lee 2009)</rationale>41 <consequence>Selection bias, invalid causal claims, inability to distinguish true effects from attrition-driven patterns</consequence>42</requirement>4344<requirement priority="high">45 <name>Adjust for Multiple Testing When Examining Multiple Outcomes</name>46 <description>Use Bonferroni, Holm, or FDR adjustment when testing multiple hypotheses</description>47 <rationale>Multiple testing inflates Type I error rate. Without adjustment, 5% significance means 26% false positive rate with 5 tests</rationale>48 <consequence>False discoveries, overstated evidence, publication of spurious results</consequence>49</requirement>5051</mandatory_requirements>5253<assumptions>5455<assumption name="Random Attrition">56 <description>Loss to follow-up is unrelated to treatment assignment and potential outcomes</description>57 <how_to_check>Compare attrition rates by treatment arm, test for correlation with baseline characteristics</how_to_check>58 <if_violated>Use Lee bounds, IPW, or explicitly bound treatment effects under worst-case scenarios</if_violated>59</assumption>6061<assumption name="Excludability (for IV/LATE)">62 <description>Random assignment affects outcome only through treatment receipt (not directly)</description>63 <how_to_check>Conceptual argument, check for alternative mechanisms (Hawthorne effects, etc.)</how_to_check>64 <if_violated>ITT is still valid and policy-relevant; LATE estimates are biased</if_violated>65</assumption>6667<assumption name="Monotonicity (for IV/LATE)">68 <description>Assignment doesn't flip treatment direction (no defiers)</description>69 <how_to_check>Check if always-takers exist in control group and never-takers in treatment</how_to_check>70 <if_violated>LATE estimates are weighted average that may be misleading</if_violated>71</assumption>7273</assumptions>7475<thinking_process>76When analyzing RCT data:771. Check data quality and completeness782. Verify balance on baseline covariates793. Calculate and report attrition rates (overall and differential)804. Estimate ITT (primary analysis)815. Estimate LATE if non-compliance exists (secondary)826. Test for heterogeneous effects (pre-specified subgroups)837. Conduct robustness checks (alternative specifications)848. Report all results transparently (including null findings)85</thinking_process>8687<implementation_pattern>8889<code_template>90```python91@app.cell92def itt_analysis_rct(df):93 # Intention-to-treat analysis: Effect of assignment to treatment94 # This is the policy-relevant parameter even with imperfect compliance9596 import statsmodels.formula.api as smf97 import pandas as pd9899 # Verify data structure100 required = {'outcome', 'treatment', 'unit_id'}101 assert required.issubset(df.columns), f"Missing columns: {required - set(df.columns)}"102103 # CRITICAL: Use cluster-robust or HC3-robust standard errors104 if 'cluster_id' in df.columns:105 # Cluster-robust (for cluster-randomized trials)106 model = smf.ols("outcome ~ treatment", data=df).fit(107 cov_type='cluster',108 cov_kwds={'groups': df['cluster_id']}109 )110 se_type = "Cluster-robust"111 else:112 # HC3-robust (for individual randomization)113 model = smf.ols("outcome ~ treatment", data=df).fit(cov_type='HC3')114 se_type = "HC3-robust"115116 # Extract results117 itt_effect = model.params['treatment']118 itt_se = model.bse['treatment']119 itt_pval = model.pvalues['treatment']120 ci_low, ci_high = model.conf_int().loc['treatment']121122 # Context for interpretation123 control_mean = df[df['treatment'] == 0]['outcome'].mean()124 control_sd = df[df['treatment'] == 0]['outcome'].std()125 effect_size = itt_effect / control_sd126127 print("INTENTION-TO-TREAT (ITT) ANALYSIS")128 print("=" * 60)129 print(f"Outcome: {df.columns[df.columns.get_loc('outcome')]}")130 print(f"N: {len(df)} ({df['treatment'].sum()} treatment, {(~df['treatment'].astype(bool)).sum()} control)")131 print(f"Standard errors: {se_type}")132 print(f"\nITT Effect: {itt_effect:.4f}")133 print(f"Std. Error: {itt_se:.4f}")134 print(f"95% CI: [{ci_low:.4f}, {ci_high:.4f}]")135 print(f"p-value: {itt_pval:.4f}")136 print(f"\nControl mean: {control_mean:.4f}")137 print(f"Effect size: {effect_size:.3f} SD")138 print(f"Relative effect: {100*itt_effect/control_mean:.1f}%")139140 return model,141```142</code_template>143144</implementation_pattern>145146<examples>147148<example context="balance_check" difficulty="basic">149<description>Verify randomization balance before analyzing outcomes</description>150<code>151```python152@app.cell153def check_balance(df):154 # Balance checks verify randomization succeeded155 # Large imbalances may require controls or suggest implementation issues156157 import pandas as pd158 import numpy as np159 from scipy import stats160161 # Baseline covariates (measured before randomization)162 baseline_vars = ['age', 'education', 'income', 'baseline_outcome']163164 results = []165 for var in baseline_vars:166 # Means by treatment group167 control_mean = df[df['treatment'] == 0][var].mean()168 treat_mean = df[df['treatment'] == 1][var].mean()169170 # Normalized difference (Imbens & Rubin 2015)171 control_var = df[df['treatment'] == 0][var].var()172 treat_var = df[df['treatment'] == 1][var].var()173 norm_diff = (treat_mean - control_mean) / np.sqrt((control_var + treat_var) / 2)174175 # T-test176 t_stat, p_val = stats.ttest_ind(177 df[df['treatment'] == 0][var].dropna(),178 df[df['treatment'] == 1][var].dropna()179 )180181 results.append({182 'Variable': var,183 'Control': f"{control_mean:.3f}",184 'Treatment': f"{treat_mean:.3f}",185 'Norm Diff': f"{norm_diff:.3f}",186 'p-value': f"{p_val:.3f}"187 })188189 balance_table = pd.DataFrame(results)190191 print("BALANCE TABLE")192 print("=" * 60)193 print(balance_table.to_string(index=False))194195 # Joint F-test196 from statsmodels.formula.api import ols197 formula = "treatment ~ " + " + ".join(baseline_vars)198 joint_model = ols(formula, data=df).fit()199200 print(f"\nJoint F-test: F={joint_model.fvalue:.3f}, p={joint_model.f_pvalue:.4f}")201202 # Flag concerns203 large_diffs = sum(abs(float(r['Norm Diff'])) > 0.25 for r in results)204 if large_diffs > 0:205 print(f"\nNote: {large_diffs} variables have |norm diff| > 0.25")206 print("Consider including these as controls in regression")207208 return balance_table,209```210</code>211<lesson>212Balance checks use baseline (pre-randomization) covariates only. Large imbalances (|norm diff| > 0.25) don't invalidate randomization but suggest including those variables as controls to improve precision. Never use post-treatment variables for balance checks.213</lesson>214</example>215216</examples>217218<common_mistakes>219220<mistake severity="critical">221 <what>Not using cluster-robust or heteroskedasticity-robust standard errors</what>222 <consequence>Type I error rates can be 45% instead of 5%, leading to massive false positive rates</consequence>223 <prevention>ALWAYS use cov_type='cluster' or cov_type='HC3'. Never use default SEs.</prevention>224</mistake>225226<mistake severity="critical">227 <what>Not reporting ITT when non-compliance exists</what>228 <consequence>Selection bias from analyzing compliers only, loss of causal interpretation</consequence>229 <prevention>ALWAYS report ITT as primary result. LATE/TOT are secondary sensitivity analyses.</prevention>230</mistake>231232<mistake severity="high">233 <what>Ignoring attrition or not testing for differential attrition</what>234 <consequence>Selection bias can completely invalidate results if attrition is differential</consequence>235 <prevention>Report attrition by arm, test differential attrition, use Lee bounds if substantial</prevention>236</mistake>237238<mistake severity="high">239 <what>P-hacking through subgroups without adjustment</what>240 <consequence>Finding "significant" effects that are just Type I errors from multiple testing</consequence>241 <prevention>Pre-specify subgroups in PAP, use Bonferroni/Holm adjustment, report all tests</prevention>242</mistake>243244<mistake severity="medium">245 <what>Using baseline covariates that were measured post-randomization</what>246 <consequence>Conditioning on post-treatment variables creates bias (bad controls)</consequence>247 <prevention>Only use covariates measured before randomization in balance checks and controls</prevention>248</mistake>249250</common_mistakes>251252<interpretation_guide>253254<reporting_standards>255Minimum reporting for RCT results:256- Sample size (by treatment arm)257- Balance table on baseline characteristics258- Attrition rates (overall and by arm)259- ITT estimates with cluster-robust/HC3 SEs260- 95% confidence intervals261- Control group mean (for context)262- Effect size in SD units263- P-values (exact, not <0.05)264- Number of clusters if cluster-randomized265</reporting_standards>266267<red_flags>268Results are questionable if:269- No balance table provided270- Classical (non-robust) standard errors used271- ITT not reported for study with non-compliance272- Attrition >20% without bounding analysis273- Multiple outcomes tested without adjustment274- Post-treatment covariates included as controls275- Results only reported for "compliers" without ITT276</red_flags>277278</interpretation_guide>279280<references>281<paper>Angrist, J. D., & Pischke, J. S. (2009). Mostly Harmless Econometrics: An Empiricist's Companion. Princeton University Press.</paper>282<paper>Bertrand, M., Duflo, E., & Mullainathan, S. (2004). How much should we trust differences-in-differences estimates? Quarterly Journal of Economics, 119(1), 249-275.</paper>283<paper>Lee, D. S. (2009). Training, wages, and sample selection: Estimating sharp bounds on treatment effects. Review of Economic Studies, 76(3), 1071-1102.</paper>284<paper>Imbens, G. W., & Rubin, D. B. (2015). Causal Inference in Statistics, Social, and Biomedical Sciences. Cambridge University Press.</paper>285</references>286287</skill_content>