Quick summary
Summarize this blog with AI
Introduction
Normality diagnostics ask whether a normal-distribution model is a reasonable description of the quantity relevant to an analysis. They are not a universal permission check for all statistical methods. In regression the relevant errors are conditional on predictors; in a paired t analysis the relevant observations are within-pair differences, not the two raw samples separately.
Tested with R 4.5.1 and nortest 1.0-4. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.
Run Shapiro–Wilk on supplied data
The R implementation accepts 3 to 5,000 nonmissing values. The null is normality with unspecified mean and variance. Here fixed-seed simulated observations provide reproducible examples; drawing from a normal generator does not force every test p-value to exceed 0.05.
set.seed(757)
normal_sample <- rnorm(80)
skewed_sample <- exp(rnorm(80, sd = 1.2))
sw <- shapiro.test(normal_sample)
skewed_sw <- shapiro.test(skewed_sample)
print(rbind(normal = c(W = unname(sw$statistic), p = sw$p.value),
skewed = c(W = unname(skewed_sw$statistic), p = skewed_sw$p.value)))
stopifnot(skewed_sw$p.value < 0.05,
abs(shapiro.test(10 + 2 * normal_sample)$p.value - sw$p.value) < 1e-10)
The Shapiro results are W = 0.9708, p = 0.06355 for the normal simulation and W = 0.5247, p ≈ 1.12 × 10⁻¹⁴ for the skewed simulation. A small p-value is evidence against the null under the test assumptions. A large p-value is failure to reject, not proof of normality. Small samples may miss substantial departures; large samples may detect departures of little practical consequence.
Inspect a Q-Q plot as well as the p-value
A normal Q-Q plot compares ordered observations with normal reference quantiles. Curvature, tail departures and individual extreme points help explain the test result. Investigate extreme observations; deleting them solely to obtain a larger p-value changes the question and biases the diagnostic.
draw_normality <- function() {
old <- par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))
on.exit(par(old))
qqnorm(normal_sample, main = "Normal simulation", pch = 19, cex = 0.65)
qqline(normal_sample, col = "#a63d28", lwd = 2)
qqnorm(skewed_sample, main = "Right-skewed simulation", pch = 19, cex = 0.65)
qqline(skewed_sample, col = "#a63d28", lwd = 2)
}
draw_normality()
The normal simulation lies closer to a line; the right-skewed sample shows marked upper-tail curvature. Neither plot assesses independence. Time-series autocorrelation and repeated measurements require separate design and dependence checks.
Use ordinary K-S only with a prespecified reference
The one-sample K-S p-value assumes a fully specified continuous distribution. This example uses mean 0 and SD 1 fixed before simulation. Estimating mean(x) and sd(x) from the tested observations changes the null distribution of the statistic; the ordinary p-value is then not valid for that fitted-normal test. Rounded/tied observations also need care.
ks <- ks.test(normal_sample, "pnorm", mean = 0, sd = 1)
cat("K-S statistic:", round(unname(ks$statistic), 4), "p:", signif(ks$p.value, 4), "
")
stopifnot(ks$p.value >= 0, ks$p.value <= 1)
The K-S result is D = 0.1616 and p = 0.02719 for the prespecified standard normal distribution. It tests a different null from Shapiro–Wilk, which fits location and scale.
Use tests calibrated for fitted mean and variance
The nortest package provides Lilliefors and Anderson–Darling normality tests for this composite null. Its lillie.test() needs more than 4 observations; ad.test() needs more than 7. Anderson–Darling weights tail differences more strongly than a K-S maximum-distance statistic. Choose diagnostics based on the planned analysis, not whichever gives the preferred p-value.
lillie <- nortest::lillie.test(normal_sample)
ad <- nortest::ad.test(normal_sample)
print(c(Lilliefors_p = lillie$p.value, Anderson_Darling_p = ad$p.value))
stopifnot(all(c(lillie$p.value, ad$p.value) >= 0),
all(c(lillie$p.value, ad$p.value) <= 1))
Here Lilliefors p ≈ 0.001427 and Anderson–Darling p ≈ 0.03336. Different tests can disagree on one sample, including one simulated from a normal generator. Inspect the data and use a planned diagnostic approach rather than choosing the preferred answer.
Enforce the input contract and report exclusions
This wrapper rejects missing, nonfinite, constant and incorrectly shaped inputs rather than silently choosing analysis rows. If missingness is handled separately, report the retained count and why the omissions are defensible. Discrete categories, heavily rounded measurements and bounded scores may not be well modeled by a continuous normal distribution.
check_shapiro <- function(x) {
if (!is.numeric(x) || !is.null(dim(x)) || length(x) < 3L || length(x) > 5000L ||
any(!is.finite(x)) || length(unique(x)) < 2L)
stop("Need3 to5000 finite nonconstant numeric observations")
shapiro.test(x)
}
fails <- function(expr) inherits(tryCatch(expr, error = identity), "error")
stopifnot(isTRUE(all.equal(check_shapiro(normal_sample)$p.value, sw$p.value)),
fails(check_shapiro(numeric())), fails(check_shapiro(rep(2, 8))),
fails(check_shapiro(c(1, 2, NA))), fails(check_shapiro(c(1, 2, Inf))),
fails(check_shapiro(rep(1:2, 2501))), fails(check_shapiro(matrix(1:9, 3))))
What these tests do not establish
Resampling the observed data and plotting Shapiro p-values is not a calibrated normality test. A custom simulation test must generate under a clearly specified null and account for any fitted parameters. Separate univariate checks do not establish joint multivariate normality. If a model is unsuitable, consider a scientifically justified transformation, a different likelihood, robust inference or design-aware resampling; none is an automatic cure. Standardizing values to mean 0 and SD 1 leaves their distributional shape unchanged.