Correlation Analysis in Python
The five-step procedure
- Identify the variables. Choose the variables to correlate. For Pearson they should be quantitative (numeric).
- Collect the data. From surveys, experiments, sensors, databases or existing records.
- Choose the coefficient. Pearson for linear numeric data; Spearman / Kendall for ranks or curves; Phi / Cramer's V for categories.
- Calculate it. Use a formula or, better, a library.
- Interpret it. Look at sign, size, significance, the scatter plot, and think about confounders.
Using NumPy
import numpy as np
x = np.array([1, 2, 3, 4, 5])
y = np.array([5, 7, 3, 9, 1])
print(np.corrcoef(x, y))
# [[ 1. -0.3]
# [-0.3 1. ]]
np.corrcoef returns a correlation matrix. The diagonal is always 1 (a variable is perfectly correlated with itself). The off-diagonal value -0.3 is the correlation between x and y - a weak negative link.
Using pandas
import pandas as pd
data = pd.DataFrame({"X": [1, 2, 3, 4, 5], "Y": [5, 7, 3, 9, 1]})
print(data["X"].corr(data["Y"])) # -0.3 (Pearson)
print(data["X"].corr(data["Y"], method="spearman")) # rank based
print(data["X"].corr(data["Y"], method="kendall")) # Kendall tau
Series has .corr() for one pair; DataFrame has .corr() for all numeric pairs, returning a matrix.
Correlation matrix and heatmap for the whole dataset
plt.figure(figsize=(14, 9))
corr = df.corr(numeric_only=True)
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", center=0, linewidths=1)
plt.title("Correlation Heatmap")
plt.show()
# Which features relate most to the target?
print(corr["quality"].sort_values(ascending=False))
# Show only the upper-triangle so each pair appears once
mask = np.triu(np.ones_like(corr, dtype=bool))
sns.heatmap(corr, mask=mask, annot=True, fmt=".2f", cmap="coolwarm", center=0)
plt.show()
Reading the heatmap
- Values close to +1 = strong positive correlation; -1 = strong negative; 0 = no linear correlation.
- With
cmap="coolwarm", center=0, red = positive, blue = negative, pale = near zero. - The matrix is symmetric, so the mask (upper triangle) removes repeated information.
- Look at the target row (
quality) first: which features correlate most with it? In the wine data, alcohol usually shows the strongest positive link and volatile acidity a negative one. - Then look for feature-feature pairs with |r| above about 0.8 (for example,
fixed acidityanddensity, orfreeandtotal sulfur dioxide). These are redundant and cause multicollinearity in linear models.
Finding the strongest pairs programmatically
corr = df.corr(numeric_only=True)
pairs = (
corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool)) # upper triangle only
.stack()
.rename("r")
.reset_index()
)
pairs["abs_r"] = pairs["r"].abs()
print(pairs.sort_values("abs_r", ascending=False).head(10))
Statistical significance
from scipy import stats
r, p = stats.pearsonr(df["alcohol"], df["quality"])
print(f"r = {r:.2f}, p = {p:.4g}")
A small p-value (for example, below 0.05) says the correlation is unlikely to be pure chance. It does not say the relationship is important - with a large dataset even a tiny r can be "significant".
Pitfalls to remember
- Drop identifier columns such as
Idbefore computing correlations (an ID has no real meaning). - Handle missing values first (
corrskips pairs with NaN, which can change sample sizes silently). - Encode categorical columns properly before correlating.
- Always confirm surprising numbers with a scatter plot.