Residual standard error: how to read it in R
Where the residual standard error sits in R's lm() output, what it means in your outcome's units, and how to read it alongside the residual plot.
Updated . Written by Matt Gencer.
On this page
The residual standard error is the typical distance between the observed outcomes and the values your regression line predicts, expressed in the outcome’s own units. In R it is the line that reads “Residual standard error: 4.252 on 29 degrees of freedom” near the bottom of summary(lm(...)), and in the example below it means a typical tree’s volume sits about 4.25 cubic feet away from the fitted line.
This post covers where that number comes from, how to read it next to the spread of the outcome and the residual plot, how it relates to R-squared, RMSE and prediction intervals, and what it cannot tell you. The worked example is a simple linear regression on R’s built-in trees data, run through a deterministic engine. Every number quoted from the engine output comes from that run; the few numbers that do not are labelled as coming from a base R lm() on the same data. For the wider picture of choosing and checking a regression, start with the regression analysis guide.
What the residual standard error is
A regression line gives every observation a fitted value. The residual is the observed value minus the fitted value, and the residual standard error summarises the size of those residuals in one number. Square each residual, add them up (the residual sum of squares), divide by the residual degrees of freedom, and take the square root.
The degrees of freedom are the number of observations minus the number of estimated coefficients. R’s documentation for sigma() gives the same recipe: the square root of the deviance divided by the number of observations minus the number of non-missing coefficients (R documentation: sigma). A simple regression estimates two coefficients, an intercept and a slope, so with 31 trees the divisor is 29.
Dividing by the degrees of freedom rather than by n matters. The fitted line is chosen to make the residuals as small as possible in this very sample, so their raw spread understates the true error spread; the smaller divisor corrects for that.
Under the usual linear regression model the errors have one common standard deviation, and the residual standard error is the estimate of it. That is why the same quantity is called the standard error of the regression, the standard error of the estimate, the residual standard deviation or sigma. The R documentation itself calls the printed label “Residual standard error” a misnomer that has been part of too many R and S outputs to change (R documentation: sigma). It is a standard deviation of residuals, not the standard error of a coefficient.
Where to find it in R
fit <- lm(Volume ~ Girth, data = trees)
summary(fit) # "Residual standard error: ... on ... degrees of freedom"
sigma(fit) # the number on its own
df.residual(fit) # the degrees of freedom it was divided by
The worked example
R ships a dataset called trees with measurements on 31 felled black cherry trees. For each tree it records the trunk diameter in inches (labelled Girth), the height in feet and the timber volume in cubic feet. Each row is a different tree, the outcome is a continuous measurement, and nothing is censored, so the rows are independent units and a linear regression of volume on girth is a reasonable first model.
Seeded and run in R; the method, linear regression with Volume as the outcome and Girth as the only predictor, was chosen before the run. The engine reports the slope as 5.066 cubic feet per extra inch of girth, with a 95% confidence interval from 4.56 to 5.572, and an R-squared of 0.9353 on n = 31. Its Shapiro-Wilk check on the residuals found no evidence against normality, and it flagged one tree whose Cook’s distance was above the usual 4/n screening threshold, a note to inspect that row, not a verdict.
The engine’s summary does not print the residual standard error. In a base R lm() on the same 31 trees (the same data the reproduction script bundles), summary(fit) reports 4.252 on 29 degrees of freedom. This figure and the others below that are not attributed to the engine come from base R on the same data. Read that in three steps.
In the outcome’s units. The residual standard error is 4.252 cubic feet. Volumes in these data average about 30 cubic feet, so a typical miss is roughly 14% of the average tree. The unit is the point: unlike a p-value or R-squared, you can say it in plain words, “the line is typically off by about four cubic feet”.
Against the outcome’s own spread. The standard deviation of Volume itself, ignoring girth, is about 16.4 cubic feet. The residual standard error of 4.25 is about a quarter of that, which is what an R-squared near 0.94 means in practice: knowing a tree’s girth removes most of the uncertainty about its volume. The two numbers are linked: R-squared is one minus the residual sum of squares over the total sum of squares, so the residual standard error is roughly the outcome’s standard deviation times the square root of one minus R-squared (16.4 × √0.065 ≈ 4.2).
As a range. If the errors are roughly normal, about two thirds of observations should fall within one residual standard error of the line and about 95% within two. In these data 21 of the 31 trees (68%) lie within 4.25 cubic feet of the fitted line, and 30 of 31 (97%) lie within twice that. This rule of thumb is a quick sanity check on your own model: a large mismatch suggests skewed or heavy-tailed errors.
Read it with the residual plot, not instead of it
One number cannot describe a pattern. The residual standard error assumes a single error spread everywhere, and the way to see whether that is true is to plot the residuals against the fitted values.
plot(fit, which = 1) # residuals vs fitted
In the trees example the single number hides some structure. Split the trees into thirds by girth and the residual standard deviation (the standard deviation of the residuals within each third) is about 2.4 cubic feet in the thinnest third, 4.2 in the middle third and 5.0 in the thickest (base R, same data). The average miss is smaller for slim trees and larger for stout ones, a mild case of the funnel shape described in the post on heteroscedasticity. The headline 4.25 is therefore an overstatement for the thin trees and an understatement for the thick ones.
Note also the pattern of the mean residual: slightly positive for the thinnest trees, negative in the middle and positive again for the thickest. That U-shaped pattern says the straight line is missing some curvature, and the squared-girth fit in the next section agrees. A formal check is in the post on linear regression assumptions.
What changes it, and what a “good” value is
The residual standard error falls when the model captures more of the outcome’s systematic variation. On the same data, base R gives these values: 4.252 for volume on girth alone, 3.882 once height is added as a second predictor (the model in the regression analysis guide), and 3.335 with a squared girth term added to the straight line. Each fit is compared on the same outcome in the same units, which is the only fair comparison.
Two cautions apply to such comparisons:
- Adding a predictor does not automatically lower it. The residual sum of squares can only fall when a term is added, but each extra coefficient also costs a residual degree of freedom, so a predictor that explains almost nothing can leave the residual standard error unchanged or even raise it, unlike plain R-squared, which never decreases.
- Different outcome scales are not comparable. Taking the logarithm of the outcome changes the units, so a residual standard error from a model of log volume on log girth (0.115 here, on the natural-log scale) cannot be set beside 4.252 cubic feet. Convert predictions back, or compare on the original scale.
There is no universal threshold for “good”. Because it is measured in the units of the outcome, whether 4.25 cubic feet is acceptable depends on what a four-cubic-foot miss costs in the question you are asking. This is why textbooks pair it with R-squared, which does not depend on the scale (James et al. 2021, An Introduction to Statistical Learning). Use both: the residual standard error for the size of the error, R-squared for the share of variation explained.
Residual standard error, RMSE and standard errors of coefficients
Three neighbouring quantities are easy to confuse.
RMSE. The root mean squared error, as it is usually computed in prediction work, divides the residual sum of squares by n, not by the degrees of freedom (James et al. 2021 define the mean squared error this way). For these 31 trees that gives about 4.11, a little under 4.252. The gap shrinks as n grows relative to the number of coefficients. In a report, say which one you computed, because the two names are often used loosely.
The standard error of a coefficient. The slope’s standard error in the engine output is 0.2474 cubic feet per inch. It is built from the residual standard error: the larger the typical residual, the more uncertain the slope, and the more spread out the predictor values are, the less. The residual standard error is therefore upstream of every standard error, p-value and confidence interval in the table, which is why a model with large residuals produces wide intervals.
Prediction intervals. The residual standard error is the main ingredient of an interval for a new observation. A 95% confidence interval for the mean volume of trees with 14 inches of girth runs from about 32.4 to 35.6 cubic feet, but a 95% prediction interval for a single new tree runs from about 25.1 to 42.8 (base R, predict(fit, newdata, interval = "prediction")). The R documentation explains the difference: confidence intervals describe uncertainty about the mean, while prediction intervals refer to future single observations and so also carry the error variance (R documentation: predict.lm). The prediction interval’s half-width of roughly 8.8 is about twice the residual standard error, as the two-standard-error rule suggests.
What it does not tell you
- It does not test the model. There is no p-value attached to it. It describes size, not statistical significance.
- It does not prove the model is right. Curvature, unequal spread, dependence between rows and influential points can all coexist with a small value. Check the plots described in the post on assumptions.
- It describes this sample. The value was computed on the data used to fit the model, so it tends to be optimistic as a statement about new data. To judge predictive accuracy, evaluate on observations the model did not see.
- It carries no causal claim. This is research statistics: the line summarises how volume varies with girth in these 31 trees, not what any intervention would do.
How to report it
State the estimate, its degrees of freedom and its unit, next to the fit measures and the coefficient table. For the example: “The model had a residual standard error of 4.25 cubic feet on 29 degrees of freedom (R-squared = 0.935, n = 31).” Add one sentence on the residual plot, for instance that the spread of the residuals grows with girth, so the typical error is not constant across tree sizes.
If you want the number as part of a reproducible record rather than a copy from a console, the example folder below the article holds the exact R script the engine generated (running it prints the full summary, residual standard error included), the engine’s output and the seed, and the guide to reproducible statistical analysis explains why that matters. The method behind the example has its own page in the reference docs: linear regression. If you are unsure whether a linear model is the right family at all, the guide to choosing a statistical test starts from the type of outcome.
Worked example
Data: Meyer HA (1953). Forest Mensuration. Penns Valley Publishers. Ryan TA, Joiner BL, Ryan BF (1976). The Minitab Student Handbook. Duxbury Press. Distributed as the trees dataset in R's datasets package. https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/trees.html. Seed: 20261022.
Run on Celeus engine 0.31.1, seed 20261022, public data.
Output
# Results - Test: Linear regression - n: 31 - statistic: 20.48 - p-value: < 0.001 - effect size: R-squared = 0.9353 - 95% CI: [4.56, 5.572] - seed: 20261022 ## Assumption checks - normality (Shapiro-Wilk): passed=TRUE (no evidence against normality) ## Power (sensitivity, not observed power) - minimum detectable effect at n=31: Cohen's f2 = 0.2711 - to detect a conventional medium effect: 55 total to detect f2=0.15 - alpha = 0.05, target power = 0.8
R script
# Celeus - reproduce.R (run from this package folder; bundled data in data/)
# Test: Linear regression | engine 0.31.1 | catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.
set.seed(20261022)
.data_path <- "data/residual-standard-error.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("Volume", "Girth")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- summary(lm(Volume ~ Girth, data = data))
print(result)
# Sensitivity: minimum detectable effect at n=31 (power=0.8, alpha=0.05). NOT observed power.
power <- pwr::pwr.f2.test(u = 1, v = 29, sig.level = 0.05, power = 0.8)
print(power)Common questions
What is a good residual standard error?
There is no universal cutoff, because it is measured in the units of the outcome. Compare it with the outcome's own spread, with what counts as a meaningful error in your field, and with the value for competing models fitted to the same data.
What is the difference between residual standard error and RMSE?
Both summarise the size of the residuals. The residual standard error divides the sum of squared residuals by the residual degrees of freedom, n minus the number of coefficients, while RMSE as usually computed divides by n, so RMSE is slightly smaller in the same sample.
Is the residual standard error the same as the standard error of the estimate?
Yes in a regression context: the standard error of the estimate, the standard error of the regression, the residual standard deviation and sigma all name the same quantity. R's own documentation calls the printed label a misnomer.
Does a small residual standard error mean the model is correct?
No. It says the points sit close to the fitted line on average, not that a straight line is the right shape or that the errors are independent and evenly spread. Check the residual plot before trusting any single summary number.
How do I get the residual standard error out of an lm object in R?
Call sigma(fit) on the fitted model, or take the sigma element of summary(fit). Both return the number printed after Residual standard error in the summary, and the residual degrees of freedom are in df.residual(fit).
Sources
- R Core Team. sigma: extract the residual standard deviation. R documentation, stats package. stat.ethz.ch
- R Core Team. predict.lm: predict method for linear model fits. R documentation, stats package. stat.ethz.ch
- James G, Witten D, Hastie T, Tibshirani R (2021). An Introduction to Statistical Learning with Applications in R, 2nd ed. Springer. doi:10.1007/978-1-0716-1418-1
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