Statistics for Data Science · Day 6

Correlation and Covariance

Why a data scientist should care whether two things move together.

By Shikhar · Series continued · ~20 min read

Imagine you're a data scientist at Netflix. Your manager walks up and says: "We've noticed some users who watch more than 3 hours a day are churning faster. Can you look into it?"

Your first instinct is to check whether there's a relationship between hours watched and churn. Does watching more make people more likely to quit? Less likely? How strong is that link, if it's even there at all?

This is exactly where correlation comes in. It's the tool that tells you whether two variables are related, how strongly, and which direction they move in together.

Why should a data scientist care about correlation?

Correlation is one of the first things you check on any new dataset. It helps you:

What is correlation?

Correlation is a number that shows the strength and direction of the relationship between two numeric variables.

The key word is relationship. When two variables are correlated, it means changes in one tend to show up alongside changes in the other.

Correlation is expressed as a single number called the correlation coefficient, usually written as r. It always falls between -1 and +1, and that one number tells you two things at once: direction and strength.

Think of it as a ruler running from -1 on one end to +1 on the other, with 0 sitting in the middle.

Direction of correlation

Positive correlation (r close to +1)

When two variables move the same way, one goes up, the other goes up too, that's a positive correlation.

Everyday examples: taller people tend to weigh more. Hotter days mean more ice cream sold. More study hours tend to mean higher exam scores.

Big-tech example: at Amazon, the number of reviews a product has and its sales volume are positively correlated. More reviews build trust, and popular products naturally accumulate more reviews too, so the two feed each other. When Amazon's data team sees review counts drop in a category, it's often an early warning sign of a coming sales dip.

Negative correlation (r close to -1)

When two variables move opposite ways, one goes up while the other goes down, that's a negative correlation.

Everyday examples: more exercise tends to mean less body fat. As a product's price rises, demand tends to fall. More hours of TV generally means lower exam scores.

Big-tech example: at Uber, there's a well-known negative correlation between driver wait times and user retention. As estimated arrival time goes up, fewer users complete their booking. That single insight is a big part of why Uber invests so heavily in its driver-matching algorithms.

Zero correlation (r ≈ 0)

When there's no real pattern connecting two variables, r sits close to 0. Knowing one tells you nothing useful about the other.

Examples: someone's shoe size and their salary. How many films a person watches per year and their blood pressure. Daily coffee intake and IQ score.

Positive correlation Negative correlation No correlation
As one variable goes up: the other goes up too (positive), goes down (negative), or does its own thing regardless (zero).

How strong is the relationship? A practical guide

The size of r, ignoring its sign, tells you how strong the correlation is. Here's a simple guide most data scientists lean on:

Value of rStrength of correlation
< 0.1No correlation
0.1 – 0.3Little correlation
0.3 – 0.5Medium correlation
0.5 – 0.7High correlation
0.7 – 1.0Very high correlation
Note

These are general guidelines, not hard rules. In social science, an r of 0.3 can be genuinely meaningful. In physics or engineering, you'd expect r above 0.95 before getting excited. Always read a correlation in the context of the field it came from.

Types of correlation coefficients

Not all data is the same, so there isn't just one correlation coefficient. Depending on what your data looks like, you should reach for one of three main ones:

Let's go through each one properly.

Covariance: the building block

Before we get to correlation, we need covariance, since correlation is built directly on top of it.

Covariance measures how two variables change together. If both tend to sit above their own average at the same time, covariance comes out positive. If one tends to be above average while the other is below, it comes out negative.

The formula for covariance is:

Cov(X,Y) = ∑ (Xi − X)(Yi − Y)n − 1
pythonimport numpy as np # Example: hours studied vs. exam score hours_studied = [1, 2, 3, 4, 5, 6, 7, 8] exam_score = [45, 50, 55, 60, 65, 70, 75, 80] cov_matrix = np.cov(hours_studied, exam_score) print("Covariance matrix:") print(cov_matrix) cov_xy = cov_matrix[0][1] print(f"\nCovariance between hours studied and exam score: {cov_xy:.2f}")
OutputCovariance matrix: [[ 6. 30.] [ 30. 150.]] Covariance between hours studied and exam score: 30.00

The covariance is 30. Is that big or small? There's no way to tell without context, exactly the "which ruler is this" problem Day 5 spent a whole article on. That's why we divide by the standard deviations to get correlation.

The problem with covariance: it depends entirely on the units you measured in. Measure height in centimetres instead of metres and you get a completely different covariance number, even though the actual relationship hasn't changed at all. That makes covariance impossible to compare fairly across different datasets.

This is exactly why correlation exists. It standardizes covariance by dividing it by the standard deviations of both variables, which gives you a number with no units that always sits between -1 and +1.

Pearson Correlation Coefficient (r)

The Pearson correlation coefficient is the most widely used measure of correlation. It captures the strength and direction of the straight-line relationship between two continuous variables.

r = Cov(X,Y)σX × σY
In full, with everything spelled out: r = ∑(Xi−X)(Yi−Y) ⁄ √[∑(Xi−X)2 × ∑(Yi−Y)2]

Dividing covariance by the product of the two standard deviations is what forces the result to always land between -1 and +1, no matter what units the original data used.

Interpreting the sign and size

Worked example: Netflix viewing time vs. satisfaction

Say we're checking whether the hours a Netflix user watches per week is related to how satisfied they say they are in post-viewing surveys (rated 1 to 10).

Hours watched per week vs. satisfaction score Hours watched per week Satisfaction (1-10) Pearson r = 0.75
pythonfrom scipy import stats # Hours watched per week vs. satisfaction score (1-10) hours_watched = [2.5, 4.7, 5.7, 6.1, 7.3, 8.0, 8.6, 10.4, 10.7, 10.7, 12.3, 12.6, 13.0, 14.4, 14.4, 17.7] satisfaction = [6.2, 4.1, 5.5, 6.1, 5.9, 5.9, 6.2, 5.9, 6.9, 6.8, 7.0, 6.1, 6.6, 6.7, 7.0, 10.0] r, p_value = stats.pearsonr(hours_watched, satisfaction) print(f"Pearson r = {r:.2f}, p-value = {p_value:.4f}")
OutputPearson r = 0.75, p-value = 0.0008
Always plot it first

Before you trust a Pearson r, check whether the relationship actually looks like a straight line. The only real way to check is to plot it and look.

Correlation matrices and heatmaps

In practice you rarely look at just two variables at a time. You look at many features at once using a correlation matrix: a table showing the correlation between every pair of variables. Companies like Google and Meta lean on exactly this during early data exploration.

pythoncorr_matrix = df[['page_views', 'session_duration', 'items_clicked', 'cart_additions', 'purchase_value']].corr() sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, vmin=-1, vmax=1) plt.title('Correlation Heatmap: E-commerce User Behavior')
Correlation heatmap: e-commerce user behavior 1.00 -0.11 0.77 0.56 0.42 -0.11 1.00 -0.02 0.00 -0.02 0.77 -0.02 1.00 0.79 0.59 0.56 0.00 0.79 1.00 0.79 0.42 -0.02 0.59 0.79 1.00 page_views session_duration items_clicked cart_additions purchase_value page_views session_duration items_clicked cart_additions purchase_value
Red means the two variables move together strongly. Blue means they move in opposite directions. White means barely any relationship at all.

Reading a heatmap is simple once you know the rule: red cells (near +1) mean a strong positive correlation, blue cells (near -1) mean a strong negative correlation, and white or light cells (near 0) mean barely any relationship at all.

In this example, cart_additions and purchase_value are highly correlated, which makes total sense. That's exactly the kind of finding that helps a product team decide what to prioritize in a checkout redesign.

Assumptions of Pearson correlation

Pearson's r is a parametric measure, which means it assumes certain things about your data. Break these assumptions and you can end up trusting a number that's actively misleading you.

  1. Linearity: the relationship between the two variables has to actually be a straight line.
  2. Normality: both variables should be roughly normally distributed.
  3. No major outliers: outliers can drag the correlation coefficient toward them, making a relationship look stronger or weaker than it really is.
  4. Continuous data: both variables should be continuous, not categorical or ordinal.

Disadvantages of Pearson correlation

1. It's blind to non-linear relationships

Think about stress and job performance. Up to a point, more stress can actually push someone to perform better, a positive relationship. But past that point, high stress wrecks performance instead, a negative relationship. Pearson looks at the whole picture at once, sees something roughly linear on average, and completely misses that U-shaped curve underneath.

Here's a real, famous illustration of the same trap.

Anscombe's Quartet II x y Pearson r = 0.82 (but look at the curve)
python# Anscombe's Quartet, dataset II (a real, famous dataset from 1973) x = [10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5] y = [9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 6.13, 3.10, 9.13, 7.26, 4.74] r, _ = stats.pearsonr(x, y) print(f"Pearson r = {r:.2f}")
OutputPearson r = 0.82

0.82 sounds like a strong linear relationship. It isn't one. This dataset is part of Anscombe's Quartet, four datasets built by statistician Francis Anscombe in 1973 specifically to prove this point: they all share the same mean, the same variance, and the same Pearson r, and they look nothing alike when you actually plot them.

Lesson

Always visualize your data. Never trust a correlation number without also looking at a scatter plot.

2. It's sensitive to outliers

Without the outlier r = 0.96 With one outlier added r = -0.30
Same 8 points, same trend, one extra point added. That single point flips the correlation from strongly positive to negative.
pythonimport numpy as np from scipy import stats # Normal data x_normal = [2, 4, 5, 6, 8, 9, 10, 12] y_normal = [3, 5, 4, 7, 9, 8, 11, 13] # Same data with one extreme outlier added x_outlier = x_normal + [50] y_outlier = y_normal + [2] r_normal, _ = stats.pearsonr(x_normal, y_normal) r_outlier, _ = stats.pearsonr(x_outlier, y_outlier) print(f"Pearson r without outlier: {r_normal:.4f}") print(f"Pearson r WITH outlier: {r_outlier:.4f}")
OutputPearson r without outlier: 0.9636 Pearson r WITH outlier: -0.3026

One single point turned a strong positive relationship into a negative one. That's the entire relationship, flipped, by adding one data point out of nine. This is exactly why Spearman's correlation exists: it's built to shrug off exactly this kind of outlier.

Spearman Rank Correlation (ρ)

Spearman's rank correlation is the go-to alternative whenever Pearson's assumptions don't hold up. Instead of working with the raw numbers, it first converts everything into ranks, then runs Pearson's formula on those ranks.

That one change makes it:

Understanding monotonic relationships

Pearson needs the relationship to be a straight line. Spearman only needs it to be monotonic.

A monotonic relationship just means: as X goes up, Y either always goes up or always goes down. It doesn't have to happen in a straight line, it just can't reverse direction.

Think of climbing a mountain on a winding road. You keep gaining altitude the whole way (monotonically increasing), even though the incline changes constantly. Spearman captures that perfectly. Pearson, expecting a straight line, struggles with it.

x Monotonically increasing x Monotonically decreasing
Neither line is straight, but neither one ever doubles back either. That's all "monotonic" means: it only ever goes one way.

Monotonically increasing: as one variable increases, the other never decreases. Example: age and years of work experience.

Monotonically decreasing: as one variable increases, the other never increases. Example: time spent on a support call and the customer's satisfaction score.

Not monotonic: the other variable goes up and down as the first one increases. Example: caffeine and focus. A little caffeine helps focus, a moderate amount helps more, but too much wrecks it.

How Spearman's works, step by step

Given data for X and Y:

  1. Rank the values of X (1 = smallest). If there's a tie, give both values the average of the ranks they'd otherwise occupy.
  2. Rank the values of Y the same way.
  3. Run Pearson's formula on the two sets of ranks, not the original values.

When there are no ties, there's a shortcut:

ρ = 1 − 6 × ∑ di2n(n2 − 1)

Here, di is the difference between the two ranks for each pair, and n is the number of observations.

Worked example: Google Play app ratings

Say you're at Google, looking at the Play Store. You want to know whether an app's engagement score (an internal metric, not normally distributed, with some extreme values) relates to its number of active installs.

pythonimport pandas as pd import numpy as np from scipy import stats np.random.seed(7) n = 50 # Engagement score is right-skewed (not normal): a few apps are viral engagement_score = np.random.exponential(scale=20, size=n) # Active installs: loosely tied to engagement, but with wild outliers active_installs = engagement_score * 50000 + np.random.normal(0, 500000, n) active_installs = np.abs(active_installs) df = pd.DataFrame({'engagement_score': engagement_score, 'active_installs': active_installs}) pearson_r, pearson_p = stats.pearsonr(df['engagement_score'], df['active_installs']) spearman_r, spearman_p = stats.spearmanr(df['engagement_score'], df['active_installs']) print(f"Pearson: r = {pearson_r:.4f}, p = {pearson_p:.4f}") print(f"Spearman: rho = {spearman_r:.4f}, p = {spearman_p:.4f}")
OutputPearson: r = 0.8660, p = 0.0000 Spearman: rho = 0.7399, p = 0.0000

Notice Pearson's number is actually higher here, not lower. That's worth sitting with for a second: Spearman being the "more appropriate" choice for skewed, outlier-prone data doesn't mean it always spits out a bigger or smaller number. It means it's measuring something more trustworthy: the consistency of the rank order, rather than a relationship that a couple of viral outlier apps could be quietly inflating.

Real-world example: Netflix's star rating switch

Back in 2017, Netflix dropped its 5-star rating system for a simple thumbs up or thumbs down. Netflix's own stated reasons were about engagement, not statistics: thumbs got roughly 200% more ratings than stars did in testing, and a lot of users were confused about what a star average even represented. But there's a real statistical point sitting underneath that decision too. Star ratings are ordinal data, so the gap between 3 and 4 stars was never guaranteed to mean the same thing as the gap between 1 and 2. If you wanted to correlate star ratings with anything else, Spearman was always the more defensible tool, not Pearson.

python# Star rating is ordinal (1-5), rewatch probability is continuous (0-100%) star_ratings = [1, 2, 2, 3, 3, 4, 4, 4, 5, 5] rewatch_prob = [5, 12, 18, 35, 40, 60, 65, 70, 85, 92] pearson_r, _ = stats.pearsonr(star_ratings, rewatch_prob) spearman_r, _ = stats.spearmanr(star_ratings, rewatch_prob) print(f"Pearson r: {pearson_r:.4f} (assumes equal gaps between stars)") print(f"Spearman rho: {spearman_r:.4f} (only cares about rank order)")
OutputPearson r: 0.9851 (assumes equal gaps between stars) Spearman rho: 0.9786 (only cares about rank order)

In this particular toy example the two numbers land close together, both are very high, because the ranks and the raw values happen to agree almost perfectly here. That won't always happen. The real reason Spearman is the correct tool for star ratings has nothing to do with which number comes out bigger: it's that Pearson's formula silently assumes the gap between 3 and 4 stars equals the gap between 1 and 2 stars, and for a human rating scale, that assumption is usually just wrong.

When to use Spearman over Pearson

Reach for Spearman when your data is ordinal (ratings, rankings, satisfaction levels), when it has real outliers, when it isn't normally distributed (income data is a classic example, since it's heavily right-skewed), or when the relationship looks curved but still monotonic on a scatter plot.

Kendall's Tau (τ): the underrated one

Kendall's tau is another rank-based correlation, similar in spirit to Spearman's. The difference is in exactly what it counts.

For every possible pair of observations, Kendall's tau asks a simple question: do X and Y move in the same direction for this pair? If yes, that's a concordant pair. If they move in opposite directions, it's a discordant pair.

τ = Concordant pairs − Discordant pairsTotal pairs

Kendall's tau tends to be the better choice when your sample size is small (it stays more accurate with fewer points), when you have a lot of tied ranks, or when you want something easy to explain in plain language: a tau of 0.6 breaks down to a clean split, 80% of all pairs agreed on direction and 20% disagreed.

pythonfrom scipy import stats # Small sample: customer satisfaction rank vs. support response time rank # (both ranked 1 = best, 5 = worst) satisfaction_rank = [1, 2, 3, 4, 5] response_time_rank = [2, 1, 3, 5, 4] tau, p_value = stats.kendalltau(satisfaction_rank, response_time_rank) print(f"Kendall's tau: {tau:.4f}") print(f"P-value: {p_value:.4f}")
OutputKendall's tau: 0.6000 P-value: 0.2333

Out of all 10 possible pairs in this tiny dataset, 8 agreed on direction (concordant) and 2 didn't (discordant), which is exactly where the 0.6 comes from. With only 5 data points, the p-value of 0.23 is a reminder not to read too much into it: this sample is too small to call the result statistically significant, even though the tau itself looks decent.

Big-tech use case: at Microsoft, when engineers evaluate search result quality, they often rank pages by relevance and compare two different ranking algorithms' outputs using Kendall's tau. A high tau means the two algorithms broadly agree on what's relevant. A low one means they disagree a lot, and someone needs to dig into why.

Choosing the right correlation method

Here's a simple way to decide:

Is your data continuous and roughly normally distributed?
Yes: use Pearson's r.
No, keep going.

Is your data ordinal, does it have outliers, or is the relationship non-linear but still monotonic?
Yes: use Spearman's ρ.

Is your sample very small (fewer than 30 points), or do you have a lot of tied ranks?
Yes: use Kendall's τ.

The most important lesson: correlation is not causation

This might be the single most important idea in all of statistics. Two variables being correlated does not mean one of them causes the other.

There's a famous, silly-sounding example: the number of Nicolas Cage movie releases in a year correlates with the number of drowning deaths, since both tend to peak in summer. Obviously his movies aren't drowning anyone. Both are being driven by a third factor entirely: summer itself, when pools are busiest and movie releases cluster. That hidden third factor has a name: a confounding variable, sometimes called a lurking variable.

Big-tech example: early studies found that Facebook users who shared more posts also had higher engagement with ads. It would be tempting to conclude "let's get people to share more, and ad revenue will follow." But the causation actually runs the other way: people who are already more engaged with the platform in general tend to both share more and click more ads. Forcing people to share more without any real engagement behind it wouldn't move ad performance at all.

Whenever you see a correlation, ask: is there a third variable that could explain this? Could the cause and effect actually be reversed from what seems obvious? For a long list of entertaining examples of correlations that mean absolutely nothing, Tyler Vigen's Spurious Correlations is worth a look.

Practical use case: correlation for feature selection in ML

One of the most practical everyday uses of correlation in data science is feature selection: deciding which columns actually deserve a place in your model.

The idea is simple. If two input features are very highly correlated with each other (say, above 0.90), they're carrying almost identical information. Keeping both doesn't help your model. It can actually hurt it, by causing multicollinearity in linear models.

pythoncorr_matrix = df[['bedrooms', 'bathrooms', 'area_sqft', 'distance_to_office', 'house_age', 'price']].corr() sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, vmin=-1, vmax=1) plt.title('Feature Correlation Matrix: Housing Data')
Correlation heatmap: housing data 1.00 0.89 0.87 -0.05 0.06 0.82 0.89 1.00 0.77 -0.04 0.02 0.74 0.87 0.77 1.00 -0.06 0.07 0.92 -0.05 -0.04 -0.06 1.00 0.05 -0.29 0.06 0.02 0.07 0.05 1.00 -0.10 0.82 0.74 0.92 -0.29 -0.10 1.00 bedrooms bathrooms area_sqft distance_to_office house_age price bedrooms bathrooms area_sqft distance_to_office house_age price
bedrooms, bathrooms, and area_sqft are all highly correlated with each other, since they all measure roughly the same thing: how big the house is.

In this housing dataset, bedrooms, bathrooms, and area_sqft are all highly correlated with each other, since they're all really just measuring the size of the house from different angles. Before feeding this into a linear regression model, you'd want to drop one or two of them rather than keep all three.

Full workflow: from data to insights

Here's roughly how a working data scientist would approach a correlation analysis from start to finish, using that same housing dataset.

pythonimport pandas as pd import numpy as np from scipy import stats import seaborn as sns import matplotlib.pyplot as plt def correlation_analysis(df, target_col, method='auto'): """Full correlation analysis workflow. Args: df: DataFrame with numeric columns target_col: the column you want to correlate others against method: 'pearson', 'spearman', or 'auto' (auto picks based on normality) """ numeric_df = df.select_dtypes(include=[np.number]) # Step 1: check normality with the Shapiro-Wilk test if method == 'auto': _, p_val = stats.shapiro(df[target_col]) method = 'pearson' if p_val > 0.05 else 'spearman' print(f"Normality test p-value: {p_val:.4f}") print(f"Auto-selected method: {method.upper()}\n") # Step 2: compute correlation with the target for every other column correlations = {} for col in numeric_df.columns: if col == target_col: continue if method == 'pearson': r, p = stats.pearsonr(df[target_col], df[col]) else: r, p = stats.spearmanr(df[target_col], df[col]) correlations[col] = {'correlation': r, 'p_value': p, 'significant': p < 0.05} result_df = pd.DataFrame(correlations).T result_df = result_df.sort_values('correlation', key=abs, ascending=False) # Step 3: show the results print(f"Correlation with '{target_col}':\n") print(result_df.round(4).to_string()) return result_df # Usage correlation_analysis(df, target_col='price', method='auto')
OutputNormality test p-value: 0.7368 Auto-selected method: PEARSON Correlation with 'price': correlation p_value significant area_sqft 0.9196 0.0000 True bedrooms 0.8154 0.0000 True bathrooms 0.7420 0.0000 True distance_to_office -0.2917 0.0003 True house_age -0.0999 0.2238 False

The function checked normality first (p = 0.74, comfortably normal, so it picked Pearson automatically), then ranked every feature by how strongly it relates to price. area_sqft, bedrooms, and bathrooms all come out strongly positive, and we already know from the heatmap above that those three are highly correlated with each other too. house_age is the one interesting result here: its correlation is weak enough (p = 0.22) that we can't even be confident it's real, not just noise in this particular sample.

area_sqft +0.92 bedrooms +0.82 bathrooms +0.74 distance_to_office -0.29 house_age -0.10
Correlation of each feature with price. Teal bars are statistically significant (p < 0.05); grey is not. house_age didn't clear that bar in this dataset.

Correlation is one of the most useful, and most misused, tools in data science. Used with the right method, backed up by an actual plot, and paired with a healthy dose of skepticism about causation, it can genuinely turn up real insight from a dataset. Skip any one of those three things and it can just as easily mislead you.

Quick check

Four questions to test whether this stuck.

A dataset has a Pearson r of 0.05 between two variables. Can you conclude they're unrelated?

Not necessarily. Pearson only detects straight-line relationships. Two variables could have a strong curved relationship, like the stress and job-performance example, and still produce a Pearson r near zero. Always plot the data before ruling out a relationship.

You're correlating customer satisfaction ratings (1 to 5 stars) with delivery time in minutes. Which correlation coefficient fits best, and why?

Spearman's rho. Star ratings are ordinal, so the gap between 3 and 4 stars isn't guaranteed to mean the same thing as the gap between 1 and 2. Spearman only needs the relationship to move consistently in one direction, which fits ordinal data far better than Pearson's straight-line assumption.

Ice cream sales and shark attacks are positively correlated. Does eating ice cream attract sharks?

No. Both tend to rise in summer, when more people are swimming and more people are buying ice cream. Summer is the confounding variable driving both, not any real connection between the two.

Two features in your dataset have a correlation of 0.95 with each other. What should you do before training a linear model?

Consider dropping one of them. At that level of correlation they're carrying almost the same information, and keeping both can cause multicollinearity, which makes a linear model's coefficients unstable and much harder to interpret.