# Correlation or linear regression? The slope, R², and comparing slopes between groups

> Use correlation to describe how closely two measurements move together; use regression when you need the slope, such as change per week or per dose. To compare groups, test the difference in slopes. R and Python code.

Canonical: https://www.licklider.ai/guides/correlation-vs-linear-regression/
Section: Guides › Correlation, regression and survival
Author: Licklider · Published 2026-11-11 · Last reviewed 2026-11-11 (UTC)

> **Quick answer.** **Correlation** (r) describes how closely two measurements move together; **regression** gives the **slope**, how much one changes per unit of the other, with a 95% confidence interval. Use regression when the question is "how much?" (density lost per week, response per dose) or when you set the x values yourself; use correlation to describe the association between two things you measured. With one x and one y and no other terms, both give the same p-value. To compare two groups, **test the difference in slopes** with an interaction term (`y ~ x * group`); "significant in one group but not the other" is not evidence that the groups differ.

**Terms used below.** *Pearson's r*: a number from −1 to +1 describing how closely points follow a straight line. *Slope*: the change in y for a one-unit change in x. *R²*: the share of the variation in y explained by the line; in simple regression it equals r². *Residual*: a point's vertical distance from the fitted line. *Interaction term*: a model term that lets each group have its own slope; its coefficient is the difference between the slopes, second group minus first (here KO − WT), so reordering the groups reverses the sign. *95% confidence interval (CI)*: the range of values compatible with the data. *df* (degrees of freedom): a number printed with the test that reflects the amount of independent data. *Extrapolation*: using the line outside the range of the data. *p-value*: the chance of a result at least this extreme if there were no real effect. *t*: the test statistic, the estimate divided by its standard error. *Ordinary least squares*: the usual way of fitting a straight line. *Welch's t-test*: compares two means without assuming equal spread. *Mixed model*: a model that keeps several measurements per animal and knows which animal each came from. *HC3 (robust) standard errors*: standard errors that allow unequal scatter between groups. *α*: the false-positive threshold, usually 0.05. *Formula notation*: `y ~ x * group` means model y from x, group and their interaction; software names the interaction term like `age_weeks:genotype[T.KO]` (predictor : group [level]). *Bland–Altman limits of agreement*: the range expected to contain 95% of the differences between two methods.

## Which one answers your question

| Your question | Use | Report |
| --- | --- | --- |
| How much does y change per unit of x? (per week, per dose, per gram) | Linear regression | Slope with 95% CI, n; R² (= r² in simple regression) describes the fit for the x values in your data |
| Do two measured quantities move together, and how closely? | Correlation | r with 95% CI, n ([Pearson vs Spearman](/guides/pearson-vs-spearman-correlation/)) |
| You set x yourself (doses, time points, concentrations) | Regression | r and R² depend on the x values you chose, so report the slope with its CI |
| Do two groups differ in how y changes with x? | Regression with an interaction term, `y ~ x * group`, one observation per animal | Difference in slopes with 95% CI and its p-value |
| Do two methods give the same values? | Neither; use Bland–Altman limits of agreement | See [Pearson vs Spearman](/guides/pearson-vs-spearman-correlation/) |
| Several measurements per animal over time | A summary per animal (slope per mouse), then Welch's t-test, or a mixed model | See [Repeated measures ANOVA vs mixed model](/guides/repeated-measures-anova-vs-mixed-model/) |

Bewick and colleagues put the difference simply: "Correlation quantifies the strength of the linear relationship between a pair of variables, whereas regression expresses the relationship in the form of an equation" ([Bewick et al. 2003](https://doi.org/10.1186/cc2401)). The correlation also depends on how spread out the x values are: "Correlation depends on the range of the true quantity in the sample. If this is wide, the correlation will be greater than if it is narrow" ([Bland & Altman 1986](https://doi.org/10.1016/S0140-6736(86)90837-8)). When x was chosen by design, report the slope with its interval rather than r.

## Worked example: same true slope, different r

Constructed data: bone density in 10 wild-type (WT) and 10 knockout (KO) mice, one measurement per mouse at ages 4 to 20 weeks. By construction, **both genotypes lose density at the same rate** (0.8 units per week). This dataset was chosen from simulated datasets so that one genotype's correlation is significant and the other's is not; the simulation below shows how often that happens. Stored code and data: [density.csv](/guides/correlation-vs-linear-regression/density.csv), [analysis.py](/guides/correlation-vs-linear-regression/analysis.py), [analysis.R](/guides/correlation-vs-linear-regression/analysis.R) and [the analysis output](/guides/correlation-vs-linear-regression/analysis_output.txt); both languages print the same output.

| Analysis | WT | KO |
| --- | --- | --- |
| Pearson's r | −0.44 (p = 0.201) | −0.86 (p = 0.001) |
| Slope per week (95% CI) | −0.82 (−2.19 to 0.54) | −0.97 (−1.45 to −0.50) |
| R² | 0.20 | 0.74 |

A tempting conclusion is "density falls with age in KO but not in WT". The slopes say otherwise: −0.82 and −0.97 per week, with the WT interval wide because the WT points happen to scatter more. The direct test of the difference in slopes (KO − WT) gives **−0.15 per week (95% CI −1.46 to 1.16), t = −0.24, df = 16 (20 mice minus 4 estimated parameters), p = 0.811**: no evidence that the genotypes differ, although the interval is too wide to rule out a moderate difference. The two fitted slopes differ only by chance; 0.8 is the value used to build the data. Fitted with one common slope (`density ~ age_weeks + genotype`), the slope is −0.90 per week (95% CI −1.53 to −0.27). The WT points scatter about 2.8 times as much around their line as the KO points (residual SD 9.24 vs 3.35), and the interaction test assumes equal scatter; a version that does not assume equal scatter, using robust (HC3) standard errors, gives the same conclusion (p = 0.824). Note also that each group's correlation p-value equals its slope p-value; the two are the same test in simple regression.

In 10,000 simulated studies with this design (10 mice per genotype, ages 4 to 20 weeks, identical true slopes of −0.8 per week, noise SD 6, significance threshold α = 0.05), the rule "significant in one genotype, not in the other" claimed a difference in **50.7%** of studies; the interaction test did so in 5.2% ([simulation.py](/guides/correlation-vs-linear-regression/simulation.py), [the simulation output](/guides/correlation-vs-linear-regression/simulation_output.txt)).

```python
import pandas as pd
import statsmodels.formula.api as smf

d = pd.read_csv("density.csv")                  # columns: genotype, age_weeks, density (rename to yours)
d["genotype"] = pd.Categorical(d.genotype, ["WT", "KO"])
m = smf.ols("density ~ age_weeks * genotype", d).fit()
print(m.params)                                  # term names: <predictor>:<group>[T.<level>]
term = "age_weeks:genotype[T.KO]"                # difference in slopes, KO - WT
lo, hi = m.conf_int().loc[term]
print(f"difference in slopes = {m.params[term]:.2f} (95% CI {lo:.2f} to {hi:.2f}), "
      f"t = {m.tvalues[term]:.2f}, df = {m.df_resid:.0f}, p = {m.pvalues[term]:.3f}")
rob = smf.ols("density ~ age_weeks * genotype", d).fit(cov_type="HC3", use_t=True)
print(rob.pvalues[term])                          # robust to unequal scatter between groups
import matplotlib.pyplot as plt                   # plot first: points and lines by group
for g, s in d.groupby("genotype", observed=True):
    plt.scatter(s.age_weeks, s.density, label=g)
    fit = smf.ols("density ~ age_weeks", s).fit()
    plt.plot(s.age_weeks.sort_values(), fit.predict(s.sort_values("age_weeks")))
plt.legend(); plt.savefig("density.png")
```

```r
d <- read.csv("density.csv")
d$genotype <- factor(d$genotype, levels = c("WT", "KO"))
m <- lm(density ~ age_weeks * genotype, data = d)
cbind(estimate = coef(m), confint(m))["age_weeks:genotypeKO", ]   # difference in slopes, KO - WT
summary(m)$coefficients["age_weeks:genotypeKO", ]; m$df.residual
lmtest::coeftest(m, vcov = sandwich::vcovHC(m, type = "HC3"))["age_weeks:genotypeKO", ]  # robust to unequal scatter
plot(density ~ age_weeks, data = d, col = genotype, pch = 19)    # plot first
for (g in levels(d$genotype)) abline(lm(density ~ age_weeks, data = d[d$genotype == g, ]), col = which(levels(d$genotype) == g))
plot(fitted(m), resid(m))                                        # residuals: look for curves or a funnel
```

In R, `x * group` expands to `x + group + x:group` ([R documentation](https://stat.ethz.ch/R-manual/R-devel/library/stats/html/lm.html)); the `x:group` coefficient is the difference in slopes. In GraphPad Prism, the "compare slopes and intercepts" option does the same comparison ([GraphPad guide](https://www.graphpad.com/guides/prism/latest/curve-fitting/reg_comparingslopesandintercepts.htm)).

## Check the regression's conditions

1. **Plot first** (code above). A scatterplot with the fitted line shows curvature, outliers and points with unusual x values that a single number hides.
2. **Residuals.** Bewick and colleagues write: "If the relationship is linear and the variability constant, then the residuals should be evenly scattered around 0 along the range of fitted values." A funnel or a curve means the straight line, or the constant spread, does not fit; consider logs or a curved model.
3. **One point per animal.** "The observations are assumed to be independent" ([Bewick et al. 2003](https://doi.org/10.1186/cc2401)). Several cells or several time points from one mouse are not independent. For several cells, average per animal ([what counts as n](/guides/what-counts-as-n/)); for several time points, fit one slope per animal or a mixed model ([Repeated measures ANOVA vs mixed model](/guides/repeated-measures-anova-vs-mixed-model/)).
4. **Similar scatter in each group.** The interaction test assumes the points scatter about equally around each group's line. If the residual SDs differ clearly, as in the example, report the robust (HC3) test (code above) alongside; if the two disagree, rely on the robust one.
5. **Stay within the data.** The SAMPL guidelines advise: "Do not extend the regression line (or the interpretation of the analysis) beyond the minimum and maximum values of the data" ([Lang & Altman 2015](https://doi.org/10.1016/j.ijnurstu.2014.09.006)).
6. **Which variable is y.** Regressing y on x and x on y give different lines unless r = ±1. Put the outcome you want to predict or explain on the y-axis, decided before the analysis.

## Check what an AI assistant ran

1. **"Significant in one group, not in the other"** used as the comparison between groups. Fit `y ~ x * group` and report the interaction.
2. **A correlation reported where you set x** (doses, time points). Report the slope with its CI.
3. **Every cell or time point treated as an independent point.** Average per animal or use a model for repeated measurements.
4. **No residual plot, or a line extended beyond the measured range.** Check both.

## Report it

Methods:

> Bone density was regressed on age (ordinary least squares); to compare genotypes, an age × genotype interaction term was included (`lm(density ~ age_weeks * genotype)`, R [4.3.3]), and the difference in slopes is reported with its 95% CI. Residuals were inspected for linearity and constant spread. Each mouse contributed one measurement (n = [10] per genotype).

Results:

> Density fell by [0.90] units per week (95% CI [0.27] to [1.53]) with a common slope; the slopes did not differ clearly between genotypes (difference, KO − WT, [−0.15] per week, 95% CI [−1.46] to [1.16]; p = [0.811]).

If the slopes do differ, report each group's slope with its interval and the difference: *"Density fell by [x] per week in WT (95% CI [x] to [x]) and by [x] in KO ([x] to [x]); difference [x] (95% CI [x] to [x]; p = [x])."* The output prints slopes as negative numbers; the sentences say "fell by" and drop the sign. For several measurements per animal, fit one slope per mouse and compare them with Welch's t-test, as the [repeated measures guide](/guides/repeated-measures-anova-vs-mixed-model/) shows.

Replace the bracketed values with your own.

## References

- Bewick V, Cheek L, Ball J (2003). Statistics review 7: correlation and regression. *Critical Care* 7:451–459. [doi:10.1186/cc2401](https://doi.org/10.1186/cc2401)
- Bland JM, Altman DG (1986). Statistical methods for assessing agreement between two methods of clinical measurement. *Lancet* 327:307–310. [doi:10.1016/S0140-6736(86)90837-8](https://doi.org/10.1016/S0140-6736(86)90837-8)
- Lang TA, Altman DG (2015). Basic statistical reporting for articles published in biomedical journals: the "Statistical Analyses and Methods in the Published Literature" or the SAMPL guidelines. *International Journal of Nursing Studies* 52:5–9. [doi:10.1016/j.ijnurstu.2014.09.006](https://doi.org/10.1016/j.ijnurstu.2014.09.006)
- R Core Team. `lm`: fitting linear models. [R documentation](https://stat.ethz.ch/R-manual/R-devel/library/stats/html/lm.html) (checked 11 November 2026)
- GraphPad Prism Curve Fitting Guide: comparing slopes and intercepts. [graphpad.com](https://www.graphpad.com/guides/prism/latest/curve-fitting/reg_comparingslopesandintercepts.htm) (checked 11 November 2026)

## Related

- [Pearson vs Spearman correlation](https://www.licklider.ai/guides/pearson-vs-spearman-correlation/)
- [Two-way ANOVA vs separate t-tests](https://www.licklider.ai/guides/two-way-anova-vs-multiple-t-tests/)
- [Repeated measures ANOVA vs mixed model](https://www.licklider.ai/guides/repeated-measures-anova-vs-mixed-model/)
- [How to interpret confidence intervals](https://www.licklider.ai/guides/how-to-interpret-confidence-intervals/)

## How this page was made

- Written and published by Licklider. Recommendations cite peer-reviewed methods papers, reporting guidelines and official software documentation listed under References; cited passages were checked against the source text.
- The worked example uses constructed data. Its numbers were produced by stored code (Python 3.13 with SciPy 1.18.1 and statsmodels 0.15.0; R 4.3.3, with sandwich 3.1.3 and lmtest 0.9.40 for the robust check), and the two languages print identical output; the simulation was run in Python.
- Separate review passes checked the statistics, the citations and readability before publication.
