Correlation, causation & regression basics

Analysts are constantly asked "does X drive Y?" - does ad spend drive sales, does discount depth drive repeat visits? Correlation and regression are the first tools for answering, and also the easiest to misuse.

Correlation coefficients

Coefficient Measures Use when
Pearson r Strength of a linear relationship Both variables numeric, roughly linear, few outliers
Spearman rho Strength of a monotonic relationship (on ranks) Skewed data, outliers, ordinal scales

Both range from -1 to +1. Values near 0 mean no linear (or monotonic) pattern - but there could still be a curved one, so always plot first.

import numpy as np
import pandas as pd
from scipy import stats

rng = np.random.default_rng(5)
n = 120
store_size = rng.uniform(800, 4000, n)                     # sq ft
ad_spend = 5 + 0.004 * store_size + rng.normal(0, 3, n)    # thousand INR / month
staff = np.round(store_size / 400 + rng.normal(0, 1, n))
sales = 40 + 0.05 * store_size + 2.0 * ad_spend + rng.normal(0, 25, n)   # thousand INR
df = pd.DataFrame({"store_size": store_size, "ad_spend": ad_spend, "staff": staff, "sales": sales})

r, p = stats.pearsonr(df["staff"], df["sales"])
rho, p_s = stats.spearmanr(df["staff"], df["sales"])
print(f"Pearson r={r:.2f} (p={p:.3g}), Spearman rho={rho:.2f}")

Staff count correlates strongly with sales. Should the chain hire more staff everywhere?

Why correlation is not causation

  • Confounding: a third variable drives both. Here, bigger stores have more staff and more sales.
  • Reverse causation: busy stores get more staff because they are busy.
  • Coincidence: with enough variables, some correlate by chance.
  • Selection effects: only surviving stores are in the data.

Establishing causation needs a randomised experiment (A/B test) or careful designs such as difference-in-differences - not just a strong r.

Simple and multiple regression

Regression estimates how Y changes with X, and lets you control for other variables.

import statsmodels.formula.api as smf

simple = smf.ols("sales ~ staff", data=df).fit()
multi = smf.ols("sales ~ staff + store_size + ad_spend", data=df).fit()
print(simple.params.round(2))
print(multi.params.round(2))
print(multi.conf_int().round(2))
print(f"R-squared: {multi.rsquared:.2f}")

In the simple model the staff coefficient is large. Once store size is included it shrinks towards zero, while the ad-spend coefficient lands near its true value of 2 - "each extra ₹1,000 of ads is associated with about ₹2,000 more sales, holding size and staff constant".

Reading the output

  • Coefficient: expected change in Y for a one-unit increase in X, other variables fixed.
  • Confidence interval / p-value: how precisely it is estimated.
  • R-squared: share of variance in Y explained - high R-squared does not prove causation either.
  • Residuals: plot them against fitted values; patterns suggest a missing curve or variable.

Common mistakes

  • Reporting r without a scatter plot (outliers can create or hide correlation).
  • Extrapolating far beyond the observed range of X.
  • Treating a significant coefficient as a causal effect in observational data.
  • Adding highly correlated predictors and then trusting their individual coefficients.

Practice

Using the generated data, plot sales against staff coloured by store size. Fit the simple and multiple models, compare the staff coefficient, and write three sentences explaining to a regional manager why hiring staff alone may not raise sales.

B

Written by Bablu Kumar Jha

Founder, BeeProton AI · Mumbai, Maharashtra, India