How to Perform a Normality Test in R

R Published Updated 4 mins read By Leon Wei
How to Perform a Normality Test in R cover image

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()
Normal and right-skewed sample Q-Q plotsNormal-simulation points follow the reference line more closely; right-skewed values curve strongly upward in the upper tail. -2 -1 0 1 2 -2 -1 0 1 2 Normal simulation Theoretical Quantiles Sample Quantiles -2 -1 0 1 2 0 5 10 15 20 Right-skewed simulation Theoretical Quantiles Sample Quantiles
The exact fixed-seed samples used in the tests.

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.

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