Volume 2: Organizational Intelligence Platforms

Pattern 19: Causal Inference

Intent

Distinguish correlation from causation to understand which interventions, behaviors, and conditions truly cause desired outcomes versus those that merely correlate, enabling evidence-based decision making and avoiding resource waste on ineffective strategies.

Also Known As

  • Causality Analysis
  • Cause-and-Effect Analysis
  • Treatment Effect Estimation
  • Counterfactual Reasoning
  • A/B Testing Framework

Problem

Correlation is not causation, but we act like it is.

Sarah's "findings": - Families who volunteer 5+ hours have 95% retention - Families who attend 3+ events have 90% retention - Families with annual commitments have 92% retention

Sarah's decisions: - "Let's require 5 hours volunteering!" (to boost retention) - "Let's mandate 3 events!" (to boost retention) - "Let's push annual commitments!" (to boost retention)

The problem: Confounding variables!

Scenario 1: Volunteer Hours - Correlation: Volunteer → High retention (95%) - True causality: Engagement → Both volunteering AND retention - Confound: Engaged families volunteer AND stay (chicken/egg) - Reality: Forcing unmotivated families to volunteer won't help

Scenario 2: Event Attendance - Correlation: Events → High retention (90%) - True causality: Social connections → Both events AND retention - Confound: Families with friends attend events AND stay - Reality: Dragging isolated families to events won't create bonds

Scenario 3: Annual Commitments - Correlation: Annual → High retention (92%) - True causality: Already committed → Choose annual - Confound: Self-selection (only committed families choose annual) - Reality: Pressuring uncertain families into annual = refunds

Without causal inference: - Confuse correlation with causation - Waste resources on ineffective interventions - Miss actual causal mechanisms - Can't learn what truly works - Make decisions on spurious patterns

With causal inference: - Test interventions rigorously (A/B tests, experiments) - Account for confounding variables - Estimate true treatment effects - Invest in what actually works - Build evidence-based playbook

Context

When this pattern applies:

  • Making strategic decisions (high stakes)
  • Evaluating intervention effectiveness
  • Multiple potential causes (need to isolate effects)
  • Historical data available for analysis
  • Can run experiments (ideal)

When this pattern may not be needed:

  • Obvious causality (payment required → access granted)
  • Low stakes decisions (cheap to try)
  • No confounding variables present
  • Can't collect rigorous data

Forces

Competing concerns:

1. Rigor vs Speed - Rigorous experiments take months - Quick observational analysis takes days - Balance: Observational first, experiments for critical decisions

2. Experimental vs Observational - Experiments = gold standard but costly/slow - Observational = fast but confounds remain - Balance: Use best method available for importance

3. Statistical vs Practical - Statistical significance (p < 0.05) - Practical significance (worth the effort?) - Balance: Require both

4. Internal vs External Validity - Internal: Did it work in THIS test? - External: Will it work in the real world? - Balance: Test in representative conditions

5. Complexity vs Interpretability - Complex methods (instrumental variables) - Simple methods (t-tests) - Balance: Start simple, increase complexity as needed

Solution

Build causal inference framework using multiple methods:

Tier 1: Randomized Controlled Trials (RCT) - Gold standard: Random assignment to treatment/control - Eliminates confounding by design - Costly, slow, but definitive

Tier 2: Quasi-Experimental Methods When RCTs not feasible: - Propensity Score Matching - Regression Discontinuity - Difference-in-Differences - Instrumental Variables

Tier 3: Observational with Controls - Regression with covariates - Stratification by confounders - Sensitivity analysis

Framework for causal questions:

1. State hypothesis: "Does X cause Y?"
2. Identify confounders: What else could explain correlation?
3. Choose method: RCT? Quasi-experimental? Observational?
4. Collect data: Treatment, outcome, confounders
5. Estimate effect: Statistical analysis
6. Validate: Robustness checks, sensitivity analysis
7. Interpret: Practical significance, generalizability

Structure

Causal Inference Tables

-- Store experiments (A/B tests, RCTs)
CREATE TABLE experiments (
  experiment_id INT PRIMARY KEY IDENTITY(1,1),

  experiment_name VARCHAR(200) NOT NULL,
  hypothesis NVARCHAR(1000),

  -- Design
  experiment_type VARCHAR(50),  -- 'rct', 'quasi_experimental', 'observational'
  treatment_description NVARCHAR(1000),
  control_description NVARCHAR(1000),

  -- Randomization
  randomization_method VARCHAR(100),  -- 'simple', 'stratified', 'block'
  randomization_seed INT,

  -- Sample size
  planned_sample_size INT,
  actual_sample_size INT,
  power_calculation NVARCHAR(500),

  -- Timeline
  start_date DATE,
  end_date DATE,

  -- Results
  primary_outcome VARCHAR(100),
  treatment_effect DECIMAL(10,4),
  standard_error DECIMAL(10,4),
  p_value DECIMAL(10,8),
  confidence_interval_lower DECIMAL(10,4),
  confidence_interval_upper DECIMAL(10,4),

  -- Interpretation
  statistically_significant BIT,
  practically_significant BIT,
  conclusion NVARCHAR(MAX),

  status VARCHAR(50) DEFAULT 'planned',  -- 'planned', 'running', 'completed', 'cancelled'
  created_date DATETIME2 DEFAULT GETDATE()
);

-- Track experiment assignments
CREATE TABLE experiment_assignments (
  assignment_id INT PRIMARY KEY IDENTITY(1,1),
  experiment_id INT NOT NULL,
  family_id INT NOT NULL,

  -- Assignment
  treatment_group VARCHAR(50),  -- 'treatment', 'control'
  assignment_date DATETIME2 DEFAULT GETDATE(),

  -- Baseline covariates (pre-treatment)
  baseline_engagement_score DECIMAL(5,2),
  baseline_risk_score DECIMAL(5,2),
  baseline_tenure_days INT,

  -- Outcome measurement
  outcome_measured BIT DEFAULT 0,
  outcome_value DECIMAL(10,2),
  outcome_date DATETIME2,

  -- Compliance
  received_treatment BIT,  -- Did they actually get treatment?

  CONSTRAINT FK_assignment_experiment FOREIGN KEY (experiment_id)
    REFERENCES experiments(experiment_id),
  CONSTRAINT FK_assignment_family FOREIGN KEY (family_id)
    REFERENCES families(family_id)
);

-- Store propensity scores for matching
CREATE TABLE propensity_scores (
  score_id INT PRIMARY KEY IDENTITY(1,1),
  family_id INT NOT NULL,

  -- Analysis context
  treatment_variable VARCHAR(100),  -- What we're studying
  analysis_date DATE,

  -- Propensity score (probability of treatment given covariates)
  propensity_score DECIMAL(8,6),

  -- Matched control (if using matching)
  matched_control_id INT,
  match_quality DECIMAL(5,4),

  CONSTRAINT FK_propensity_family FOREIGN KEY (family_id)
    REFERENCES families(family_id)
);

-- Track causal findings
CREATE TABLE causal_findings (
  finding_id INT PRIMARY KEY IDENTITY(1,1),

  -- Question
  causal_question NVARCHAR(500),  -- "Does mentoring cause retention?"

  -- Method used
  analysis_method VARCHAR(100),  -- 'rct', 'propensity_matching', 'regression', etc.
  experiment_id INT,

  -- Estimate
  estimated_effect DECIMAL(10,4),
  standard_error DECIMAL(10,4),
  confidence_interval_lower DECIMAL(10,4),
  confidence_interval_upper DECIMAL(10,4),
  p_value DECIMAL(10,8),

  -- Interpretation
  direction VARCHAR(20),  -- 'positive', 'negative', 'no_effect'
  magnitude VARCHAR(20),  -- 'large', 'medium', 'small'
  practical_significance BIT,

  -- Evidence quality
  evidence_strength VARCHAR(20),  -- 'strong', 'moderate', 'weak'
  confounding_controlled BIT,

  conclusion NVARCHAR(MAX),
  analysis_date DATE,

  CONSTRAINT FK_finding_experiment FOREIGN KEY (experiment_id)
    REFERENCES experiments(experiment_id)
);

Implementation

Randomized Controlled Trial

class RandomizedControlledTrial {
  constructor(db) {
    this.db = db;
  }

  async setupExperiment(config) {
    // Create experiment
    const expResult = await this.db.query(`
      INSERT INTO experiments (
        experiment_name,
        hypothesis,
        experiment_type,
        treatment_description,
        control_description,
        randomization_method,
        planned_sample_size,
        primary_outcome,
        start_date,
        status
      ) VALUES (?, ?, 'rct', ?, ?, ?, ?, ?, CURRENT_DATE, 'planned')
      RETURNING experiment_id
    `, [
      config.name,
      config.hypothesis,
      config.treatmentDescription,
      config.controlDescription,
      config.randomizationMethod || 'simple',
      config.sampleSize,
      config.primaryOutcome
    ]);

    const experimentId = expResult[0].experiment_id;

    // Select eligible families
    const eligibleFamilies = await this.getEligibleFamilies(config.eligibilityCriteria);

    // Power analysis (do we have enough participants?)
    const powerAnalysis = this.calculatePower(eligibleFamilies.length, config.expectedEffect);
    if (powerAnalysis.power < 0.80) {
      console.warn(`Warning: Statistical power only ${(powerAnalysis.power * 100).toFixed(0)}% with ${eligibleFamilies.length} participants. Need ${powerAnalysis.recommendedN} for 80% power.`);
    }

    // Randomize to treatment/control
    const assignments = this.randomize(eligibleFamilies, config.randomizationMethod);

    // Save assignments
    for (const assignment of assignments) {
      await this.db.query(`
        INSERT INTO experiment_assignments (
          experiment_id,
          family_id,
          treatment_group,
          baseline_engagement_score,
          baseline_risk_score,
          baseline_tenure_days
        ) VALUES (?, ?, ?, ?, ?, ?)
      `, [
        experimentId,
        assignment.family_id,
        assignment.group,
        assignment.baseline_engagement_score,
        assignment.baseline_risk_score,
        assignment.baseline_tenure_days
      ]);
    }

    return {
      experiment_id: experimentId,
      n_treatment: assignments.filter(a => a.group === 'treatment').length,
      n_control: assignments.filter(a => a.group === 'control').length,
      power: powerAnalysis.power
    };
  }

  async getEligibleFamilies(criteria) {
    // Build SQL based on eligibility criteria
    let sql = `
      SELECT 
        f.family_id,
        fem.engagement_score as baseline_engagement_score,
        ra.withdrawal_risk as baseline_risk_score,
        DATEDIFF(NOW(), f.enrollment_date) as baseline_tenure_days
      FROM families f
      JOIN family_engagement_metrics fem ON f.family_id = fem.family_id
      LEFT JOIN risk_assessments ra ON f.family_id = ra.family_id
      WHERE 1=1
    `;

    const params = [];

    if (criteria.minEngagement) {
      sql += ` AND fem.engagement_score >= ?`;
      params.push(criteria.minEngagement);
    }

    if (criteria.maxRisk) {
      sql += ` AND ra.withdrawal_risk <= ?`;
      params.push(criteria.maxRisk);
    }

    if (criteria.minTenureDays) {
      sql += ` AND DATEDIFF(NOW(), f.enrollment_date) >= ?`;
      params.push(criteria.minTenureDays);
    }

    return await this.db.query(sql, params);
  }

  randomize(families, method = 'simple') {
    const assignments = [];

    if (method === 'simple') {
      // Simple random assignment (50/50 split)
      for (const family of families) {
        const group = Math.random() < 0.5 ? 'treatment' : 'control';
        assignments.push({ ...family, group });
      }

    } else if (method === 'stratified') {
      // Stratified by engagement level (ensure balance)
      const lowEngagement = families.filter(f => f.baseline_engagement_score < 50);
      const medEngagement = families.filter(f => f.baseline_engagement_score >= 50 && f.baseline_engagement_score < 75);
      const highEngagement = families.filter(f => f.baseline_engagement_score >= 75);

      for (const stratum of [lowEngagement, medEngagement, highEngagement]) {
        for (const family of stratum) {
          const group = Math.random() < 0.5 ? 'treatment' : 'control';
          assignments.push({ ...family, group });
        }
      }

    } else if (method === 'block') {
      // Block randomization (guarantees exactly 50/50)
      const shuffled = families.sort(() => Math.random() - 0.5);
      const midpoint = Math.floor(shuffled.length / 2);

      shuffled.forEach((family, i) => {
        const group = i < midpoint ? 'treatment' : 'control';
        assignments.push({ ...family, group });
      });
    }

    return assignments;
  }

  calculatePower(n, expectedEffect, alpha = 0.05) {
    // Simplified power calculation
    // Real implementation would use statistical libraries

    const effectSize = expectedEffect / 15;  // Cohen's d (assuming SD ~15)
    const criticalZ = 1.96;  // For alpha=0.05, two-tailed

    // Approximate power calculation
    const ncp = effectSize * Math.sqrt(n / 2);  // Non-centrality parameter
    const power = 1 - this.normalCDF(criticalZ - ncp);  // Very rough approximation

    // Recommend sample size for 80% power
    const recommendedN = Math.ceil((2 * Math.pow(criticalZ + 0.84, 2)) / Math.pow(effectSize, 2));

    return {
      power: Math.max(0, Math.min(1, power)),
      recommendedN: recommendedN
    };
  }

  normalCDF(x) {
    // Rough approximation of normal CDF
    return 0.5 * (1 + this.erf(x / Math.sqrt(2)));
  }

  erf(x) {
    // Approximation of error function
    const a1 = 0.254829592;
    const a2 = -0.284496736;
    const a3 = 1.421413741;
    const a4 = -1.453152027;
    const a5 = 1.061405429;
    const p = 0.3275911;

    const sign = x < 0 ? -1 : 1;
    x = Math.abs(x);

    const t = 1 / (1 + p * x);
    const y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);

    return sign * y;
  }

  async analyzeResults(experimentId) {
    // Get outcome data
    const data = await this.db.query(`
      SELECT 
        ea.treatment_group,
        ea.outcome_value,
        ea.baseline_engagement_score
      FROM experiment_assignments ea
      WHERE ea.experiment_id = ?
        AND ea.outcome_measured = 1
    `, [experimentId]);

    const treatment = data.filter(d => d.treatment_group === 'treatment');
    const control = data.filter(d => d.treatment_group === 'control');

    // Calculate means
    const treatmentMean = treatment.reduce((sum, d) => sum + d.outcome_value, 0) / treatment.length;
    const controlMean = control.reduce((sum, d) => sum + d.outcome_value, 0) / control.length;

    const effect = treatmentMean - controlMean;

    // Calculate standard error
    const treatmentVar = this.variance(treatment.map(d => d.outcome_value));
    const controlVar = this.variance(control.map(d => d.outcome_value));
    const pooledVar = ((treatment.length - 1) * treatmentVar + (control.length - 1) * controlVar) / 
                      (treatment.length + control.length - 2);
    const standardError = Math.sqrt(pooledVar * (1/treatment.length + 1/control.length));

    // T-test
    const tStat = effect / standardError;
    const df = treatment.length + control.length - 2;
    const pValue = this.tTestPValue(tStat, df);

    // Confidence interval (95%)
    const tCritical = 1.96;  // Approximate for large samples
    const ciLower = effect - (tCritical * standardError);
    const ciUpper = effect + (tCritical * standardError);

    // Update experiment with results
    await this.db.query(`
      UPDATE experiments
      SET 
        treatment_effect = ?,
        standard_error = ?,
        p_value = ?,
        confidence_interval_lower = ?,
        confidence_interval_upper = ?,
        statistically_significant = ?,
        status = 'completed'
      WHERE experiment_id = ?
    `, [
      effect,
      standardError,
      pValue,
      ciLower,
      ciUpper,
      pValue < 0.05 ? 1 : 0,
      experimentId
    ]);

    return {
      treatment_mean: treatmentMean,
      control_mean: controlMean,
      treatment_effect: effect,
      standard_error: standardError,
      t_statistic: tStat,
      p_value: pValue,
      confidence_interval: [ciLower, ciUpper],
      statistically_significant: pValue < 0.05,
      sample_size: { treatment: treatment.length, control: control.length }
    };
  }

  variance(values) {
    const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
    return values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / (values.length - 1);
  }

  tTestPValue(tStat, df) {
    // Very rough approximation - real implementation would use statistical library
    const abst = Math.abs(tStat);
    if (abst > 3) return 0.001;
    if (abst > 2.5) return 0.01;
    if (abst > 2) return 0.05;
    if (abst > 1.5) return 0.15;
    return 0.50;
  }
}

module.exports = RandomizedControlledTrial;

Propensity Score Matching (Python)

# propensity_matching.py
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors

class PropensityScoreMatcher:
    """
    Propensity Score Matching for causal inference from observational data

    Use when you can't randomize but want to estimate treatment effect
    """

    def __init__(self):
        self.propensity_model = LogisticRegression()
        self.matcher = None

    def estimate_propensity_scores(self, X, treatment):
        """
        Estimate probability of receiving treatment given covariates

        X: DataFrame of covariates (engagement, tenure, etc.)
        treatment: Binary array (1=received treatment, 0=control)
        """
        self.propensity_model.fit(X, treatment)
        propensity_scores = self.propensity_model.predict_proba(X)[:, 1]

        return propensity_scores

    def match_samples(self, propensity_scores, treatment, caliper=0.1):
        """
        Match treated units to control units with similar propensity scores

        caliper: Maximum difference in propensity score for match
        """
        treated_idx = np.where(treatment == 1)[0]
        control_idx = np.where(treatment == 0)[0]

        treated_scores = propensity_scores[treated_idx].reshape(-1, 1)
        control_scores = propensity_scores[control_idx].reshape(-1, 1)

        # Find nearest neighbor for each treated unit
        self.matcher = NearestNeighbors(n_neighbors=1)
        self.matcher.fit(control_scores)

        distances, indices = self.matcher.kneighbors(treated_scores)

        # Create matched pairs
        matches = []
        for i, (dist, idx) in enumerate(zip(distances, indices)):
            if dist[0] <= caliper:
                treated_unit = treated_idx[i]
                control_unit = control_idx[idx[0]]
                matches.append({
                    'treated_id': treated_unit,
                    'control_id': control_unit,
                    'distance': dist[0]
                })

        return pd.DataFrame(matches)

    def estimate_treatment_effect(self, matches, outcomes):
        """
        Calculate Average Treatment Effect on the Treated (ATT)

        matches: DataFrame from match_samples
        outcomes: Array of outcome values
        """
        treated_outcomes = outcomes[matches['treated_id']]
        control_outcomes = outcomes[matches['control_id']]

        att = np.mean(treated_outcomes - control_outcomes)
        se = np.std(treated_outcomes - control_outcomes) / np.sqrt(len(matches))

        return {
            'att': att,
            'standard_error': se,
            'confidence_interval': [att - 1.96*se, att + 1.96*se],
            'n_matches': len(matches)
        }

    def check_balance(self, X, treatment, propensity_scores):
        """
        Check covariate balance before/after matching
        """
        treated = X[treatment == 1]
        control = X[treatment == 0]

        balance_stats = {}
        for col in X.columns:
            treated_mean = treated[col].mean()
            control_mean = control[col].mean()
            pooled_std = np.sqrt((treated[col].var() + control[col].var()) / 2)

            # Standardized mean difference
            smd = (treated_mean - control_mean) / pooled_std

            balance_stats[col] = {
                'treated_mean': treated_mean,
                'control_mean': control_mean,
                'smd': smd
            }

        return pd.DataFrame(balance_stats).T

Usage Example

const rct = new RandomizedControlledTrial(db);

// Setup experiment: Does mentoring cause retention?
const experiment = await rct.setupExperiment({
  name: 'Mentoring Impact on Retention',
  hypothesis: 'Families receiving mentor support have 15pp higher retention',
  treatmentDescription: 'Assigned experienced family mentor, monthly check-ins',
  controlDescription: 'Standard support (no mentor)',
  randomizationMethod: 'stratified',
  sampleSize: 100,
  expectedEffect: 15,
  primaryOutcome: 'retention_rate',
  eligibilityCriteria: {
    minEngagement: 40,
    maxRisk: 80,
    minTenureDays: 30
  }
});

console.log(`
Experiment Setup Complete:
  Experiment ID: ${experiment.experiment_id}
  Treatment Group: ${experiment.n_treatment} families
  Control Group: ${experiment.n_control} families
  Statistical Power: ${(experiment.power * 100).toFixed(0)}%
`);

// ... Wait for experiment to run (3-6 months) ...

// Analyze results
const results = await rct.analyzeResults(experiment.experiment_id);

console.log(`
Experiment Results:
  Treatment Mean: ${results.treatment_mean.toFixed(1)}%
  Control Mean: ${results.control_mean.toFixed(1)}%
  Treatment Effect: ${results.treatment_effect.toFixed(1)} percentage points
  95% CI: [${results.confidence_interval[0].toFixed(1)}, ${results.confidence_interval[1].toFixed(1)}]
  P-value: ${results.p_value.toFixed(4)}
  Statistically Significant: ${results.statistically_significant ? 'YES' : 'NO'}

Interpretation:
  ${results.statistically_significant 
    ? `Mentoring causes a ${results.treatment_effect.toFixed(1)}pp increase in retention (p<0.05). This is a real effect, not due to chance.`
    : `No statistically significant effect detected. Either mentoring doesn't work, or sample size too small.`
  }
`);

// Example output:
// Experiment Results:
//   Treatment Mean: 87.3%
//   Control Mean: 72.5%
//   Treatment Effect: 14.8 percentage points
//   95% CI: [8.2, 21.4]
//   P-value: 0.0023
//   Statistically Significant: YES
//   
// Interpretation:
//   Mentoring causes a 14.8pp increase in retention (p<0.05). This is a real effect, not due to chance.

Variations

By Method Rigor

Tier 1: Randomized Controlled Trial - Gold standard - Random assignment eliminates confounding - Expensive, slow, definitive

Tier 2: Quasi-Experimental - Propensity Score Matching - Regression Discontinuity - Difference-in-Differences - Instrumental Variables

Tier 3: Observational + Controls - Multiple regression - Stratification - Sensitivity analysis

By Application

Product Features: - A/B testing - Multivariate testing - Sequential testing

Interventions: - RCTs - Stepped wedge design - Cluster randomization

Policy Evaluation: - Natural experiments - Regression discontinuity - Difference-in-differences

Consequences

Benefits

1. True causality Know mentoring CAUSES retention (not just correlates).

2. Avoid waste Don't invest in interventions that don't work.

3. Optimize resource allocation Double down on what actually works.

4. Evidence-based Decisions backed by rigorous evidence.

5. Confounding controlled Account for alternative explanations.

6. Effect size quantified Not just "it works" but "14.8pp improvement."

Costs

1. Time investment RCTs take 3-6 months to complete.

2. Complexity Requires statistical expertise.

3. Sample size needs Need 100+ participants for power.

4. Ethical constraints Can't always withhold potentially beneficial treatment.

5. External validity Results may not generalize beyond test population.

6. Implementation overhead Tracking assignments, outcomes, compliance.

Sample Code

Simple A/B test analysis:

async function analyzeABTest(testName) {
  const data = await db.query(`
    SELECT 
      treatment_group,
      COUNT(*) as n,
      AVG(outcome_value) as mean,
      STDDEV(outcome_value) as stddev
    FROM experiment_assignments ea
    JOIN experiments e ON ea.experiment_id = e.experiment_id
    WHERE e.experiment_name = ?
      AND ea.outcome_measured = 1
    GROUP BY treatment_group
  `, [testName]);

  const treatment = data.find(d => d.treatment_group === 'treatment');
  const control = data.find(d => d.treatment_group === 'control');

  const effect = treatment.mean - control.mean;
  const pooledStd = Math.sqrt(
    (Math.pow(treatment.stddev, 2) * (treatment.n - 1) +
     Math.pow(control.stddev, 2) * (control.n - 1)) /
    (treatment.n + control.n - 2)
  );

  const se = pooledStd * Math.sqrt(1/treatment.n + 1/control.n);
  const tStat = effect / se;

  return {
    treatment_mean: treatment.mean,
    control_mean: control.mean,
    effect: effect,
    relative_lift: (effect / control.mean) * 100,
    t_statistic: tStat,
    significant: Math.abs(tStat) > 1.96
  };
}

Known Uses

Homeschool Co-op Intelligence Platform - RCT: Mentoring program (14.8pp retention improvement, p<0.05) - A/B test: Payment reminder timing (5.2pp on-time improvement) - Observational: Annual commitment effect (13pp, but self-selection)

Tech Companies: - Google: 1000s of A/B tests annually - Amazon: Test everything - Netflix: Algorithm improvements

Healthcare: - Clinical trials (FDA requirement) - Treatment effectiveness - Drug development

Education: - What Works Clearinghouse - IES randomized trials - EdTech effectiveness studies

Requires: - Pattern 1: Universal Event Log - outcome data - Pattern 26: Feedback Loop Implementation - tracking experiments

Enables: - Pattern 15: Intervention Recommendation - recommend proven interventions - Pattern 18: Opportunity Mining - validate opportunities - Pattern 24: Template-Based Communication - A/B test messages

Enhanced by: - Pattern 12: Risk Stratification Models - predict treatment heterogeneity - Pattern 13: Confidence Scoring - confidence in causal claims

References

Academic Foundations

  • Pearl, Judea (2009). Causality: Models, Reasoning, and Inference (2nd ed.). Cambridge University Press. ISBN: 978-0521895606 - Foundational causal inference text
  • Imbens, Guido W., and Donald B. Rubin (2015). Causal Inference for Statistics, Social, and Biomedical Sciences. Cambridge University Press. ISBN: 978-0521885881
  • Angrist, Joshua D., and Jörn-Steffen Pischke (2009). Mostly Harmless Econometrics. Princeton University Press. ISBN: 978-0691120355 - Practical causal inference
  • Kohavi, Ron, Diane Tang, and Ya Xu (2020). Trustworthy Online Controlled Experiments. Cambridge University Press. ISBN: 978-1108724265 - A/B testing and experimentation

Time Series Pattern Recognition

  • Box, G.E.P., et al. (2015). Time Series Analysis: Forecasting and Control (5th ed.). Wiley. ISBN: 978-1118675021
  • Hyndman, R.J., & Athanasopoulos, G. (2021). Forecasting: Principles and Practice (3rd ed.). https://otexts.com/fpp3/ - Free online
  • Time Series Classification: Bagnall, A., et al. (2017). "The great time series classification bake off." Data Mining and Knowledge Discovery 31(3): 606-660.

Practical Implementation

Causal Inference Methods

  • Pattern 10: Engagement Velocity Tracking - Velocity as temporal pattern
  • Pattern 12: Risk Stratification Models - Use causal evidence in predictions
  • Pattern 20: Natural Experiments - Test causality in natural settings
  • Volume 3, Pattern 16: Temporal Validation - Validate temporal data

Tools & Services