Correlation vs Causation in Data Analysis: Examples That Fool Beginners

Why correlation doesn't prove causation: confounders, reverse causation, selection bias and Simpson's paradox, with pandas examples.

By CrunchCubeUpdated 21 October 20265 min read

An ecommerce analyst notices something odd: customers who spend more time on a product page are less likely to buy. The correlation is clear and negative.

The obvious conclusion: long pages scare people away, so make every product page shorter. The marketing team is ready to cut all the descriptions.

That conclusion is probably wrong, and the reason is one of the most useful ideas in data analysis.

The simple idea

Correlation means two things move together. Causation means one thing actually makes the other happen.

Here's the classic example. Ice cream sales and drowning incidents rise and fall together. Does ice cream cause drowning? No. Hot weather causes both: more people buy ice cream, and more people go swimming. Remove the summer and the "relationship" disappears.

Hot weather is a confounder: a hidden third factor that pushes both numbers in the same direction.

Back to the product page

Let's simulate the analyst's data, so we know the true story and can watch the correlation lie.

In our fake shop, half the products are expensive. People spend longer on expensive product pages (they compare, read reviews, think about it), and they also buy expensive things less often. Reading time itself has, if anything, a small positive effect.

import numpy as np
import pandas as pd
 
rng = np.random.default_rng(42)
n = 2000
 
expensive = rng.random(n) < 0.5
seconds = np.where(expensive, rng.normal(180, 40, n), rng.normal(60, 20, n)).clip(5)
buy_prob = np.where(expensive, 0.05, 0.30) + 0.0004 * (seconds - seconds.mean())
bought = rng.random(n) < buy_prob.clip(0, 1)
 
df = pd.DataFrame({
    "expensive_product": expensive,
    "seconds_on_page": seconds.round(),
    "bought": bought.astype(int),
})
 
print(df["seconds_on_page"].corr(df["bought"]).round(2))  # -0.19

There it is: −0.19. More time, fewer purchases. Now look inside each group:

print(df.groupby("expensive_product")[["seconds_on_page", "bought"]].mean().round(2))
 
for is_expensive, part in df.groupby("expensive_product"):
    print(is_expensive, part["seconds_on_page"].corr(part["bought"]).round(2))
                   seconds_on_page  bought
expensive_product
False                        60.61    0.26
True                        180.78    0.08
 
False 0.0
True 0.12

Within cheap products, time on page has no relationship with buying. Within expensive products, it's slightly positive. The negative overall number came entirely from mixing two kinds of product. Price was the confounder.

If the team had shortened every page, they would have changed nothing, or slightly hurt sales of the expensive items.

Four ways correlation fools you

1. Confounders

A third factor drives both variables, like price in our example or summer for ice cream. Before trusting a correlation, ask: "What else could cause both of these?"

2. Reverse causation

The arrow points the other way. Users who open the help page churn more. Does the help page make people leave? More likely, people already having trouble open the help page.

In Game of Thrones terms: the Night's Watch is always where the trouble is. That's not because they cause trouble. They're stationed there because trouble comes from there.

3. Selection bias

The data only includes people who got through some filter. Survey your customers about price and they'll say it's fine, because everyone who thought it was too expensive never became a customer. Your data only contains people who said yes.

4. Coincidence

With enough columns, some will correlate by pure luck. Compare 50 metrics against each other and you're running over 1,200 comparisons. A few strong-looking correlations are guaranteed, and they mean nothing.

Simpson's paradox: when the winner loses

This one fools experienced analysts. Two campaigns, A and B, each brought 1,000 visitors:

data = pd.DataFrame({
    "campaign": ["A", "A", "B", "B"],
    "device":   ["mobile", "desktop", "mobile", "desktop"],
    "visitors": [800, 200, 200, 800],
    "buyers":   [48, 32, 10, 120],
})
data["rate_%"] = (data.buyers / data.visitors * 100).round(1)
print(data)
 
totals = data.groupby("campaign")[["visitors", "buyers"]].sum()
totals["rate_%"] = (totals.buyers / totals.visitors * 100).round(1)
print(totals)
  campaign   device  visitors  buyers  rate_%
0        A   mobile       800      48     6.0
1        A  desktop       200      32    16.0
2        B   mobile       200      10     5.0
3        B  desktop       800     120    15.0
 
          visitors  buyers  rate_%
campaign
A             1000      80     8.0
B             1000     130    13.0

Campaign A converts better on mobile (6% vs 5%) and on desktop (16% vs 15%). Yet overall, B wins easily, 13% to 8%.

How? Desktop users buy far more often, and B happened to get mostly desktop visitors. B didn't win on quality; it won on its audience mix. If you only looked at the total, you'd cancel the better campaign.

How to get closer to causation

  1. Run an experiment. An A/B test with random assignment is the gold standard, because chance, not the user, decides who sees what. Confounders get spread evenly across both groups.
  2. Segment before you conclude. Break results down by the obvious suspects: device, price band, new versus returning, country. If the pattern flips inside groups, you've found a confounder.
  3. Ask about timing. A cause has to happen before its effect. If the "cause" was measured after the outcome, you may have reverse causation.
  4. Look for a mechanism. Can you explain how A would cause B? "Long pages bore people" is a mechanism. Then check whether the data within groups supports it.
Common mistake

Writing "X drives Y" in a report because of a correlation. Write "X is associated with Y" until you've tested it. It's one word, and it protects you from a very expensive wrong decision.

When this matters most

For a quick dashboard, correlations are fine as a starting point: they tell you where to look. The danger begins when someone acts on them: redesigning pages, cutting a campaign, changing a price. Any decision that costs money deserves a breakdown by segment at minimum, and an experiment if you can run one.

Want the full workflow around this? Read the exploratory data analysis workflow from CSV to insights, and start with clean data, because a duplicate row can create a correlation all by itself.

In one line: a correlation tells you where to look, not what to do. Check for confounders, reverse causation and mixed groups before you call anything a cause.

Keep reading

New games, tools and tutorials

About one short email a week. No spam, one-click unsubscribe.