Skip to main content
9 of 13
reference added
Tim
  • 141.2k
  • 26
  • 270
  • 512

Note that the Shapiro-Wilk is a powerful test of normality.

The best approach is really to have a good idea of how sensitive any procedure you want to use is to various kinds of non-normality (how badly non-normal does it have to be in that way for it to affect your inference more than you can accept).

An informal approach for looking at the plots would be to generate a number of data sets that are actually normal of the same sample size as the one you have - (for example, say 24 of them). Plot your real data among a grid of such plots (5x5 in the case of 24 random sets). If it's not especially unusual looking (the worst looking one, say), it's reasonably consistent with normality.

enter image description here

To my eye, data set "Z" in the center looks roughly on a par with "o" and "v" and maybe even "h", while "d" and "f" look slightly worse. "Z" is the real data. While I don't believe for a moment that it's actually normal, it's not particularly unusual-looking when you compare it with normal data.

[Edit: I just conducted a random poll -- well, I asked my daughter, but at a fairly random time -- and her choice for the least like a straight line was "d". So 100% of those surveyed thought "d" was the most-odd one.]

More formal approach would be to do a Shapiro-Francia test (which is effectively based on the correlation in the QQ-plot), but (a) it's not even as powerful as the Shapiro Wilk test, and (b) formal testing answers a question (sometimes) that you should already know the answer to anyway (your data isn't exactly normal), instead of the question you need answered (how badly does that matter?).


As requested, code for the above display. Nothing fancy involved:

z = lm(dist~speed,cars)$residual
n = length(z)
xz = cbind(matrix(rnorm(12*n),nr=n),z,matrix(rnorm(12*n),nr=n))
colnames(xz) = c(letters[1:12],"Z",letters[13:24])

opar = par()
par(mfrow=c(5,5));
par(mar=c(0.5,0.5,0.5,0.5))
par(oma=c(1,1,1,1));

ytpos = (apply(xz,2,min)+3*apply(xz,2,max))/4
cn = colnames(xz)

for(i in 1:25) {
  qqnorm(xz[,i],axes=FALSE,ylab= colnames(xz)[i],xlab="",main="")
  qqline(xz[,i],col=2,lty=2)
  box("figure", col="darkgreen")
  text(-1.5,ytpos[i],cn[i])
}

par(opar)

Note that this was just for the purposes of illustration; I wanted a small data set that looked mildly non-normal which is why I used the residuals from a linear regression on the cars data (the model isn't quite appropriate). However, if I was actually generating such a display for a set of residuals for a regression, I'd regress all 25 data sets on the same $x$'s as in the model, and display QQ plots of their residuals, since residuals have some structure not present in normal random numbers.

See more:

Buja, A., Cook, D. Hofmann, H., Lawrence, M. Lee, E.-K., Swayne, D.F and Wickham, H. (2009) Statistical Inference for exploratory data analysis and model diagnostics Phil. Trans. R. Soc. A 2009 367, 4361-4383 doi: 10.1098/rsta.2009.0120

Glen_b
  • 290.5k
  • 37
  • 652
  • 1.1k