Celeus

R vs R-squared: what each tells you

r gives the direction and strength of a straight-line link; R-squared is the share of variation a model accounts for. Both worked on 31 trees in R.

On this page

Pearson’s r and R-squared come from the same arithmetic, so they are easy to treat as one number. They answer different questions. r says how closely two measures follow a straight line and in which direction. R-squared says what share of the variation in an outcome a fitted model accounts for. This post computes both on 31 trees, shows when they coincide and when they do not, and says which to report.

What r tells you

Pearson’s r summarises a straight-line association between two continuous measures, from -1 to 1. The sign gives the direction: positive means the outcome tends to rise when the predictor rises, negative means it tends to fall. The size gives the tightness: values near 0 mean no straight-line pattern, values near 1 or -1 mean the points hug a line. Swapping the two measures changes nothing, because r treats them symmetrically (Rodgers and Nicewander 1988 give thirteen ways to read it).

For the worked example we use R’s built-in trees data: 31 felled black cherry trees, one row each. We take trunk girth (recorded in the dataset as a diameter in inches) and timber volume (cubic feet). The rows are different trees, so we treat them as independent units with two continuous measurements each, which is the design Pearson’s correlation is built for. The engine ran the correlation with the method fixed in advance:

  • Test: Pearson correlation, n = 31.
  • Result: r = 0.9671, 95% CI 0.9323 to 0.9842, p < 0.001.
  • Sample-size check: passed.

Read as an association: in these trees, thicker trunks go with larger volumes, very tightly. The interval says the population value is plausibly anywhere from about 0.93 to 0.98 if these trees stand in for similar ones. The method reference for Pearson correlation describes the method, and the choosing a statistical test guide covers when a correlation is the right summary at all.

What R-squared tells you

R-squared belongs to a model, not to a pair of measures. After you fit a regression, R-squared is the proportion of the outcome’s total variation, measured as a sum of squared distances from its mean, that the fitted model accounts for. It runs from 0 (no better than predicting the mean for everyone) to 1 (every point on the fit), and it has no sign.

For one predictor and an intercept, the two are linked exactly: R-squared is r squared. Here r = 0.9671, and a straight-line fit of volume on girth gives R-squared = 0.9353 (0.9671 squared, to rounding). In words, about 93.5% of the variation in volume among these trees goes along with girth under a straight-line fit (Ozer 1985 discusses how far the “share of variance” reading can be pushed). Fitting girth on volume instead gives the same 0.9353, because the relationship is symmetric in the simple case.

With more than one predictor there is no single r to square. R-squared is then the squared correlation between the observed outcomes and the model’s fitted values (for a least-squares fit with an intercept). Adding height to the model gives R-squared = 0.9480, and the squared correlation between the observed volumes and the fitted volumes matches that figure. Our linear regression reference page describes the model, and the multiple linear regression guide shows how to read the coefficients.

The engine’s Pearson method gives the r figures above. The R-squared figures in this post come from base R’s lm(), not from the engine, and the script below the article does not compute them. Every R-squared figure in this post comes from these base R lines, which run in any R session because trees and anscombe ship with R:

fit <- lm(Volume ~ Girth, data = trees)
summary(fit)$r.squared       # 0.9353
summary(fit)$adj.r.squared   # 0.9331
coef(fit)["Girth"]           # 5.07
sigma(fit)                   # 4.25
summary(lm(Girth ~ Volume, data = trees))$r.squared   # 0.9353
fit2 <- lm(Volume ~ Girth + Height, data = trees)
summary(fit2)$r.squared      # 0.9480
summary(fit2)$adj.r.squared  # 0.9442
cor(trees$Volume, fitted(fit2))^2   # 0.9480
fit3 <- lm(Volume ~ Girth + I(Girth^2), data = trees)
summary(fit3)$r.squared      # 0.9616
sigma(fit3)                  # 3.33
x <- seq(-3, 3, length.out = 61)
cor(x, x^2)                  # 0, to rounding
sapply(1:4, function(i) cor(anscombe[[paste0("x", i)]], anscombe[[paste0("y", i)]]))  # all about 0.816
sapply(1:4, function(i) cor(anscombe[[paste0("x", i)]], anscombe[[paste0("y", i)]])^2)  # all about 0.667

What each one hides

  • r hides the slope. Two datasets can share r = 0.97 with very different slopes. r says how tightly points follow a line, never how steep the line is. For the trees, the slope is 5.07 cubic feet per extra inch of diameter (the dataset’s Girth column), and that comes from the regression, not from r.
  • R-squared hides the sign. r = 0.9671 and r = -0.9671 both give R-squared = 0.9353. If direction matters, as it usually does in a results section, report r or the slope as well.
  • Neither gives the size of the error. R-squared is a proportion, so it cannot tell you how far off a prediction typically is in the outcome’s units. For these trees the residual standard error is 4.25 cubic feet. Our post on the residual standard error explains how to read it next to R-squared.
  • Both describe this sample. They were computed on the data used for the fit, so they tend to flatter the model as a statement about new data.

Why R-squared is not a measure of correctness

A high R-squared says the points sit close to the fitted shape. It does not say the shape is right. Anscombe (1973) built four small datasets with essentially the same r, about 0.816, and so the same R-squared of about 0.667: one is roughly linear, one is a clean curve, one is a straight line spoiled by an outlier, and one has all its x values equal except for a single influential point. The numbers match; the pictures do not. R ships them as the anscombe data, so you can check this yourself.

The same failure shows up with a perfect relationship. Take x from -3 to 3 and y = x squared. y is fully determined by x, yet r is zero (to rounding) and so is the R-squared of a straight-line fit, because the up-slope and the down-slope cancel. The fit’s R-squared reports how well a line works, not whether a relationship exists.

In the other direction, the trees themselves show a high value on a model that is not quite the right shape. Volume depends on a trunk’s cross-section, which grows with the square of its diameter. Adding a squared girth term raises R-squared from 0.9353 to 0.9616 and cuts the residual standard error from 4.25 to 3.33 cubic feet. A line was already a good description at 0.935, and the data still prefer a curve. The number alone could not have told you that; a plot of the residuals against the fitted values would. The post on linear regression assumptions lists the plots to look at first.

Kvalseth (1985) reviews several competing definitions of R-squared and why they can disagree, one reason to say which one you computed. In R, summary.lm changes its definition for a model fitted without an intercept: it measures the residuals against zero, not against the mean (the R documentation for summary.lm gives the formula), so the value is not comparable with R-squared from a model that has an intercept, and it is often, though not always, larger.

When adjusted R-squared is the number to report

Plain R-squared never goes down when you add a predictor, because a model with more terms can always fit the sample at least as well. A predictor with no relationship to the outcome still nudges it up by chance. Adjusted R-squared charges for each extra term. It is 1 minus (1 - R-squared) times (n - 1) divided by (n - k - 1), where n is the sample size and k the number of predictors, so it can fall when a useless predictor is added.

For the trees the difference is small, because n = 31 against one or two predictors is not stretched:

Model R-squared Adjusted R-squared
Volume on girth 0.9353 0.9331
Volume on girth and height 0.9480 0.9442

The gap becomes obvious when predictors outnumber what the sample supports. To see it, we added ten columns of pure random noise to the same 31 volumes. Nothing in those columns has any relationship with volume by construction. The seeded code is below; with the seed fixed, you get the same figures.

set.seed(20261024)
noise <- as.data.frame(matrix(rnorm(31 * 10), nrow = 31))
fit <- lm(trees$Volume ~ ., data = noise)
summary(fit)$r.squared      # 0.3716
summary(fit)$adj.r.squared  # 0.0575

Ten predictors that carry no information still produce an R-squared of 0.3716, so more than a third of the variation in volume looks accounted for. The adjusted value is 0.0575, close to zero, which is the honest reading. This is the reason to prefer the adjusted figure for a model with several predictors, and to report it when comparing models of different sizes. Even adjusted R-squared is still computed on the fitting sample, so it does not replace checking a model on new data when prediction is the goal.

Which to report, and how to word it

  • Describing a pair of measures: report r with its 95% confidence interval and n. Add a plot, since r cannot show a curve or an outlier. Direction goes with r.
  • Describing a fitted regression with one predictor: report the slope with its interval, the residual standard error, and R-squared. Say that R-squared is the square of r if a reader might ask.
  • Describing a regression with several predictors, or comparing models: report adjusted R-squared alongside R-squared, and name the number of predictors.
  • Always: say what the model is, so the figure can be placed. R-squared of 0.94 from a one-predictor line and 0.94 from a model with thirty predictors are different statements.

Example wording from the trees: “Girth and volume were strongly and positively correlated (r = 0.9671, 95% CI 0.9323 to 0.9842, n = 31). A straight-line model of volume on girth had R-squared = 0.9353 (adjusted 0.9331) and a residual standard error of 4.25 cubic feet.” That sentence gives direction, tightness, precision and error size, and does not claim that girth causes volume. This is research statistics: it describes how the two measures vary together in these 31 trees, not what would happen if one were changed.

Avoid “R-squared shows the model is correct”, “r-squared proves the effect” and “the model explains 93.5% of the cause”. R-squared is a share of variation in this sample, under this model, and nothing more.

Where Celeus fits

Celeus, the tool behind this blog, ran the correlation above. It fixes the method before it looks at the result, reports the checks it ran, and gives you a package with the R script that reproduces the figure. It does not decide whether a straight line is the right shape for your data, or whether a high R-squared is enough for your purpose: those judgements stay with you. See the validation page.

Rerunning the example

The R script attached below, exactly as the engine generated it, reads the trees data and reruns the Pearson correlation. The seed is fixed and package versions are pinned. It prints r, its interval, the test and a power sensitivity check, not the R-squared figures; the base R block above gives those, and they are deterministic because lm() involves no randomness. The reproducible statistical analysis guide explains what a reviewer needs to rerun a result like this. For a related question about causal wording, see the post on correlation vs causation.

Reproduce this

Worked example

Data: Ryan TA, Joiner BL, Ryan BF (1976). The Minitab Student Handbook. North Scituate, MA: Duxbury Press. Distributed as the trees dataset in R's datasets package. Dataset documentation (stat.ethz.ch). Seed: 20261024.

Run on Celeus engine 0.31.7, seed 20261024, public data.

Output

# Results

- Test: Pearson correlation
- n: 31
- statistic: 20.48
- p-value: < 0.001
- effect size: Pearson's r = 0.9671
- 95% CI: [0.9323, 0.9842]
- seed: 20261024

## Assumption checks
- sample size: passed=TRUE (adequate n)

## Power (sensitivity, not observed power)
- minimum detectable effect at n=31: correlation r = 0.4794
- to detect a conventional medium effect: 85 total to detect r=0.30
- alpha = 0.05, target power = 0.8
R script
# Celeus - reproduce.R  (run from this package folder; bundled data in data/)
# Test: Pearson correlation  |  engine 0.31.7  |  catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.

set.seed(20261024)
.data_path <- "data/r-vs-r-squared.csv"
if (!file.exists(.data_path)) stop("Run reproduce.R from the package folder (the bundled data is in data/).")
data <- readr::read_csv(.data_path, show_col_types = FALSE)  # needs the readr package
clean_cells <- function (df) 
{
    ctrl_collapse <- "[\r\n\t\v\f]+"
    zero_width_chars <- intToUtf8(c(8203L, 65279L), multiple = TRUE)
    uni_space_chars <- intToUtf8(c(160L, 8239L, 8199L), multiple = TRUE)
    clean_text <- function(x) {
        out <- x
        for (ch in zero_width_chars) out <- gsub(ch, "", out, 
            fixed = TRUE)
        out <- gsub(ctrl_collapse, " ", out)
        for (ch in uni_space_chars) out <- gsub(ch, " ", out, 
            fixed = TRUE)
        trimws(gsub(" +", " ", out))
    }
    changed_cols <- character(0)
    cells_cleaned <- 0L
    retyped <- character(0)
    for (j in seq_along(df)) {
        col <- df[[j]]
        if (!is.character(col)) 
            next
        cleaned <- clean_text(col)
        n_changed <- sum(!is.na(col) & !is.na(cleaned) & col != 
            cleaned, na.rm = TRUE)
        if (n_changed > 0L) {
            changed_cols <- c(changed_cols, names(df)[j])
            cells_cleaned <- cells_cleaned + n_changed
        }
        df[[j]] <- cleaned
        non_blank <- cleaned[!is.na(cleaned) & nzchar(cleaned)]
        if (length(non_blank) && all(grepl("^[+-]?(0|[1-9][0-9]*)([.][0-9]+)?$", 
            non_blank))) {
            parsed <- suppressWarnings(as.numeric(cleaned))
            was_blank <- is.na(cleaned) | !nzchar(cleaned)
            lost <- (is.na(parsed) | is.infinite(parsed)) & !was_blank
            if (!any(lost)) {
                finite <- parsed[!is.na(parsed)]
                all_whole <- all(finite == floor(finite))
                in_range <- all(abs(finite) <= .Machine$integer.max)
                df[[j]] <- if (all_whole && in_range) 
                  as.integer(parsed)
                else parsed
                retyped <- c(retyped, names(df)[j])
            }
        }
    }
    names(df) <- clean_text(names(df))
    if (length(changed_cols)) {
        attr(df, "ingest_clean_columns") <- changed_cols
        attr(df, "ingest_clean_cells_cleaned") <- cells_cleaned
    }
    if (length(retyped)) 
        attr(df, "ingest_clean_retyped") <- retyped
    df
}
data <- clean_cells(data)
# Complete-case analysis: drop rows missing any analysis value.
.acols <- c("Girth", "Volume")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- cor.test(data[["Girth"]], data[["Volume"]], method = "pearson")
print(result)

# Sensitivity: minimum detectable effect at n=31 (power=0.8, alpha=0.05). NOT observed power.
power <- pwr::pwr.r.test(n = 31, sig.level = 0.05, power = 0.8)
print(power)

Common questions

Is R-squared just r squared?

In a straight-line regression with one predictor and an intercept, yes: R-squared equals the square of Pearson's r. With several predictors there is no single r, and R-squared is the squared correlation between the observed outcomes and the model's fitted values (for a least-squares fit with an intercept).

Can R-squared be high when the model is wrong?

Yes. Four datasets with very different shapes, including a curve and a single influential point, give nearly the same r and the same R-squared to two decimals. The value describes how tightly points sit around the fit, not whether the fitted form suits the data.

When should I report adjusted R-squared instead of R-squared?

Report adjusted R-squared when the model has more than one predictor or when you compare models with different numbers of predictors. Plain R-squared never falls when a predictor is added, even a useless one; the adjusted version penalises each extra term.

Does a negative r mean R-squared is negative?

No. Squaring removes the sign, so r = -0.97 and r = 0.97 both give an R-squared near 0.94. The sign is information only r carries: it says whether the outcome rises or falls as the predictor rises.

What is a good R-squared?

There is no universal cutoff. Values depend on the field, the outcome and how noisy it is, so compare with comparable studies and with a simpler model. A low value can still be useful, and a high one can come from the wrong model.

Sources

  1. Anscombe FJ (1973). Graphs in statistical analysis. The American Statistician 27(1):17-21. doi:10.1080/00031305.1973.10478966
  2. Kvalseth TO (1985). Cautionary note about R squared. The American Statistician 39(4):279-285. doi:10.1080/00031305.1985.10479448
  3. Ozer DJ (1985). Correlation and the coefficient of determination. Psychological Bulletin 97(2):307-315. doi:10.1037/0033-2909.97.2.307
  4. Rodgers JL, Nicewander WA (1988). Thirteen ways to look at the correlation coefficient. The American Statistician 42(1):59-66. doi:10.1080/00031305.1988.10475524
  5. R Core Team. summary.lm: Summarizing Linear Model Fits. R documentation, stats package. stat.ethz.ch

Methods discussed

Try it on your own data

Upload a dataset and Celeus suggests a method, checks its assumptions, runs it in R, and gives you the script to rerun it.

Try it in Celeus