How to Use the 'scale' Function in R

R Published Updated 3 mins read By Leon Wei
How to Use the 'scale' Function in R cover image

Quick summary

Summarize this blog with AI

Standardize numeric columns with scale

With its defaults, scale() subtracts each column mean and divides by its sample standard deviation. It returns a matrix, even for a numeric data frame. This tutorial shows the transformation, missing-value behavior and how to reuse training statistics on new observations. All blocks run in order with base R 4.5.1.

Inspect the center and scale

train <- data.frame(height = c(160, 170, 180), weight = c(50, 60, 70))
z_train <- scale(train)
print(z_train)
# Each column: -1, 0, 1
centers <- attr(z_train, "scaled:center")
spreads <- attr(z_train, "scaled:scale")
print(centers)  # height 170; weight 60
print(spreads)  # height 10; weight 10
stopifnot(all(z_train[, 1] == c(-1, 0, 1)))
stopifnot(all(z_train[, 2] == c(-1, 0, 1)))

Use scale(train, scale = FALSE) to center only. With center = FALSE, scale = TRUE, the divisor is a root-mean-square quantity with denominator n - 1, not generally the sample standard deviation. If a method requires a different transformation, supply the explicit numeric divisors.

Preserve missing values

incomplete <- data.frame(a = c(1, NA, 3), b = c(10, 20, 30))
z_incomplete <- scale(incomplete)
print(round(z_incomplete, 6))
# a: -0.707107, NA, 0.707107; b: -1, 0, 1
stopifnot(is.na(z_incomplete[2, "a"]))
stopifnot(isTRUE(all.equal(attr(z_incomplete, "scaled:center"),
                         c(a = 2, b = 20))))

The default method omits missing observations when estimating each column center and divisor, while leaving their transformed positions missing. It does not accept a na.rm argument and it does not impute missing data. A downstream model may still reject the missing values. Decide the missing-data policy separately and fit any imputation using training data only.

Apply the training transformation to new rows

new_rows <- data.frame(height = c(175, 190), weight = c(65, 80))
stopifnot(identical(names(new_rows), names(train)))
z_new <- scale(new_rows, center = centers, scale = spreads)
print(z_new)
# Each column: 0.5, 2.0
stopifnot(all(z_new[, 1] == c(0.5, 2)))

# The same statistics work for separate batches.
batches <- lapply(seq_len(nrow(new_rows)), function(i) {
  scale(new_rows[i, , drop = FALSE], center = centers, scale = spreads)
})
stopifnot(isTRUE(all.equal(unname(do.call(rbind, batches)),
                         unname(z_new), check.attributes = FALSE)))

Independently calling scale() on each batch estimates a different transformation for every batch. The stored centers and spreads keep batches comparable. A training/test split has the same requirement: do not let test observations determine preprocessing parameters. Keep the same predictor names and order; a numeric vector of centers is applied positionally.

Reject unusable columns explicitly

candidate <- data.frame(varying = c(1, 2, 3),
                        constant = c(7, 7, 7),
                        absent = c(NA_real_, NA_real_, NA_real_))
candidate_sd <- vapply(candidate, sd, numeric(1), na.rm = TRUE)
usable <- is.finite(candidate_sd) & candidate_sd > 0
print(usable)  # TRUE FALSE FALSE
stopifnot(identical(unname(usable), c(TRUE, FALSE, FALSE)))
stopifnot(all(is.nan(scale(candidate["constant"]))))

A constant column cannot be standardized by its zero standard deviation. An all-missing column, or one with only one observed value, also lacks a usable sample standard deviation. Investigate these columns and explicitly choose removal or another model-specific treatment. Do not replace their transformed values with zero merely to suppress an error.

Reverse the transformation when needed

recovered <- sweep(sweep(z_train, 2, spreads, "*"), 2, centers, "+")
stopifnot(isTRUE(all.equal(unname(recovered), unname(as.matrix(train)),
                         check.attributes = FALSE)))

Scaling does not remove outliers or make a distribution normal. It changes the units. Select only intended numeric predictors—never a numeric identifier or a target column just because it has numeric storage. For an example that uses this preprocessing in a classifier, see nearest-neighbor classification in R.

References

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles
How to Use 'countif' in R cover image
r Oct 17, 2026

How to Use 'countif' in R

Unlock the power of 'countif' in R with our comprehensive guide. Perfect for beginners looking to enhance their R programming skills.

How to Use 'abline' in R cover image
r Apr 30, 2024

How to Use 'abline' in R

Unlock the power of 'abline' function in R for data visualization; this guide covers everything from basics to advanced applications with exampl…