Answer

Is R-squared useful or misleading?

Inspired by a question on Cross Validated ·

regression

The short answer

Both, depending on the question. R-squared tells you what share of the variation in your outcome the model accounts for in this sample. It does not tell you whether the model is correct, whether the relationship is linear, or how precise your predictions are, and it depends heavily on how widely the predictor happens to vary. Use it as a descriptive summary alongside residual plots and the residual standard error, not as a score of model quality.

What R-squared actually measures

For a linear regression with an intercept, R-squared is one minus the ratio of the residual sum of squares to the total sum of squares. In words: of all the variation in y around its mean, what fraction is accounted for by the fitted values? It runs from 0 to 1 and, for simple regression, equals the squared correlation between x and y.

That is a statement about this sample, not about the truth of the model. Two very different things feed into it: how strongly y responds to x, and how much x itself varies. In the population, for a straight-line model with slope b, the share of variance explained is b² Var(x) / (b² Var(x) + σ²), where σ² is the noise variance. Hold the slope and the noise fixed and widen the range of x, and R-squared climbs toward 1. Narrow the range, and it sinks toward 0, even though the model is exactly right in both cases.

Three ways it misleads

A low R-squared is not a failure either. In fields where outcomes are driven by many small influences, such as behavior or health, a correct model with a precisely estimated effect can explain only a few percent of the variance. Whether that effect matters depends on its size and its confidence interval, not on R-squared.

See it in R and Python

The code below makes each problem concrete with base R: the same straight line with a wide and a narrow range of x, a straight line fitted to a curve, and ten pure-noise predictors added to a model. The Python tab does the same analysis with NumPy.

R

set.seed(13314)
n <- 200

# 1. Same line, same noise, different spread of x
x_wide   <- runif(n, 0, 20)
x_narrow <- runif(n, 0, 2)
y_wide   <- 1 + 0.5 * x_wide   + rnorm(n, sd = 1)
y_narrow <- 1 + 0.5 * x_narrow + rnorm(n, sd = 1)
fw <- lm(y_wide ~ x_wide)
fn <- lm(y_narrow ~ x_narrow)
summ <- function(f) c(slope = coef(f)[[2]], se = coef(summary(f))[2, 2],
                      sigma = sigma(f), R2 = summary(f)$r.squared)
round(rbind(wide = summ(fw), narrow = summ(fn)), 3)

# 2. A straight line fitted to a curve can still score a high R2
x <- runif(n, 1, 10)
y <- x^2 + rnorm(n, sd = 2)
lin  <- lm(y ~ x)
quad <- lm(y ~ x + I(x^2))
round(c(R2_linear = summary(lin)$r.squared, R2_quadratic = summary(quad)$r.squared,
        sigma_linear = sigma(lin), sigma_quadratic = sigma(quad)), 3)

# 3. Pure-noise predictors still push R2 up; adjusted R2 does not follow
noise <- matrix(rnorm(n * 10), n, 10)
f0 <- lm(y_narrow ~ x_narrow)
f1 <- lm(y_narrow ~ x_narrow + noise)
round(rbind(
  one_predictor   = c(R2 = summary(f0)$r.squared, adjR2 = summary(f0)$adj.r.squared),
  plus_10_noise   = c(R2 = summary(f1)$r.squared, adjR2 = summary(f1)$adj.r.squared)
), 3)

Python

import numpy as np

rng = np.random.default_rng(13314)
n = 200

def fit(y, *cols):
    # OLS with intercept; returns coefficients, their SEs, sigma, R2 and adjusted R2
    X = np.column_stack([np.ones(len(y)), *cols])
    beta, *_ = np.linalg.lstsq(X, y, rcond=None)
    resid = y - X @ beta
    df = len(y) - X.shape[1]
    s2 = resid @ resid / df
    se = np.sqrt(np.diag(s2 * np.linalg.inv(X.T @ X)))
    r2 = 1 - resid @ resid / np.sum((y - y.mean()) ** 2)
    adj = 1 - (1 - r2) * (len(y) - 1) / df
    return beta, se, np.sqrt(s2), r2, adj

# 1. Same line, same noise, different spread of x
x_wide, x_narrow = rng.uniform(0, 20, n), rng.uniform(0, 2, n)
y_wide = 1 + 0.5 * x_wide + rng.normal(0, 1, n)
y_narrow = 1 + 0.5 * x_narrow + rng.normal(0, 1, n)
for name, (y, x) in {"wide": (y_wide, x_wide), "narrow": (y_narrow, x_narrow)}.items():
    b, se, s, r2, _ = fit(y, x)
    print(f"{name:7s} slope={b[1]:.3f} se={se[1]:.3f} sigma={s:.3f} R2={r2:.3f}")

# 2. A straight line fitted to a curve can still score a high R2
x = rng.uniform(1, 10, n)
y = x**2 + rng.normal(0, 2, n)
lin, quad = fit(y, x), fit(y, x, x**2)
print(f"R2 linear={lin[3]:.3f} quadratic={quad[3]:.3f}; sigma linear={lin[2]:.3f} quadratic={quad[2]:.3f}")

# 3. Pure-noise predictors still push R2 up; adjusted R2 does not follow
noise = rng.normal(size=(n, 10))
f0, f1 = fit(y_narrow, x_narrow), fit(y_narrow, x_narrow, noise)
print(f"one predictor: R2={f0[3]:.3f} adjR2={f0[4]:.3f}")
print(f"plus 10 noise: R2={f1[3]:.3f} adjR2={f1[4]:.3f}")

The figures below come from one seeded run of the R code. Python uses a different random number generator, so its figures differ slightly, but the pattern is the same.

When R-squared is useful

For judging whether the model is right, look at residual plots. For judging how precise predictions will be, look at the residual standard error, which is in the units of y. For judging whether an effect is real and how large it is, look at the coefficient and its confidence interval. If you are choosing between models with different numbers of predictors, our page on AIC vs BIC covers the penalized alternatives.

How to report R-squared in APA style (7th edition)

Report R-squared (or adjusted R-squared) as part of the model results, not as the headline. Italicize R, give two decimals, and drop the leading zero because it cannot exceed 1. Using the wide-range example above:

"A simple linear regression showed that y increased with x, b = 0.51, SE = 0.01, and the model accounted for most of the variance in y, R² = .89."

For models with several predictors, report adjusted R² as well, and when a block of predictors is added, report the change (ΔR²) with its F test. In a regression table, put R² and adjusted R² in the bottom rows under the coefficients.

Related tools and guides

More answered questions

Working with your own data?

General answers only go so far. Send us your situation and we'll reply by email within two business days.

Ask your question

Written by AskStats with AI assistance. This is general information, not advice for your specific data or study. When the results matter, check your approach with a qualified statistician.