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)))
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.