LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 196 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnML Algorithms & EvaluationCausal Inference and Counterfactuals
📊MediumEvaluation & Benchmarks

Causal Inference and Counterfactuals

Separate correlation from intervention, diagnose Simpson's reversal, estimate adjusted treatment effects, and inspect overlap before trusting observational AI evaluations.

22 min read
Learning path
Step 44 of 196 in the full curriculum
Experiment Design and A/B TestingPyTorch Training Loops

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

An AI assistant resolves 54% of its tickets, while the old workflow resolves 76%. A release report calls the assistant harmful and recommends pulling the plug. Then the tickets are split by pre-routing risk: the assistant's observed resolution rate is ten percentage points higher inside both easy and difficult groups. Which comparison should drive the rollout decision?

Experiment Design and A/B Testing Foundations introduced fair comparisons. Causal inference asks whether an action caused an observed outcome, then makes the assignment assumptions behind that conclusion mathematically explicit.[1]Reference 1Causal Inference in Statistics: A Primerhttps://bayes.cs.ucla.edu/PRIMER/ You can't rely on the unadjusted aggregate label on the report.

Assistant tickets are 10% low risk and 90% high risk; control tickets are 90% low risk and 10% high risk. Raw success is 54% versus 76%. Standardizing both workflows to 70% low risk and 30% high risk gives 78% versus 68%.
The raw arms have opposite case mixes. Giving both workflows the target mix changes their success-rate comparison; it doesn't by itself establish causality.

Watch the aggregate reverse the group results

Work with a synthetic log of 200 tickets. Treatment means routing a ticket to a fixed assistant workflow; control means routing it to the existing human workflow. Success means resolution within 24 hours, measured the exact same way in both arms. Risk is recorded before routing, not inferred from the assistant's response.

Within each risk group, the assistant's observed success rate is ten percentage points higher. Before looking at the totals, predict what the aggregate would say if both arms had the same risk mix.

Ticket riskAssistant successControl successObserved difference
Low9/10 = 90%72/90 = 80%+10 percentage points
High45/90 = 50%4/10 = 40%+10 percentage points
Naive aggregate54/100 = 54%76/100 = 76%-22 percentage points

The totals compare different populations. The assistant receives 90 difficult tickets; control receives 90 easy tickets. That unequal mix flips the aggregate direction.

This reversal is Simpson's paradox: group composition changes the direction of the aggregated association. Observational counts alone still don't establish a causal improvement.

The next snippet computes both within-group differences and the reversed aggregate. It reports percentage points, not relative percentage changes: 90% minus 80% is 10 points, whereas the relative increase is 10/80 = 12.5%.

inspect-confounded-ticket-outcomes.py
1groups = { 2 "low": {"assistant": (9, 10), "control": (72, 90)}, 3 "high": {"assistant": (45, 90), "control": (4, 10)}, 4} 5 6for risk, counts in groups.items(): 7 assistant_success, assistant_total = counts["assistant"] 8 control_success, control_total = counts["control"] 9 difference = assistant_success / assistant_total - control_success / control_total 10 print(f"{risk:>4} risk difference: {100 * difference:+.0f} percentage points") 11 12assistant_rate = (9 + 45) / (10 + 90) 13control_rate = (72 + 4) / (90 + 10) 14print(f"naive aggregate difference: {100 * (assistant_rate - control_rate):+.0f} percentage points")
Output
1low risk difference: +10 percentage points 2high risk difference: +10 percentage points 3naive aggregate difference: -22 percentage points

The reversal disappears once both workflows are compared against the same risk distribution. That repairs the visible mix, but it still leaves a deeper question: for one assistant-handled ticket, what would control have done?

Pearl's ladder: association, intervention, and counterfactuals

Standard machine learning models optimize conditional expectations like P(Y∣X)P(Y \mid X)P(Y∣X). When evaluating systems, this observational conditioning mixes the effect of the model with the behavior of upstream routers, triage heuristics, and customer selection. Judea Pearl organized causal queries into three distinct levels known as the Ladder of Causation.[1]Reference 1Causal Inference in Statistics: A Primerhttps://bayes.cs.ucla.edu/PRIMER/

Rung 1: association (seeing)

Observational queries ask: If we see event XXX, what's our belief about YYY? In notation, this is P(Y∣X)P(Y \mid X)P(Y∣X). This is the native language of statistical learning, correlation, and passive monitoring. In the support log, P(Y=1∣A=1)=0.54P(Y=1 \mid A=1) = 0.54P(Y=1∣A=1)=0.54. This number answers what happened to tickets observed under the assistant, but it reflects the difficult queue the assistant was assigned to rather than the tool's standalone capability.

Rung 2: intervention (doing)

Interventional queries ask: What happens to YYY if we actively set variable XXX to value xxx? Pearl denotes this operation with the do⁡(⋅)\operatorname{do}(\cdot)do(⋅) operator: P(Y∣do⁡(A=1))P(Y \mid \operatorname{do}(A=1))P(Y∣do(A=1)). Intervening isn't passive conditioning. An intervention cuts the natural causal arrows flowing into AAA, replacing the upstream routing rule with an external mandate. If we force every incoming ticket to the assistant regardless of risk, the resolution rate is P(Y=1∣do⁡(A=1))P(Y=1 \mid \operatorname{do}(A=1))P(Y=1∣do(A=1)), which doesn't equal P(Y=1∣A=1)P(Y=1 \mid A=1)P(Y=1∣A=1) whenever confounding exists.

Rung 3: counterfactuals (imagining)

Counterfactual queries ask retrospective questions about specific units: Given that ticket #102 received the legacy control workflow and failed to resolve within 24 hours, what would have happened if we had routed that exact ticket to the assistant? In notation, this queries P(YA=1∣A=0,Y=0)P(Y_{A=1} \mid A=0, Y=0)P(YA=1​∣A=0,Y=0). Rung 3 lives in hypothetical alternative worlds. It enables individual credit assignment, root-cause debugging, and algorithmic fairness audits that observational data alone can't answer.

A dashboard shows that users who trigger an LLM code explanation feature retain 15% better than users who don't. Does this justify rolling out the feature to all users?

Answer

No. That comparison sits on Rung 1: P(Retain | Feature = 1) vs P(Retain | Feature = 0). Highly motivated users might use the feature more frequently. Deciding a rollout requires an interventional query on Rung 2: P(Retain | do(Feature = 1)), which separates user motivation from feature impact.

Separate observed outcomes from missing alternatives

Take one difficult ticket that received the assistant and resolved within 24 hours. Its observed outcome is 1. Define A=1A=1A=1 for assistant assignment and A=0A=0A=0 for control. The potential outcomes Y(1)Y(1)Y(1) and Y(0)Y(0)Y(0) are that ticket's binary resolution outcomes under the two workflows. Their difference Y(1)−Y(0)Y(1)-Y(0)Y(1)−Y(0) is its individual treatment effect.

Only one potential outcome is ever observed for a given unit. Here Y(1)=1Y(1)=1Y(1)=1, but the counterfactual Y(0)Y(0)Y(0) could be 0 or 1. The assistant may have caused this success, or the ticket may have resolved either way. Another control ticket can help estimate an average, but can't reveal this ticket's missing outcome. This limitation is known as the Fundamental Problem of Causal Inference.[2]Reference 2Causal Inference: What Ifhttps://miguelhernan.org/whatifbook

Causal estimands: ATE and ATT

The average treatment effect averages those individual differences across the entire population:

ATE⁡=E[Y(1)−Y(0)]\operatorname{ATE} = \mathbb{E}[Y(1) - Y(0)]ATE=E[Y(1)−Y(0)]

For binary resolution, an ATE of 0.10 means ten additional resolutions per 100 tickets on average under assistant assignment versus control.

Engineers also inspect the Average Treatment Effect on the Treated (ATT):

ATT⁡=E[Y(1)−Y(0)∣A=1]\operatorname{ATT} = \mathbb{E}[Y(1) - Y(0) \mid A = 1]ATT=E[Y(1)−Y(0)∣A=1]

When treatment assignment is completely randomized, ATE and ATT coincide. When routing policies select specific subgroups, they can diverge sharply. If the assistant provides a larger boost on difficult tickets than on easy tickets, and the triage system routes mostly difficult tickets to the assistant, ATT will exceed ATE.

Latent response types and the counterfactual matrix

Because individual counterfactuals are hidden, every unit in a binary-outcome setting belongs to one of four latent response types:

  1. Always Resolves (Immune): Y(1)=1,Y(0)=1Y(1)=1, Y(0)=1Y(1)=1,Y(0)=1. The ticket succeeds under either workflow.
  2. Helped (Beneficiary): Y(1)=1,Y(0)=0Y(1)=1, Y(0)=0Y(1)=1,Y(0)=0. The assistant succeeds where legacy control fails.
  3. Harmed (Adverse): Y(1)=0,Y(0)=1Y(1)=0, Y(0)=1Y(1)=0,Y(0)=1. Legacy control succeeds, but the assistant fails.
  4. Never Resolves (Doomed): Y(1)=0,Y(0)=0Y(1)=0, Y(0)=0Y(1)=0,Y(0)=0. The ticket fails under both workflows.
Potential outcome counterfactual matrix and latent population types. Top: The Fundamental Problem of Causal Inference shows that for any single ticket, either Y(1) or Y(0) is observed while the counterfactual outcome is missing. Bottom: Two hypothetical populations have identical average treatment effect (+10 percentage points: 78% assistant vs 68% control), but Population A has zero tickets harmed (10 helped, 0 harmed) while Population B has ten tickets harmed (20 helped, 10 harmed).
Two populations with identical average treatment effects (+10 points) can hide vastly different individual outcomes: Population A helps 10 tickets without harming any, while Population B harms 10 tickets per hundred.

Consider two hypothetical populations of 100 tickets, with both potential outcomes filled in for illustration:

Potential-outcome typeY(1)Y(1)Y(1)Y(0)Y(0)Y(0)Population APopulation B
Resolves under either workflow116858
Helped by assistant101020
Harmed by assistant01010
Resolves under neither002212

Both populations produce 78 assistant successes and 68 control successes. In Population A, ten tickets benefit and zero are harmed. In Population B, twenty benefit and ten are harmed. The net gain is ten points in both cases. Even an ideal randomized experiment estimating marginal averages can't reveal which pairing of potential outcomes is true without structural assumptions like monotonicity (Yi(1)≥Yi(0)Y_i(1) \ge Y_i(0)Yi​(1)≥Yi​(0) for all iii).

The next snippet verifies both populations from their underlying counts:

same-average-different-counterfactuals.py
1import numpy as np 2 3# Columns are Y(1), Y(0); rows match the four types in the table. 4potential_outcomes = np.array([[1, 1], [1, 0], [0, 1], [0, 0]]) 5populations = {"A": np.array([68, 10, 0, 22]), "B": np.array([58, 20, 10, 12])} 6 7for name, counts in populations.items(): 8 rates = counts @ potential_outcomes / counts.sum() 9 effect = rates[0] - rates[1] 10 print( 11 f"{name}: assistant={rates[0]:.0%}, control={rates[1]:.0%}, " 12 f"ATE={100 * effect:+.0f} points; helped={counts[1]}, harmed={counts[2]}" 13 )
Output
1A: assistant=78%, control=68%, ATE=+10 points; helped=10, harmed=0 2B: assistant=78%, control=68%, ATE=+10 points; helped=20, harmed=10

Why can't the control group's 76% aggregate success rate serve as the missing outcome for every assistant ticket?

Answer

The control group contains a much larger share of easy tickets, so its aggregate doesn't provide a comparable average for the assistant-assigned cases. Even a perfectly comparable control group identifies an average, not the missing binary outcome of each individual ticket.

Causal graphs, d-separation, and the three canonical junctions

The routing table shows an imbalanced mix, but not why routing produced it. Suppose the triage system sends difficult tickets to the assistant more often. Risk exists before routing and influences both assignment and resolution, making it a confounder.

Directed acyclic graphs (DAGs) encode causal assumptions as non-parametric structural equations. Arrows represent direct causal influence, not correlations. To understand how statistical dependencies flow through graphs, Pearl developed the rules of d-separation (directional separation), built from three elementary three-node junctions.[1]Reference 1Causal Inference in Statistics: A Primerhttps://bayes.cs.ucla.edu/PRIMER/

Three canonical causal DAG junctions: Chain (Mediator), Fork (Confounder), and Collider (Inverted Fork). In a chain, the causal path is naturally active and conditioning on the mediator blocks it. In a fork, the backdoor path is naturally active and conditioning on the confounder blocks it. In a collider, the path is naturally blocked and conditioning on the collider opens a spurious association.
The three canonical causal DAG junctions dictate how associations travel. Forks create confounding that conditioning removes; colliders naturally block association until conditioning opens an artificial backdoor path.

1. The chain (mediator): A→M→YA \to M \to YA→M→Y

Treatment AAA causes an intermediate variable MMM, which in turn causes outcome YYY. For example, the assistant (AAA) produces an automated diagnostic summary (MMM), which helps staff resolve the ticket (YYY).

  • Unconditioned state: The path is active. Association flows from AAA to YYY, transmitting the causal effect.
  • Conditioned state: Conditioning on mediator MMM blocks the path. Holding the summary fixed isolates any direct effect A→YA \to YA→Y, but blocks the indirect mechanism through which the assistant works. If you adjust for a mediator when estimating the total effect, you wipe out the very benefit you're trying to measure.

2. The fork (confounder): A←C→YA \leftarrow C \to YA←C→Y

A pre-treatment common cause CCC influences both treatment assignment AAA and outcome YYY. In our triage system, baseline ticket risk (CCC) determines routing (AAA) and changes resolution difficulty (YYY).

  • Unconditioned state: The path is active. Association flows through the backdoor A←C→YA \leftarrow C \to YA←C→Y, creating a non-causal association that produces Simpson's reversal.
  • Conditioned state: Conditioning on confounder CCC blocks the backdoor path. Stratifying by risk allows within-stratum comparisons to isolate the true causal effect.

3. The collider (inverted fork): A→K←YA \to K \leftarrow YA→K←Y

Two independent variables point to a shared effect KKK. For instance, suppose an automated quality audit flag (KKK) is triggered if the assistant was used (AAA) or if the ticket failed to resolve (YYY).

  • Unconditioned state: The path is naturally blocked. No association flows between AAA and YYY through KKK.
  • Conditioned state: Conditioning on collider KKK (such as filtering an evaluation dashboard to only tickets that triggered quality audit flags) opens an artificial path between AAA and YYY. This phenomenon is Berkson's bias or selection bias. If an audited ticket didn't fail, it probably used the assistant; this induces an artificial negative correlation between assistant usage and ticket resolution!

The backdoor criterion

Pearl formalized when a set of observed covariates ZZZ is sufficient to identify the causal effect of AAA on YYY:[1]Reference 1Causal Inference in Statistics: A Primerhttps://bayes.cs.ucla.edu/PRIMER/

A set of variables ZZZ satisfies the backdoor criterion relative to an ordered pair of variables (A,Y)(A, Y)(A,Y) in a DAG if:

  1. No node in ZZZ is a descendant of AAA.
  2. ZZZ blocks every path between AAA and YYY that contains an arrow pointing into AAA (backdoor paths).

If ZZZ satisfies this criterion, the causal effect is identified by the backdoor adjustment formula:

P(Y=y∣do⁡(A=a))=∑zP(Y=y∣A=a,Z=z)P(Z=z)P(Y = y \mid \operatorname{do}(A = a)) = \sum_{z} P(Y = y \mid A = a, Z = z) P(Z = z)P(Y=y∣do(A=a))=z∑​P(Y=y∣A=a,Z=z)P(Z=z)

Ticket risk is recorded before routing, while an escalation note is created by the assistant after routing. Which variable can support backdoor adjustment, and why can't the other replace it?

Answer

Pre-treatment risk can block the common-cause fork when it captures the relevant assignment and outcome differences. The assistant-generated escalation note occurs after treatment, so conditioning on it can block the mediator path or act as a collider, introducing new selection bias.

Standardize both arms to one population

Suppose the planned deployment has 70% low-risk and 30% high-risk tickets. What would each workflow's resolution rate be at that same mix? The assistant rate becomes 0.7(0.90)+0.3(0.50)=0.780.7(0.90)+0.3(0.50)=0.780.7(0.90)+0.3(0.50)=0.78; the control rate becomes 0.7(0.80)+0.3(0.40)=0.680.7(0.80)+0.3(0.40)=0.680.7(0.80)+0.3(0.40)=0.68. Their standardized difference is:

Δ^standardized=0.7(0.90−0.80)+0.3(0.50−0.40)=0.10\widehat{\Delta}_{\text{standardized}} = 0.7(0.90-0.80) + 0.3(0.50-0.40) = 0.10Δstandardized​=0.7(0.90−0.80)+0.3(0.50−0.40)=0.10

The hat marks an estimate from observed data. Both within-group differences are ten points, so this weighted difference is ten points for any shared risk mix. The individual arm rates depend on the mix, and unequal subgroup differences would make the estimated effect depend on it too. Calling this difference an identified causal effect requires explicit identification conditions.[2]Reference 2Causal Inference: What Ifhttps://miguelhernan.org/whatifbook

The next snippet continues the earlier session and applies one shared population distribution to both arms:

standardize-ticket-treatment-effects.py
1target_weights = {"low": 0.7, "high": 0.3} 2standardized = {"assistant": 0.0, "control": 0.0} 3 4for risk, population_weight in target_weights.items(): 5 for workflow in standardized: 6 successes, total = groups[risk][workflow] 7 standardized[workflow] += population_weight * successes / total 8 9adjusted_effect = standardized["assistant"] - standardized["control"] 10for workflow, rate in standardized.items(): 11 print(f"{workflow} at the target mix: {rate:.0%}") 12print(f"standardized difference: {100 * adjusted_effect:+.0f} percentage points") 13assert abs(adjusted_effect - 0.10) < 1e-12
Output
1assistant at the target mix: 78% 2control at the target mix: 68% 3standardized difference: +10 percentage points

The arithmetic answers a target-population comparison. It estimates an identified causal effect only under explicit assumptions:

Identification assumptionMeaning in the ticket example
Conditional exchangeabilityWithin measured risk groups, no unrecorded common cause still changes both routing and resolution: (Y(1),Y(0))⊥A∣R(Y(1), Y(0)) \perp A \mid R(Y(1),Y(0))⊥A∣R.
Positivity or overlapBoth assistant and control assignments occur for every target risk stratum: 0<P(A=1∣R=r)<10 < P(A=1 \mid R=r) < 10<P(A=1∣R=r)<1.
ConsistencyThe recorded outcome matches the specific treatment received: Y=AY(1)+(1−A)Y(0)Y = A Y(1) + (1-A) Y(0)Y=AY(1)+(1−A)Y(0).
No interference (SUTVA)One ticket's assignment doesn't alter another ticket's outcome (no queue spillover).
Transfer to targetThe 70% and 30% weights describe deployment, and risk-specific potential-outcome averages apply there too.

The smallest strata contain only ten tickets. If one low-risk assistant success became a failure, its rate would fall from 90% to 80%, and the standardized difference would fall from ten points to three. That's a sensitivity check, not a formal confidence interval, but it shows why the estimate needs uncertainty analysis before an engineering rollout. More data reduce sampling variance; they don't remove unmeasured confounding.

Both observed risk groups show a ten-point advantage, but a hidden customer-tier field affects both assistant routing and resolution. Does standardizing only by recorded risk identify the causal effect?

Answer

Not necessarily. The arithmetic still returns a risk-adjusted ten-point association, but an unmeasured common cause violates conditional exchangeability. Without measuring and adjusting for customer tier, or switching to a randomized design, that association isn't guaranteed to be causal.

Weight the assignment, then check its support

Standardization averages group-specific outcomes at a chosen mix. Inverse Probability of Treatment Weighting (IPTW) instead weights each observation by the inverse of the probability of receiving the treatment it actually received. The propensity score is the conditional assignment probability e(r)=P(A=1∣R=r)e(r) = P(A=1 \mid R=r)e(r)=P(A=1∣R=r), not the probability of successful resolution.[2]Reference 2Causal Inference: What Ifhttps://miguelhernan.org/whatifbook

Estimate it from the log: low-risk tickets went to the assistant 10 times out of 100, so e^(low)=0.10\hat{e}(\text{low}) = 0.10e^(low)=0.10. High-risk tickets went 90 times out of 100, so e^(high)=0.90\hat{e}(\text{high}) = 0.90e^(high)=0.90. An assistant ticket gets weight 1/e^(r)1/\hat{e}(r)1/e^(r); a control ticket gets 1/(1−e^(r))1/(1-\hat{e}(r))1/(1−e^(r)).

Inverse Probability of Treatment Weighting (IPTW) reweighting mechanism. The original confounded sample has severe risk imbalance: Assistant has 10% low risk and 90% high risk, while Control has 90% low risk and 10% high risk. Inverting propensity scores inflates rare observations (weight 10.0 for low-risk assistant and high-risk control) to create a balanced pseudo-population with a 50/50 risk mix in both arms, breaking the confounding arrow.
Inverse-probability weighting inflates underrepresented strata to construct a balanced pseudo-population where assignment is independent of baseline risk, severing the confounding backdoor path.

The ten low-risk assistant tickets each get weight 10, for total weight 100. The ninety high-risk assistant tickets each get weight 1/0.91/0.91/0.9, also totaling 100. The control arm gets the same 100/100 weighted mix. These weights recover the logged population's 50%/50% risk mix, not the planned deployment's 70%/30% mix. Weighting and standardization agree when they target the same population.

This snippet works directly with the grouped counts:

weight-routing-propensity.py
1import numpy as np 2 3totals = np.array([[10, 90], [90, 10]], dtype=float) 4successes = np.array([[9, 72], [45, 4]], dtype=float) 5 6def assignment_weights(counts): 7 if np.any(counts <= 0): 8 raise ValueError("No within-risk comparison: a workflow has zero tickets") 9 propensity = counts[:, 0] / counts.sum(axis=1) 10 return propensity, np.column_stack([1 / propensity, 1 / (1 - propensity)]) 11 12propensity, weights = assignment_weights(totals) 13weighted_totals = totals * weights 14rates = (successes * weights).sum(axis=0) / weighted_totals.sum(axis=0) 15print("assignment propensity (low, high):", propensity) 16print("weighted counts, rows=low/high; columns=assistant/control:") 17print(np.round(weighted_totals, 1)) 18print(f"50/50 mix: assistant={rates[0]:.0%}, control={rates[1]:.0%}") 19print(f"weighted difference: {100 * (rates[0] - rates[1]):+.0f} percentage points") 20 21try: 22 assignment_weights(np.array([[10, 90], [100, 0]])) 23except ValueError as error: 24 print("unsupported comparison:", error)
Output
1assignment propensity (low, high): [0.1 0.9] 2weighted counts, rows=low/high; columns=assistant/control: 3[[100. 100.] 4 [100. 100.]] 550/50 mix: assistant=70%, control=60% 6weighted difference: +10 percentage points 7unsupported comparison: No within-risk comparison: a workflow has zero tickets

Positivity violations and extreme weights

If high-risk tickets always receive the assistant, then e(high)=1e(\text{high}) = 1e(high)=1: no high-risk control outcomes can appear in the logs. This violates positivity (overlap). Without examples from both actions, no reweighting can identify the counterfactual without extra parametric assumptions.

Even non-zero probabilities can destabilize estimators. A rare assignment probability of 0.005 yields an inverse weight of 200, allowing a single noisy ticket to dominate the estimate. Production systems inspect weight distributions and employ stabilized weights (the Hajek estimator, normalizing weights to sum to one within each arm) or apply weight clipping, accepting a small amount of bias to rein in variance.

The weighted assistant rate is 70%, while the 70/30 standardized assistant rate was 78%. Did one calculation fail?

Answer

No. The inverse-probability weights here target the log's 50/50 risk mix: 0.5(0.90) + 0.5(0.50) = 0.70. Deployment standardization targets 70/30: 0.7(0.90) + 0.3(0.50) = 0.78. The differences between arms happen to agree because both risk groups share the same ten-point difference.

Instrumental variables when unmeasured confounders lurk

What happens when an unmeasured confounder UUU (like customer technical frustration or urgency) affects both treatment routing AAA and resolution YYY? Conditional exchangeability fails because UUU isn't recorded in the logs. Backdoor adjustment and IPTW can't eliminate the bias.

An instrumental variable (IV) ZZZ offers an alternative identification route. A valid instrument satisfies three conditions:[1]Reference 1Causal Inference in Statistics: A Primerhttps://bayes.cs.ucla.edu/PRIMER/

  1. Relevance: ZZZ causally affects treatment assignment AAA (Cov⁡(A,Z)≠0\operatorname{Cov}(A, Z) \neq 0Cov(A,Z)=0).
  2. Exclusion Restriction: ZZZ affects outcome YYY only through treatment AAA (no direct arrow Z→YZ \to YZ→Y).
  3. Exogeneity / Independence: ZZZ shares no common causes with YYY (Z⊥UZ \perp UZ⊥U).
Diagram showing Instrument Z (Gateway canary flag), Treatment A (Assistant assignment), Unmeasured confounder U (Customer frustration), and Outcome Y (Resolution).
Instrument Z (Gateway canary flag), Treatment A (Assistant assignment), Unmeasured confounder U (Customer frustration), and Outcome Y (Resolution).

In an AI support platform, ZZZ could be a randomized client gateway latency experiment or canary flag that stochastically pushes incoming requests toward the assistant without affecting customer frustration.

Wald estimator and local average treatment effects

When treatment is binary and compliance is imperfect, the Wald estimator computes the causal effect by taking the ratio of the intention-to-treat outcome difference to the assignment compliance difference:

ATE⁡IV=E[Y∣Z=1]−E[Y∣Z=0]E[A∣Z=1]−E[A∣Z=0]\operatorname{ATE}_{\text{IV}} = \frac{\mathbb{E}[Y \mid Z=1] - \mathbb{E}[Y \mid Z=0]}{\mathbb{E}[A \mid Z=1] - \mathbb{E}[A \mid Z=0]}ATEIV​=E[A∣Z=1]−E[A∣Z=0]E[Y∣Z=1]−E[Y∣Z=0]​

Suppose setting canary flag Z=1Z=1Z=1 raises assistant usage from 30% to 70% (ΔA=0.40\Delta A = 0.40ΔA=0.40), while overall ticket resolution rises from 68% to 72% (ΔY=0.04\Delta Y = 0.04ΔY=0.04). The Wald estimate is:

ATE⁡IV=0.72−0.680.70−0.30=0.040.40=0.10(+10 percentage points)\operatorname{ATE}_{\text{IV}} = \frac{0.72 - 0.68}{0.70 - 0.30} = \frac{0.04}{0.40} = 0.10 \quad (+10\text{ percentage points})ATEIV​=0.70−0.300.72−0.68​=0.400.04​=0.10(+10 percentage points)

Under one-sided non-compliance and monotonicity (no defiers who choose the opposite of their assignment), this identifies the Local Average Treatment Effect (LATE): the causal effect on compliers whose routing was changed by the instrument.

Why can't an engineer use the ticket's word count as an instrument for assistant routing?

Answer

Ticket word count correlates with issue complexity, which directly affects resolution time. This violates the exclusion restriction: word count has a direct causal path to resolution that doesn't pass through assistant routing.

Causal inference in production AI systems

Causal principles are fundamental to machine learning and AI agent engineering, particularly when models interact with environments, user behavior, and multi-step tool calls.

Offline policy evaluation (OPE)

When testing a new agent policy π(a∣s)\pi(a \mid s)π(a∣s) (such as a redesigned triage prompt), deploying it live to production traffic can degrade user experience. Instead, teams evaluate π\piπ on historical interaction logs collected under a legacy logging policy π0(a∣s)\pi_0(a \mid s)π0​(a∣s).

This problem matches observational causal inference. The action aaa is the treatment, state sss is the covariate context, and reward rrr is the outcome. The standard importance-sampling estimator (IPS) weights logged rewards by the policy probability ratio:

V^IPS(π)=1N∑i=1Nπ(ai∣si)π0(ai∣si)ri\hat{V}_{\text{IPS}}(\pi) = \frac{1}{N} \sum_{i=1}^N \frac{\pi(a_i \mid s_i)}{\pi_0(a_i \mid s_i)} r_iV^IPS​(π)=N1​i=1∑N​π0​(ai​∣si​)π(ai​∣si​)​ri​

For multi-step agents, importance ratios compound exponentially over the trajectory horizon TTT: ∏t=1Tπ(at∣st)π0(at∣st)\prod_{t=1}^T \frac{\pi(a_t \mid s_t)}{\pi_0(a_t \mid s_t)}∏t=1T​π0​(at​∣st​)π(at​∣st​)​, causing massive variance.

To tame this variance, teams turn to the Doubly Robust (DR) estimator. DR fits a baseline regression model Q^(s,a)≈E[R∣S=s,A=a]\hat{Q}(s, a) \approx \mathbb{E}[R \mid S=s, A=a]Q^​(s,a)≈E[R∣S=s,A=a] and uses importance weights only to correct the residual:

V^DR(π)=1N∑i=1N[Q^(si,π)+π(ai∣si)π0(ai∣si)(ri−Q^(si,ai))]\hat{V}_{\text{DR}}(\pi) = \frac{1}{N} \sum_{i=1}^N \left[ \hat{Q}(s_i, \pi) + \frac{\pi(a_i \mid s_i)}{\pi_0(a_i \mid s_i)} \left( r_i - \hat{Q}(s_i, a_i) \right) \right]V^DR​(π)=N1​i=1∑N​[Q^​(si​,π)+π0​(ai​∣si​)π(ai​∣si​)​(ri​−Q^​(si​,ai​))]

The Doubly Robust estimator is unbiased if either the propensity model π0\pi_0π0​ is accurate OR the reward model Q^\hat{Q}Q^​ is accurate.

The next snippet verifies how the Doubly Robust estimator corrects a biased direct model using importance-weighted residuals from our 200-ticket log:

offline-policy-evaluation.py
1import numpy as np 2 3# Reconstruct individual records from the 200 tickets 4# Stratum: risk (0: low, 1: high), action A (1: assistant, 0: control), outcome Y 5records = [] 6records.extend([(0, 1, 1)] * 9 + [(0, 1, 0)] * 1) # Low assistant: 9/10 7records.extend([(0, 0, 1)] * 72 + [(0, 0, 0)] * 18) # Low control: 72/90 8records.extend([(1, 1, 1)] * 45 + [(1, 1, 0)] * 45) # High assistant: 45/90 9records.extend([(1, 0, 1)] * 4 + [(1, 0, 0)] * 6) # High control: 4/10 10 11data = np.array(records) 12risk, a_obs, y_obs = data[:, 0], data[:, 1], data[:, 2] 13 14# Logging policy pi_0(A=1 | risk) 15pi_0 = np.where(risk == 0, 0.10, 0.90) 16 17# Evaluate candidate policy pi_target: always route to assistant (action 1) 18pi_target = 1.0 19w = np.where(a_obs == 1, pi_target / pi_0, 0.0) 20 21# Direct model Q_hat with deliberate positive bias (+5 percentage points) 22q_hat_1 = np.where(risk == 0, 0.95, 0.55) # True rates: 0.90, 0.50 23q_hat_obs = np.where(a_obs == 1, q_hat_1, 0.50) 24 25v_ips = np.mean(w * y_obs) 26v_direct = np.mean(q_hat_1) 27v_dr = np.mean(q_hat_1 + w * (y_obs - q_hat_obs)) 28 29print(f"Direct model (biased): {v_direct:.1%}") 30print(f"IPS estimator (unbiased): {v_ips:.1%}") 31print(f"Doubly Robust (corrected): {v_dr:.1%}") 32assert abs(v_dr - 0.70) < 1e-12
Output
1Direct model (biased): 75.0% 2IPS estimator (unbiased): 70.0% 3Doubly Robust (corrected): 70.0%

Recommendation and search debiasing

In recommendation engines and search rankers, user interaction logs suffer from severe position bias and popularity bias. Users click the top-ranked item because it's displayed first, not necessarily because it's the most relevant. Training ranking models directly on raw clicks creates a self-reinforcing feedback loop. By treating presentation position as an observed confounder and weighting training losses by inverse examination probabilities, models learn true user preferences rather than interface artifacts.

Counterfactual reasoning in LLM agents

When an autonomous agent executes a sequence of tool calls (e.g. database query, code generation, test execution) and fails at step 5, diagnosing which step caused the failure is non-trivial. Did tool call 2 return subtly corrupted data, or did tool call 4 fail on valid input?

By constructing a causal DAG of the execution trace, developers can perform counterfactual interventions: holding the agent's intermediate state fixed up to step 2, intervening on tool call 2's return value, and re-running downstream generation. This isolates the causal contribution of individual agent steps and provides principled credit assignment for agent reinforcement learning.

Decide what the comparison supports

Evaluation methodIdentification requirementsCore advantagesPrimary failure modes
Standardization (g-formula)Exchangeability, positivity, consistency, correctly specified outcome modelDirect estimation across target population distributionsUnmeasured confounding; model misspecification in high dimensions
Inverse Weighting (IPTW)Exchangeability, positivity, correctly modeled assignment propensityBalances entire covariate distribution without outcome modelingExtreme weights explode variance; positivity violations
Instrumental Variables (IV)Relevant instrument, exclusion restriction, exogeneityIdentifies causal effects even when unmeasured confounders existWeak instruments amplify standard errors; identifies only LATE
Doubly Robust (DR / OPE)Overlap, and either propensity model or reward model correctly specifiedMinimum variance and double protection against misspecificationMulti-step horizon compounding; simultaneous model failure
Randomized A/B ExperimentUnconfounded in expectation; non-interference (SUTVA)Gold standard; breaks all pre-treatment confounding pathsQueue interference; attrition; inability to observe individual counterfactuals

A properly implemented randomized design makes assignment independent of potential outcomes in expectation. It balances baseline characteristics across arms, but it doesn't automatically repair post-assignment attrition, interference, or measurement errors. For support tickets sharing an agent pool, assigning one ticket to an AI assistant can free staff time for another, violating the no-interference assumption.

For this release report, the raw -22-point difference compared unequal case mixes. The +10-point standardized difference answers a better-specified comparison, but remains an estimate conditioned on routing, measurement, and interference assumptions. It doesn't prove that every ticket benefited. A randomized evaluation with sufficient tickets in both risk strata resolves routing bias; consistent measurement horizons and queue designs ensure the resulting numbers can be trusted.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.Why did the AI assistant show a higher observed success rate in both risk strata while appearing worse in the naive aggregate?

Correct answer: The assistant received 90% difficult tickets while control received 90% easy tickets, reversing the aggregate.

Pre-treatment risk acts as a confounder: the assistant received 90% high-risk tickets while control received only 10%. This imbalance flips the aggregate comparison.

2.How does Pearl's Ladder of Causation distinguish between observational conditioning P(Y|X) and intervention P(Y|do(X))?

Correct answer: P(Y|X) passive-observes data under natural assignment, while do(X) actively severs incoming causal arrows to X.

Rung 1 (seeing) conditions on passive data where confounding may lurk. Rung 2 (doing) simulates an active intervention that overrides the natural assignment mechanism.

3.What does the potential outcome Y(0) represent for a ticket that was actually assigned to the AI assistant (A=1)?

Correct answer: The unobserved counterfactual outcome that ticket would have experienced if it had instead been routed to control.

For a treated ticket (A=1), Y(1) is observed while Y(0) is counterfactual and permanently missing due to the Fundamental Problem of Causal Inference.

4.Two support workflows produce identical ATEs of +10 percentage points. What does this establish regarding individual user harm?

Correct answer: The marginal ATE cannot separate a population with zero harmed users from one where 10% of users suffer harm.

A population with 10 helped and 0 harmed yields +10 net points, and a population with 20 helped and 10 harmed also yields +10 net points. Identifying joint potential outcomes requires stronger assumptions like monotonicity.

5.What occurs when an evaluation analysis conditions on a collider variable K in the causal graph A -> K <- Y?

Correct answer: It opens an artificial non-causal path between treatment A and outcome Y, introducing Berkson's selection bias.

Colliders naturally block information flow. Conditioning on a collider (or its descendant) unblocks the path and introduces spurious non-causal association.

6.Why is an assistant-generated escalation note inappropriate as a confounder adjustment variable for estimating total effect?

Correct answer: Because it is created after routing along the causal pathway, so conditioning on it blocks the assistant's mechanism.

Variables produced after treatment cannot serve as confounder adjustments for the total effect. Conditioning on mediators blocks causal pathways.

7.What fundamental causal identification failure occurs if a triage policy strictly routes 100% of high-risk tickets to the assistant?

Correct answer: Positivity fails because high-risk control tickets never appear, leaving their counterfactual outcomes unidentified.

Positivity requires 0 < P(A=1|X) < 1. If high-risk tickets always receive the assistant, counterfactual control outcomes cannot be identified from observational data alone.

8.In Inverse Probability of Treatment Weighting (IPTW), why do low-risk assistant tickets receive an inflated weight of 10.0?

Correct answer: Because their assignment probability was 0.10, so inverting it inflates their count to reconstruct a balanced mix.

IPTW assigns weight 1/e(x) to treated units. Units that rarely receive treatment are upweighted to reconstruct an unconfounded pseudo-population.

9.Which condition represents an indispensable requirement for a valid Instrumental Variable Z?

Correct answer: The instrument Z must affect the outcome Y only through its direct causal effect on treatment assignment A.

The exclusion restriction demands that the instrument have no direct causal link to the outcome other than through the treatment.

10.What is the primary operational advantage of the Doubly Robust (DR) estimator in Offline Policy Evaluation?

Correct answer: It remains unbiased if either the propensity assignment model or the reward baseline model is correctly specified.

Doubly robust estimators provide dual protection against model misspecification by combining an outcome regression model with propensity-weighted residual corrections.

10 questions remaining.

Next Step
Continue to PyTorch Training Loops

A causal comparison makes interventions and their evidence explicit; the next lesson applies the same discipline to controlled model updates, gradients, and reproducible training behavior.

PreviousExperiment Design and A/B Testing
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Causal Inference in Statistics: A Primer

Pearl, J., Glymour, M., & Jewell, N. P. · 2016

https://bayes.cs.ucla.edu/PRIMER/

Causal Inference: What If

Hernán, M. A., & Robins, J. M. · 2020 · Chapman & Hall/CRC

https://miguelhernan.org/whatifbook

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.