Partial Correlation
The engine for Association Purification. Partial Correlation isolates the unique bond between two variables by mathematically neutralizing the influence of confounding third factors.
What is it?
Partial Correlation measures the linear relationship between two variables (X and Y) while programmatically holding constant the effects of one or more confounding variables (Z).
When to use it
- Third Confounder: A third variable Z is known to correlate with both X and Y.
- Spurious Checks: Confirm if a raw correlation is real or just a shared reflection of Z.
- Suppressed Links: Reveal hidden correlations that are masked by Z.
Core Idea
It removes the shared variance of Z from both X and Y, then correlates the remaining residuals (what is left of X vs. what is left of Y):
By correlating residuals, it evaluates the direct pathway of association rather than the indirect path through Z.
Hypotheses
How it works
- Run linear regression of X on Z; collect residuals (X_res).
- Run linear regression of Y on Z; collect residuals (Y_res).
- Correlate X_res with Y_res.
- Degrees of freedom decreases to N - 3 due to Z's constraint.
Assumptions
Important Note
🔍 Spurious Trap: Ice cream sales (X) and drowning rates (Y) correlate strongly (r ≈ 0.60). But when temperature (Z) is partialed out, the correlation drops to exactly zero!
Quick Example
| Pairings | Raw Correlation |
|---|---|
| Raw X vs Y | 0.65 |
| X vs Z (confounder) | 0.70 |
| Y vs Z (confounder) | 0.80 |
Partial Correlation Live Laboratory
Adjust raw correlations to see how the Venn diagram overlaps and isolates the direct r_xy.z pathway.
| Metric | Raw Score | Controlled (Partial) |
|---|---|---|
| Correlation (r) | 0.6000 | 0.2157 |
| Degrees of Freedom | 23 | 22 |
| t-statistic | 3.597 | 1.036 |
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: ρXY·Z = 0 (partial correlation is zero after controlling for Z)
Hₐ: ρXY·Z ≠ 0 (partial correlation is non-zero after controlling for Z)
Tests correlation between X and Y after removing linear effects of control variable(s) Z from both X and Y. If controlling for multiple variables, notation is ρXY·Z₁Z₂...Zₖ.
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- Scatterplot matrix for all variables to assess linearity
- Mahalanobis distance to identify multivariate outliers
- Univariate Q-Q plots for X, Y, and Z to check normality
- Compare zero-order correlation r_XY with partial correlation r_XY·Z to quantify control effect
- Mardia's test or Henze-Zirkler test for multivariate normality
- Residual plots from Y~Z and X~Z regressions to check homoscedasticity
- Variance inflation factor (VIF) if controlling multiple variables (detect multicollinearity)
- Bootstrap confidence intervals for r_partial as sensitivity check
- Sensitivity analysis with different control sets
- Compare Pearson and Spearman partial correlations for robustness
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Meditation and Stress Controlling for Sleep Quality
Research question: Is meditation practice duration associated with perceived stress, controlling for sleep quality? Design: Cross-sectional survey (N=120) measuring weekly meditation minutes (X, continuous 0-300), Perceived Stress Scale score (Y, continuous 0-40), and Pittsburgh Sleep Quality Index (Z, continuous 0-21, higher=worse sleep). Rationale: Sleep quality is a known confounder that affects both meditation adherence and stress levels. We test whether meditation-stress correlation persists after removing sleep's influence.
# Partial Correlation: Meditation-Stress controlling for Sleep
# Research: Does meditation predict stress after accounting for sleep quality?
# Install/load packages
library(ppcor) # For partial correlation
library(psych) # For pairs.panels
library(car) # For vif
library(MVN) # For multivariate normality tests
library(tidyverse)
# Simulate realistic data (or load: data <- read.csv("meditation_stress.csv"))
set.seed(2025)
library(MASS)
# Create correlated variables with known structure:
# Sleep (Z) causes both Meditation (X) and Stress (Y)
# Meditation (X) causes Stress (Y)
mu <- c(150, 20, 10) # Means: meditation=150min, stress=20, sleep=10
Sigma <- matrix(c(
2500, -30, -50, # Meditation variance & covariances
-30, 64, 15, # Stress variance & covariances
-50, 15, 25 # Sleep variance & covariances
), nrow=3, byrow=TRUE)
data_matrix <- mvrnorm(n=120, mu=mu, Sigma=Sigma)
data <- data.frame(
meditation_min = pmax(0, pmin(300, data_matrix[,1])), # Bounded 0-300
stress_score = pmax(0, pmin(40, data_matrix[,2])), # Bounded 0-40
sleep_quality = pmax(0, pmin(21, data_matrix[,3])) # Bounded 0-21
)
# === STEP 1: Check Assumptions ===
# 1. Linearity: Scatterplot matrix
pairs.panels(data,
method = "pearson",
hist.col = "steelblue",
density = TRUE,
ellipses = TRUE,
main = "Scatterplot Matrix: Linearity Check")
# Look for: linear patterns (not curved), correlation ellipses
# 2. Multivariate normality
cat("\n=== Multivariate Normality Tests ===\n")
mvn_test <- mvn(data, mvnTest = "mardia")
print(mvn_test$multivariateNormality)
# Mardia skewness and kurtosis p > .05 → multivariate normal
# Univariate normality for each variable
cat("\n=== Univariate Normality(Shapiro-Wilk) ===\n")
shapiro.test(data$meditation_min)
shapiro.test(data$stress_score)
shapiro.test(data$sleep_quality)
# Q-Q plots
par(mfrow=c(1,3))
qqnorm(data$meditation_min, main="Meditation Q-Q Plot")
qqline(data$meditation_min)
qqnorm(data$stress_score, main="Stress Q-Q Plot")
qqline(data$stress_score)
qqnorm(data$sleep_quality, main="Sleep Q-Q Plot")
qqline(data$sleep_quality)
# 3. Multivariate outliers (Mahalanobis distance)
mahal <- mahalanobis(data, colMeans(data), cov(data))
mahal_cutoff <- qchisq(0.999, df=3) # χ² critical value, df = # variables
outliers <- which(mahal > mahal_cutoff)
cat("\n=== Multivariate Outliers ===\n")
cat("Number of outliers(p < .001):", length(outliers), "\n")
if(length(outliers) > 0) {
cat("Outlier cases:", outliers, "\n")
}
# 4. Homoscedasticity: residual plots from Y~Z and X~Z
par(mfrow=c(1,2))
fit_yz <- lm(stress_score ~ sleep_quality, data=data)
plot(fit_yz, which=1, main="Y~Z Residuals(Stress~Sleep)")
fit_xz <- lm(meditation_min ~ sleep_quality, data=data)
plot(fit_xz, which=1, main="X~Z Residuals(Meditation~Sleep)")
# Look for: constant spread (no funnel shape)
# === STEP 2: Compute Zero-Order (Bivariate) Correlation ===
cat("\n=== Zero-Order Correlation(before controlling) ===\n")
zero_order <- cor.test(data$meditation_min, data$stress_score)
print(zero_order)
# r_XY = correlation ignoring sleep
# === STEP 3: Compute Partial Correlation ===
cat("\n=== Partial Correlation(controlling for Sleep) ===\n")
# Method 1: Using ppcor package (easiest)
partial_result <- pcor.test(data$meditation_min,
data$stress_score,
data$sleep_quality)
print(partial_result)
# r_XY·Z = partial correlation after removing sleep effects
# Method 2: Manual calculation (for understanding)
# Step 1: Regress Y on Z, get residuals (Y with Z removed)
resid_y <- residuals(lm(stress_score ~ sleep_quality, data=data))
# Step 2: Regress X on Z, get residuals (X with Z removed)
resid_x <- residuals(lm(meditation_min ~ sleep_quality, data=data))
# Step 3: Correlate the residuals
partial_manual <- cor.test(resid_x, resid_y)
cat("\nManual partial correlation(residual method):\n")
print(partial_manual)
# Visualize residual correlation
ggplot(data.frame(resid_x, resid_y), aes(x=resid_x, y=resid_y)) +
geom_point(alpha=0.5) +
geom_smooth(method="lm", se=TRUE, color="red") +
labs(title="Partial Correlation Visualization",
subtitle="Meditation-Stress residuals after controlling Sleep",
x="Meditation residuals(sleep removed)",
y="Stress residuals(sleep removed)") +
theme_classic()
# === STEP 4: Compare Zero-Order vs Partial ===
cat("\n=== Comparison ===\n")
cat(sprintf("Zero-order r_XY: %.3f (p = %.4f)\n",
zero_order$estimate, zero_order$p.value))
cat(sprintf("Partial r_XY·Z: %.3f (p = %.4f)\n",
partial_result$estimate, partial_result$p.value))
cat(sprintf("Change in r: %.3f\n",
partial_result$estimate - zero_order$estimate))
cat(sprintf("Proportion explained by sleep: %.1f%%\n",
100 * (zero_order$estimate - partial_result$estimate) / zero_order$estimate))
# === STEP 5: Effect Size ===
cat("\n=== Effect Sizes ===\n")
cat(sprintf("Partial r²: %.3f (%.1f%% unique variance)\n",
partial_result$estimate^2,
100*partial_result$estimate^2))
# Interpret partial r (Cohen's guidelines)
if(abs(partial_result$estimate) < 0.1) {
interpretation <- "negligible"
} else if(abs(partial_result$estimate) < 0.3) {
interpretation <- "small"
} else if(abs(partial_result$estimate) < 0.5) {
interpretation <- "medium"
} else {
interpretation <- "large"
}
cat(sprintf("Interpretation: %s effect\n", interpretation))
# === STEP 6: Bootstrap 95% CI (sensitivity check) ===
library(boot)
boot_partial <- function(data, indices) {
d <- data[indices,]
pcor.test(d$meditation_min, d$stress_score, d$sleep_quality)$estimate
}
set.seed(2025)
boot_results <- boot(data, boot_partial, R=1000)
boot_ci <- boot.ci(boot_results, type="perc")
cat("\n=== Bootstrap 95% CI ===\n")
print(boot_ci)
# === APA-Style Reporting ===
cat("\n=== APA-Style Report ===\n")
cat(sprintf(
"A partial correlation was computed to assess the relationship between\nmeditation practice and perceived stress while controlling for sleep quality.\nAssumptions were checked: multivariate normality was satisfied(Mardia test\np > .05), linearity was confirmed via scatterplot matrix, and no extreme\nmultivariate outliers were detected(Mahalanobis D² < χ²_critical).\n\nThe zero-order correlation between meditation and stress was r = %.3f, p = %.3f.\nAfter controlling for sleep quality, the partial correlation was r_partial = %.3f,\np = %.3f, 95%% CI [%.3f, %.3f]. Sleep quality accounted for %.1f%% of the\noriginal correlation. The partial correlation r² = %.3f indicates that meditation\nexplains %.1f%% of unique variance in stress, beyond sleep quality. This represents\na %s effect size(Cohen, 1988).\n",
zero_order$estimate, zero_order$p.value,
partial_result$estimate, partial_result$p.value,
boot_ci$percent[4], boot_ci$percent[5],
100 * (zero_order$estimate - partial_result$estimate) / zero_order$estimate,
partial_result$estimate^2,
100 * partial_result$estimate^2,
interpretation
))The zero-order correlation (r = -0.12, ignoring sleep) was attenuated after controlling for sleep quality (r_partial = -0.08, p = .38). Sleep quality explained 33% of the original meditation-stress correlation, suggesting sleep is a substantial confounder. The partial correlation was non-significant, indicating meditation's unique association with stress (beyond sleep) is weak. This highlights the importance of controlling for sleep when studying meditation-stress relationships.
Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Partial Spearman Rho — The robust rank-based equivalent for controlled associations.
- Polynomial Residualization — Model the confounder using squared terms before partialling.
- Semipartial Correlation — Isolate the unique variance of ONLY one variable, not both.
- Ridge-Augmented Correlation — Apply L2 penalties to stabilize the purified bond.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
- Compare Pearson vs Spearman partial correlations
- Bootstrap confidence intervals
- Test with different control variable sets
- Examine change from zero-order to partial correlation
Partial correlation is typically a single test, not omnibus. Post-hoc considerations:
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
Same as Pearson r: Small: .10, Medium: .30, Large: .50 (Cohen, 1988). Represents correlation after removing control variables from both X and Y
Proportion of variance in Y (after removing Z) explained by X (after removing Z). Example: r_partial = .30 → r² = .09 → 9% unique variance explained
If |r_partial| << |r_zero-order|, control variable is substantial confounder. If |r_partial| ≈ |r_zero-order|, control variable has minimal confounding
0.2
0.5
0.8
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
At least 30 observations for stable correlation estimates. For multiple controls, n > 20 + 8*k where k = number of control variables
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | α=.05, power=.80 | n ≈ 783 |
| Medium Effect | α=.05, power=.80 | n ≈ 84 |
| Large Effect | α=.05, power=.80 | n ≈ 28 |
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
A Pearson/Spearman partial correlation was computed to assess the relationship between X variable and Y variable while controlling for Z variable(s). State assumption checks: 'Assumptions of linearity, multivariate normality, and absence of extreme outliers were met' or describe violations and remedies. The zero-order correlation between X and Y was r = .XX, p = .XXX. After controlling for Z, the partial correlation was r_partial = .XX, p = .XXX, 95% CI .XX, .XX. Control variable(s) accounted for XX% of the original correlation. The partial r² = .XX indicates that X explains XX% of unique variance in Y beyond Z, representing a small/medium/large effect size (Cohen, 1988). Interpret in research context.
- Zero-order correlation r_XY with p-value
- Partial correlation r_XY·Z with p-value
- 95% confidence interval for partial r
- df (n - k - 2, where k = number of controls)
- partial r² (unique variance explained)
- List all control variables
- Statement about assumption checks
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Relationship | Zero-Order r | Partial r (pr) | Change | p (Partial) |
|---|---|---|---|---|
| Income ↔ Health | .65 | .25 | -.40 (Spurious) | .004 |
| Exercise ↔ Health | .45 | .42 | -.03 (Robust) | < .001 |
The Raw Link. The simple correlation before any controlling/adjusting.
The True Link. The correlation that remains after the 'noise' of the control variable (Age) is mathematically removed.
False Connection. When the raw correlation collapses after adjustment, the original link was driven by the third variable.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Execute Partial Correlation
ppcor::pcor.test(x, y, z)
# 2. Full Partial Matrix
psych::partial.r(df, x=c('var1','var2'), y='control_var')Partial correlation is the foundation of Causal Inference. Use it to destroy 'Spurious Correlations' (e.g., Ice Cream Sales vs. Shark Attacks, controlling for Temperature).
# Graphical Gaussian Models (Network of Partials)
# Visualizing the 'pure' network structure
qgraph::qgraph(cor(df), graph = 'pcor')Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.