// Back to articles

Causal Inference for Pricing: Beyond Correlation

How to use causal ML techniques to understand true price elasticity and avoid spurious correlations.

When building pricing models, the most dangerous thing you can do is confuse correlation with causation. In this post, I’ll share how we applied causal inference techniques at Zalando to build robust price elasticity models that actually work in production.

The Problem with Naive Approaches

Consider a typical scenario: you want to understand how price affects demand for a product. The naive approach is to fit a regression:

# Don't do this!
demand = β₀ + β₁ × price + ε

The estimated coefficient β₁ seems to tell you the price elasticity. But there’s a fundamental problem: prices are not randomly assigned.

Products get higher prices when:

  • They’re more popular (confounding!)
  • They’re higher quality (confounding!)
  • It’s peak season (confounding!)
  • Inventory is low (confounding!)

This leads to the infamous “price puzzle” where naive models show that higher prices increase demand — clearly nonsensical but a direct result of confounding.

The Causal Framework

Causal inference provides tools to reason about what happens when we intervene on price, not just observe it. The key distinction:

  • Observational: P(demand | price = €50) — what demand do we see when price is €50?
  • Causal: P(demand | do(price = €50)) — what demand would we get if we set price to €50?

We used several techniques to estimate causal effects:

1. Instrumental Variables

An instrumental variable (IV) affects demand only through price. Good instruments for pricing include:

  • Cost shocks: Changes in supplier costs that affect price but not demand directly
  • Exchange rates: For imported goods, currency fluctuations affect prices
  • Competitor stockouts: When competitors run out, we may adjust prices
from linearmodels.iv import IV2SLS

# Two-stage least squares estimation
model = IV2SLS.from_formula(
    'demand ~ 1 + [price ~ cost_shock + exchange_rate]',
    data=df
)
results = model.fit()

The intuition: we use variation in price that comes from the instrument (which is exogenous) rather than variation that comes from confounders.

2. Double Machine Learning

For high-dimensional settings, Double ML (DML) provides a powerful framework:

from econml.dml import LinearDML

# Flexible ML models for nuisance functions
model_y = GradientBoostingRegressor()  # Demand model
model_t = GradientBoostingRegressor()  # Price model

dml = LinearDML(
    model_y=model_y,
    model_t=model_t,
    discrete_treatment=False,
    cv=5
)

dml.fit(
    Y=demand,           # Outcome
    T=price,            # Treatment
    X=product_features, # Heterogeneity variables
    W=confounders       # Controls
)

# Get causal effect
ate = dml.ate()  # Average treatment effect

The beauty of DML is that it handles confounders flexibly using ML while still providing valid causal estimates through cross-fitting.

3. Difference-in-Differences

When you have natural experiments — like a price change that affects some products but not others — DiD is powerful:

import statsmodels.formula.api as smf

# Price change affected category A but not category B
model = smf.ols(
    'demand ~ treated * post + product_fe + time_fe',
    data=df
).fit(cov_type='cluster', cov_kwds={'groups': df['product_id']})

# Coefficient on treated:post is the causal effect
causal_effect = model.params['treated:post']

The parallel trends assumption is crucial: absent the treatment, treated and control groups would have followed similar trends.

Practical Implementation Tips

1. Start with DAGs

Before writing any code, draw a causal graph (DAG) of your problem:

Cost Shock ──▶ Price ──▶ Demand


Quality ────────┴──────▶ Demand

This clarifies what you need to control for and what makes a valid instrument.

2. Validate with A/B Tests

The gold standard for causal inference is randomized experiments. Use A/B tests to:

  • Validate your observational estimates
  • Calibrate model uncertainty
  • Build trust with stakeholders

At Zalando, we ran pricing experiments on subsets of products to validate our causal models before scaling them.

3. Heterogeneous Effects Matter

Price elasticity isn’t constant — it varies by:

  • Product category
  • Customer segment
  • Time of year
  • Stock levels

The econml library makes heterogeneous treatment effect estimation straightforward:

from econml.dml import CausalForestDML

cf = CausalForestDML(
    model_y=RandomForestRegressor(),
    model_t=RandomForestRegressor(),
    n_estimators=1000
)

cf.fit(Y=demand, T=price, X=features, W=confounders)

# Effect for specific product
individual_effect = cf.effect(X=product_features)

4. Sensitivity Analysis

Always ask: “How wrong would we have to be about unobserved confounding for our conclusion to flip?”

from sensemakr import Sensemakr

# Sensitivity analysis for OLS results
sensitivity = Sensemakr(
    model=regression_model,
    treatment="price",
    benchmark_covariates=["quality_score"]
)

sensitivity.summary()

Results in Production

After implementing causal pricing models:

  • 30% reduction in pricing errors vs. naive models
  • More accurate revenue forecasting
  • Better understanding of which products are price-sensitive
  • Increased trust from business stakeholders who saw results validated by experiments

Key Takeaways

  1. Correlation ≠ Causation: This isn’t just statistics pedantry — naive models give wrong answers
  2. Multiple methods: Use IVs, DML, and DiD as appropriate for your data
  3. Validate experimentally: Nothing beats an A/B test for ground truth
  4. Heterogeneity matters: Average effects hide important variation
  5. Communicate uncertainty: Causal estimates have assumptions — be explicit about them

Interested in causal ML? Check out the excellent resources at causalinference.io and the econml library documentation.