agentsclimarketplace

Causal inference

Skill vermapragya/analytics-skill/skills/causal-inference

Skills library for product data scientists working with Claude

Install
npx -y skills add vermapragya/analytics-skill --skill causal-inference

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Estimates causal effects when RCTs aren't feasible — using difference-in-differences, propensity score matching, or instrumental variables — with a decision framework for which method fits. Use when the user mentions causal inference, DiD, difference-in-differences, propensity matching, IV, instrumental variable, "estimate the impact of X without an A/B test", quasi-experiment, or natural experiment.

SKILL.md

9.6 KB, as published. Nobody here has run it

Causal Inference

When to use this skill

Use when the question is "what is the causal effect of X on Y" but a clean A/B test isn't possible. Triggers:

  • "Estimate the impact of <feature/policy/launch> without an A/B test"
  • "We can't randomize. How do we measure effect?"
  • "DiD analysis"
  • "Propensity score matching"
  • "Instrumental variable"
  • "Did the launch of X cause Y to change?"

If a clean A/B test IS possible, use ab-test-design + ab-test-analysis — they always beat quasi-experimental methods.

If the question is "predict Y" not "what causes Y," use linear-regression or logistic-regression.

Decision framework: which method?

Answer these questions in order:

1. Is there a clear before/after time and a comparison group not affected by the change?
   YES → Difference-in-Differences (DiD)
   NO  → continue

2. Did some users opt into a feature/treatment based on observable characteristics?
   YES → Propensity Score Matching (PSM) or weighting
   NO  → continue

3. Is there a "lever" that affects treatment but doesn't directly affect outcome?
   YES → Instrumental Variable (IV)
   NO  → none of these will work cleanly; recommend RCT or accept correlational evidence with explicit caveats.

Required inputs

InputWhy it matters
Treatment definitionWho/what gets the intervention
Outcome definitionThe metric you want to estimate the effect on
Pre/post time periods (DiD)When did the change occur?
Comparison/control groupWho isn't treated?
ConfoundersVariables that affect both treatment and outcome
Mechanism storyWhy might treatment cause outcome?

Method 1: Difference-in-Differences (DiD)

Setup

  • A change happened at a specific time (e.g., new feature rolled out 2026-03-01).
  • One group experiences the change; another doesn't (or experiences it later).
  • You have pre-period AND post-period data for both groups.

Assumption (critical): Parallel trends

The treated and control groups would have followed parallel trends in the outcome absent the treatment. Show this by plotting both groups' outcome over time in the pre-period.

Estimation

import statsmodels.formula.api as smf

# panel data: one row per (unit, period)
model = smf.ols(
    formula='outcome ~ treated * post',
    data=df
).fit(cov_type='cluster', cov_kwds={'groups': df['unit_id']})

# The coefficient on `treated:post` is the DiD estimate

When DiD fails

  • Parallel trends violated (treated group was already trending up before treatment)
  • Spillover: control group is affected indirectly by treatment
  • Confounding shock: another event hit one group at the same time
  • Anticipation: treated units changed behavior before the treatment

Method 2: Propensity Score Matching (PSM)

Setup

  • Some users opted into / received a feature; others didn't.
  • The decision was driven by observable factors (not random).
  • You want to compare "similar" treated and control users.

Steps

  1. Fit a logistic regression: P(treated = 1 | confounders).
  2. Get propensity scores for all users.
  3. Match treated users to control users with similar scores.
  4. Compare outcomes between matched pairs.
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors

# Step 1: fit propensity
p_model = LogisticRegression().fit(df[confounders], df['treated'])
df['propensity'] = p_model.predict_proba(df[confounders])[:, 1]

# Step 2: 1:1 match (treated → nearest control)
treated = df[df['treated'] == 1]
control = df[df['treated'] == 0]
nn = NearestNeighbors(n_neighbors=1).fit(control[['propensity']])
distances, indices = nn.kneighbors(treated[['propensity']])
matched_control = control.iloc[indices.flatten()].reset_index(drop=True)

# Step 3: compute ATT (average treatment effect on the treated)
att = (treated['outcome'].values - matched_control['outcome'].values).mean()

Assumption (critical): No unmeasured confounders

Anything affecting both treatment selection AND outcome must be in the confounders list. This is unfalsifiable — you must defend it with domain knowledge.

Diagnostics

  • Common support: propensity score distributions overlap between treated and control. If they don't, can't match.
  • Balance check: after matching, treated and control should have similar means on all confounders.

Method 3: Instrumental Variable (IV)

Setup

  • Treatment is endogenous (correlated with unobserved confounders).
  • There exists an "instrument" Z that:
    1. Affects treatment (relevance)
    2. Doesn't directly affect outcome except through treatment (exclusion)
    3. Isn't correlated with unobserved confounders

Examples:

  • Random assignment to receiving an offer (instrument) → actually using it (treatment) → outcome
  • Distance from store (instrument) → store usage (treatment) → spend (outcome)
  • Encouragement design experiments

Two-stage least squares

import statsmodels.formula.api as smf
from linearmodels.iv import IV2SLS

# Treatment = f(instrument, controls)
# Outcome = f(predicted_treatment, controls)
model = IV2SLS.from_formula(
    'outcome ~ 1 + control_x + [treatment ~ instrument]',
    data=df
).fit()

Why IV is hard

  • Finding a valid instrument is rare — exclusion restriction is unfalsifiable.
  • Weak instruments (low correlation with treatment) inflate SEs and bias estimates.
  • Estimates apply only to "compliers" — users who change treatment status because of the instrument.

Workflow (any method)

  1. Force the causal question. "What is the effect of X on Y?" — not "is X associated with Y."

  2. Argue for a method using the decision framework above. State which method, why, and what assumption is being made.

  3. Show the data structure. Pre/post + control? Selection on observables? Instrument?

  4. Run the method.

  5. Run sensitivity checks:

    • DiD: plot parallel trends pre-treatment
    • PSM: balance table + common support
    • IV: first-stage F-statistic > 10
  6. Compute the effect with CI. Cluster errors at the appropriate level.

  7. State counterfactual clearly.

    • DiD: "Had the policy not been implemented, treated group would have moved X% instead of X+δ%"
    • PSM: "If untreated users had been treated, their outcome would have improved by δ"
    • IV: "For compliers (those induced by the instrument), the effect is δ"
  8. Write the readout with explicit caveats about what this evidence can and cannot support.

Output format

# Causal Inference: <treatment> → <outcome>

## Question
What is the causal effect of <treatment> on <outcome>?

## Why not a randomized experiment?
<e.g., feature is already launched, can't randomize at this point>
<e.g., regulatory constraint>
<e.g., ethical reason>

## Method: <DiD | PSM | IV>
- **Rationale:** <why this method fits this question>
- **Critical assumption:** <parallel trends | no unmeasured confounders | exclusion restriction>
- **Defense of assumption:** <evidence and domain reasoning>

## Setup
- Treatment: <definition + when>
- Outcome: <definition + grain>
- Comparison group: <who, why valid>
- Sample: N treated, N control
- Confounders adjusted for: <list>

## Estimated effect
- **Point estimate:** <δ> (units: <e.g., $ per user>)
- **95% CI:** [<low>, <high>]
- **Interpretation:** <plain English what this means>

## Sensitivity checks
- <parallel trends plot — pre-trends look parallel>
- <propensity balance table — covariates balanced after match>
- <first-stage F-stat > 10 for IV>
- <robust to alternative specifications?>

## Caveats
- <causal claim limited to ...>
- <effect estimate applies to ...>
- <what could overturn this finding>

## Decision implications
- <recommendation based on effect size and uncertainty>

Validation checks

  • Method choice is justified using the decision framework
  • Critical assumption is named and defended
  • Sensitivity check ran and is reported
  • CI reported, not just point estimate
  • Counterfactual stated in plain English
  • Caveats include what the evidence can NOT support

Edge cases & failure modes

  • DiD with non-parallel pre-trends: don't use DiD. Try synthetic control method or accept evidence is weak.
  • PSM with poor common support: large treated regions have no comparable controls. Don't match those; report effect for the overlapping region only.
  • IV with weak instrument: F-stat < 10 → IV estimates are biased toward OLS, with inflated SEs. Don't use the instrument.
  • Confounders that change WITH treatment: don't include as covariates (they're mediators). Including them removes the effect you're trying to measure.
  • Treatment effect heterogeneity: average effects may hide important variation. Report by subgroup if pre-specified.
  • External validity: effect estimated on observed population may not generalize. Note explicitly.

Scripts

  • scripts/did.py — Difference-in-differences with cluster-robust SEs.
  • scripts/psm.py — Propensity score matching with balance diagnostics.

Related skills

  • ab-test-design / ab-test-analysis — gold standard; use whenever feasible
  • linear-regression — non-causal modeling
  • logistic-regression — propensity scoring underlies PSM
  • stakeholder-readout — package the result with clear caveats

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.