How to Perform Shapiro-Wilk Test in R

R Published Updated 3 mins read By Leon Wei
How to Perform Shapiro-Wilk Test in R cover image

Quick summary

Summarize this blog with AI

Introduction

stats::shapiro.test() tests a normal-distribution null hypothesis. A small p-value indicates incompatibility with that null under the test’s assumptions; a large p-value does not prove normality. R accepts 3–5000 nonmissing observations and uses approximate p-values except at n=3. The standard test assumes an independent sample. Preserve legitimate repeated measurements and consider the analysis design before choosing what variable to test.

Tested with R 4.5.1. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.

Preserve repeated observations and count missing values

Two different records can legitimately have the same measurement. Deduplicating the numeric values would change the sample and its distribution.

records <- data.frame(id = 1:11, value = c(4.2, 5.1, 5.1, 6.3, 4.8, 5.5, 6.0, 4.5, 5.8, 5.0, NA))
stopifnot(!anyDuplicated(records$id))
x <- records$value[!is.na(records$value)]
stopifnot(length(x) == 10L, sum(x == 5.1) == 2L)
print(c(observed = length(x), missing = sum(is.na(records$value))))
# observed 10; missing 1

These are synthetic measurements. Excluding missing values defines the analyzed subset; it does not establish that missingness is harmless. Do not impute or transform values solely to obtain a desired p-value.

Run the test and report its actual output

Choose a significance level before looking at results. The statistic and p-value below come from the stated observations, not an imagined result.

result <- shapiro.test(x)
print(round(c(W = unname(result$statistic), p_value = result$p.value), 5))
# W 0.97393; p_value 0.92467 (rounded)
stopifnot(abs(unname(result$statistic) - 0.97393) < 1e-5, result$p.value > 0.05)
non_normal_example <- c(rep(1, 8), 2, 8)
comparison <- shapiro.test(non_normal_example)
stopifnot(comparison$p.value < 0.001)

At alpha 0.05 we do not reject for x. This is not a 92% probability that x is normal, and it does not verify every assumption of a later test. The second fixture is visibly concentrated at 1 with a long upper tail; its small p-value does not identify the cause of the pattern.

Inspect a normal Q-Q plot as well

The Q-Q plot compares ordered observations with theoretical normal quantiles. The reference line uses the first and third quartiles; it is not a confidence band.

draw_qq <- function() {
  qqnorm(x, pch = 19, col = "#1b6a83", main = "Normal Q-Q plot: ten retained measurements")
  qqline(x, col = "#b15435", lwd = 2)
}
draw_qq()
qq <- qqnorm(x, plot.it = FALSE)
stopifnot(length(qq$x) == length(x), identical(sort(qq$y), sort(x)))
Normal Q-Q plot of the ten retained measurementsTen observations, including both measurements equal to 5.1, are compared with normal quantiles. An orange quartile reference line shows the overall alignment. -1.5 -1.0 -0.5 0.0 0.5 1.0 1.5 4.5 5.0 5.5 6.0 Normal Q-Q plot: ten retained measurements Theoretical Quantiles Sample Quantiles
The Q-Q plot retains both equal measurements and complements the test without proving normality.

Small samples can miss meaningful departures; large samples can detect departures that have little practical effect. For regression, normality assumptions typically concern errors conditional on predictors, not the marginal distribution of every input. Estimated residuals are not independent raw observations, so do not interpret a naive residual test as a complete model diagnostic.

Reject unsupported shapes and sample sizes clearly

This wrapper accepts a real numeric vector, rejects infinities, then makes the observed-sample policy explicit.

check_normality <- function(values) {
  if (!is.numeric(values) || is.complex(values) || !is.null(dim(values)))
    stop("Supply a real numeric vector")
  if (any(is.infinite(values))) stop("Infinite values need review")
  observed <- values[!is.na(values)]
  if (length(observed) < 3L || length(observed) > 5000L)
    stop("Need 3 to 5000 observed values")
  if (length(unique(observed)) < 2L) stop("All observed values are identical")
  shapiro.test(observed)
}
stopifnot(isTRUE(all.equal(check_normality(records$value)$p.value, result$p.value)))
for (bad in list(numeric(0), c(1, 2), rep(1, 3), rep(NA_real_, 4), 1:5001,
                 c(1, 2, Inf), c("1", "2", "3"), matrix(1:4, 2)))
  stopifnot(inherits(tryCatch(check_normality(bad), error = identity), "error"))
stopifnot(is.finite(check_normality(c(1, 2, 4))$p.value))

The unique call here only checks whether variation exists; it does not alter the sample passed to the test. There is no automatic under-20 workaround or requirement to switch to another test. Choose an analysis justified by the scientific question, sampling design and robustness requirements rather than a pass/fail normality gate.

Primary 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 Remove Outliers in R cover image
r Oct 17, 2026

How to Remove Outliers in R

Learn how to identify and remove outliers in R with this step-by-step guide, featuring detailed code samples for beginners.