25  Normality tests

Setup
library(dplyr) 
library(ggplot2)
library(knitr)

Tests of normality are statistical inference procedures designed to test that the underlying distribution of a random variable is normally distributed. There is a long history of these tests, and there are a plethora of them available for use, i.e. Jarque and Bera (), D’Agostino and Pearson (), Ralph B. D’agostino and Jr. (). Such kind of tests are based on the comparison of the sample skewness and kurtosis with the skewness and kurtosis of a normal distribution, hence their estimation is crucial.

Skewness function
skewness <- function(x, na.rm = FALSE){
  e_x <- mean(x, na.rm = na.rm)
  sd_x <- sd(x, na.rm = na.rm)
  mean((x - e_x)^3, na.rm = na.rm)/(sd_x^3)
}
Kurtosis function
kurtosis <- function(x, na.rm = FALSE, excess = TRUE){
  e_x <- mean(x, na.rm = na.rm)
  sd_x <- sd(x, na.rm = na.rm)
  mean((x - e_x)^4, na.rm = na.rm)/(sd_x^4) - ifelse(excess, 3, 0)
}

25.1 Jarque-Brera test

If X is an independent and identically distributed process, the asymptotic distribution of the skewness and kurtosis in holds for XN. Hence, we construct an omnibus test for normality. Standardizing the skewness and kurtosis, under H0:β1(Xn)=0,β2(Xn)=3, we have
Z1(Xn)=nb1(Xn)6dnN(0,1),Z2(Xn)=nb2(Xn)324dnN(0,1), where b1(Xn) and b2(Xn) are defined respectively in and in . It is possible to prove that Z1(Xn) and Z2(Xn) are independent. Since the sum of two independent standard normal squared random variables follows a χv2 distribution with v=2 degrees of freedom, let’s rewrite the Jarque-Brera statistic as: JB(Xn)=Z1(Xn)2+Z2(Xn)2dnχ22. However, if n is small the JB(Xn) over-reject the null hypothesis H0, i.e. type I error.

Jarque-Brera test
# =============== Setups =============== 
n <- 50 # number of simulations 
set.seed(1) # random seed 
ci <- 0.05 # confidence level 
# ======================================
# Simulated variable 
x <- rnorm(n, 0, 1)
# Sample skewness
b1 <- skewness(x)
# Sample kurtosis 
b2 <- kurtosis(x, excess = F)
# JB-statistic test
JB <- n*(b1^2/6 + (b2 - 3)^2/24)
# JB-test p-value 
pvalue <-  1 - pchisq(JB, df = 2)
# JB-test critic value at 95%
z_lim <- qchisq(1-ci, 2)
# =============== Plot =============== 
# Grid of points 
grid <- seq(0, 10, 0.01)
# Rejection area 
grid_rej <- grid[grid > z_lim]
# Non-rejection area 
grid_norej <- grid[grid < z_lim]
ggplot()+
  geom_line(aes(grid, dchisq(grid, 2)))+
  geom_segment(aes(z_lim, xend = z_lim, y = 0, yend = dchisq(z_lim, 2)))+
  geom_point(aes(JB, 0))+
  geom_ribbon(aes(x = grid_rej, ymin = 0, ymax = dchisq(grid_rej, 2), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_norej, ymin = 0, ymax = dchisq(grid_norej, 2), fill = "norej"), alpha = 0.3)+
  geom_segment(aes(JB, xend = JB, y = 0, yend = dchisq(JB, 2)))+
  scale_x_continuous(breaks = c(0, JB, z_lim), 
                     labels = c("0", format(c(JB, z_lim), digits = 4)))+
  labs(x = "JB statistic", y = "", fill = NULL)+
  scale_fill_manual(values = c(rej = "red", norej = "green"), 
                    labels = c(rej = "Rejection Area", norej = "Non Rejection Area")) + 
  theme_bw()+
  theme(
    legend.position = "top",
    panel.grid = element_blank()
  )
Figure 25.1: Jarque-Brera test for normality with a simulated Normal sample of 50 observations with α=0.05.

25.2 Urzua-Jarque-Brera test

Let’s substitute the asymptotic moments with the exact sample moments of skewness and kurtosis. Following Urzúa (), let’s write the new omnibus test statistic as: Z~1(Xn)=b1(Xn)V{b1(Xn)}dnN(0,1),Z~2(Xn)=b2(Xn)E{b2(Xn)}V{b2(Xn)}dnN(0,1), where the exact moments V{b1(Xn)}, E{b2(Xn)} and V{b2(Xn)} are defined respectively in , , . Hence, the Urzua-Jarque-Brera test adjusted for small samples is computed as: UJB(Xn)=Z~1(Xn)2+Z~2(Xn)2dnχ22.

Urzua-Jarque-Brera test
# =============== Setups =============== 
n <- 50 # number of simulations 
set.seed(1) # random seed 
ci <- 0.05 # confidence level 
# ======================================
# Simulated variable 
x <- rnorm(n, 0, 1)
# Sample skewness
b1 <- skewness(x)
# Skewness's variance
v_b1 <- (6*(n-2))/((n+1)*(n+3))
# Statistic for skewness
Z1 <- b1/sqrt(v_b1)
# Sample kurtosis 
b2 <- kurtosis(x, excess = F)
# Kurtosis's expected value
e_b2 <- (3*(n-1))/(n+1)
# Kurtosis's variance
v_b2 <- (24*n*(n-2)*(n-3))/((n+1)^2*(n+3)*(n+5))
# Statistic for kurtosis
Z2 <- (b2-e_b2)/sqrt(v_b2)
# UJB-statistic test
UJB <- Z1^2 + Z2^2
# UJB p-value 
pvalue <-  1 - pchisq(UJB, df = 2)
# UJB critic value at 95%
z_lim <- qchisq(1-ci, 2)
# =============== Plot =============== 
# Grid of points 
grid <- seq(0, 10, 0.01)
# Rejection area 
grid_rej <- grid[grid > z_lim]
# Non-rejection area 
grid_norej <- grid[grid < z_lim]
ggplot()+
  geom_line(aes(grid, dchisq(grid, 2)))+
  geom_segment(aes(z_lim, xend = z_lim, y = 0, yend = dchisq(z_lim, 2)))+
  geom_point(aes(UJB, 0))+
  geom_ribbon(aes(x = grid_rej, ymin = 0, ymax = dchisq(grid_rej, 2), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_norej, ymin = 0, ymax = dchisq(grid_norej, 2), fill = "norej"), alpha = 0.3)+
  geom_segment(aes(UJB, xend = UJB, y = 0, yend = dchisq(UJB, 2)))+
  scale_x_continuous(breaks = c(0, UJB, z_lim), 
                     labels = c("0", format(c(UJB, z_lim), digits = 4)))+
  labs(x = "UJB statistic", y = "", fill = NULL)+
  scale_fill_manual(values = c(rej = "red", norej = "green"), 
                    labels = c(rej = "Rejection Area", norej = "Non Rejection Area")) + 
  theme_bw()+
  theme(
    legend.position = "top",
    panel.grid = element_blank()
  )
Figure 25.2: Urzua-Jarque-Brera test for normality with a simulated Normal sample of 50 observations with α=0.05.

25.3 D’Agostino skewness test

D’Agostino and Pearson () proposed an alternative way to test that the skewness is different from zero. Starting from the statistic Z~1(Xn), compute: β2(b1)=3(n2+27n70)(n+1)(n+3)(n2)(n+5)(n+7)(n+9), and W2=2β2(b1)11,δ=1ln(W),α=2W21. The statistic test for skewness is defined as:
Z1(Xn)=δlog{Z~1(Xn)α+(Z~1(Xn)α)2+1}==δsinh1(Z~1(Xn)α), where Z1(Xn)N(0,1).

D’Agostino skewness test
# =============== Setups =============== 
n <- 20 # number of simulations 
set.seed(1) # random seed 
ci <- 0.05 # confidence level 
# ======================================
# Simulated variable 
x <- rnorm(n, 0, 1)
# Sample skewness
b1 <- skewness(x)
# Skewness's variance
v_b1 <- (6*(n-2))/((n+1)*(n+3))
# Statistic for skewness
Z1 <- b1/sqrt(v_b1)
# Test for Skewness 
beta2_b1 <- (3*(n^2 + 27*n - 70)*(n+1)*(n+3))/((n-2)*(n+5)*(n+7)*(n+9))
W2 <- sqrt(2*beta2_b1 - 1) - 1
delta <- 1/sqrt(log(sqrt(W2)))
alpha <- sqrt(2/(W2 - 1))
# Test for skewness
Z_ast_b1 <- delta*asinh(Z1/alpha)
# UJB p-value 
pvalue <- 1 - pnorm(Z_ast_b1, lower.tail = TRUE)
# UJB critic value at 95%
z_lim <- qnorm(1-ci/2)
# =============== Plot =============== 
# Grid of points 
grid <- seq(-4, 4, 0.01)
# Rejection area 
grid_rej_l <- grid[grid >= z_lim]
grid_rej_r <- grid[grid <= -z_lim]
# Non-rejection area 
grid_norej <- grid[!(grid > z_lim | grid < -z_lim)]
ggplot()+
  geom_line(aes(grid, dnorm(grid)))+
  geom_segment(aes(z_lim, xend = z_lim, y = 0, yend = dnorm(z_lim)))+
  geom_segment(aes(-z_lim, xend = -z_lim, y = 0, yend = dnorm(-z_lim)))+
  geom_point(aes(Z_ast_b1, 0))+
  geom_ribbon(aes(x = grid_rej_l, ymin = 0, ymax = dnorm(grid_rej_l), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_rej_r, ymin = 0, ymax = dnorm(grid_rej_r), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_norej, ymin = 0, ymax = dnorm(grid_norej), fill = "norej"), alpha = 0.3)+
  geom_segment(aes(Z_ast_b1, xend = Z_ast_b1, y = 0, yend = dnorm(Z_ast_b1)))+
  scale_x_continuous(breaks = c(0, Z_ast_b1, z_lim, -z_lim), 
                     labels = c("0", format(c(Z_ast_b1, z_lim, -z_lim), digits = 4)))+
  labs(x = "Skewness statistic", y = "", fill = NULL)+
  scale_fill_manual(values = c(rej = "red", norej = "green"), 
                    labels = c(rej = "Rejection Area", norej = "Non Rejection Area")) + 
  theme_bw()+
  theme(
    legend.position = "top",
    panel.grid = element_blank()
  )
Figure 25.3: Skewness test with a simulated Normal sample of 50 observations with α=0.05.

25.4 Anscombe Kurtosis test

Anscombe and Glynn () proposed a test for skewness. Starting from the statistic Z~2(Xn), let’s compute the third standardized moment of b2: β1(b2)=6(n25n+2)(n+7)(n+9)6(n+3)(n+5)n(n2)(n3), and A=6+8β1(b2)[2β1(b2)+1+4β1(b2)]. The statistic test for kurtosis is defined as: Z2(Xn)=9A2(129A[12/A1+Z~2(Xn)2/(A4)]1/3), where Z2(Xn)N(0,1).

Anscombe kurtosis test
# =============== Setups =============== 
n <- 50 # number of simulations 
set.seed(1) # random seed 
ci <- 0.05 # confidence level 
# ======================================
# Simulated variable 
x <- rnorm(n, 0, 1)
# Sample kurtosis 
b2 <- kurtosis(x, excess = F)
# Kurtosis's expected value
e_b2 <- (3*(n-1))/(n+1)
# Kurtosis's variance
v_b2 <- (24*n*(n-2)*(n-3))/((n+1)^2*(n+3)*(n+5))
# Statistic for kurtosis
Z2 <- (b2-e_b2)/sqrt(v_b2)
# Test for Skewness 
beta1_b2 <- (6*(n^2 - 5*n + 2))/((n+7)*(n+9))*sqrt((6*(n+3)*(n+5))/(n*(n-2)*(n-3)))
A <- 6 + (8/beta1_b2)*(2/beta1_b2 + sqrt(1 + 4/(beta1_b2^2)))
# Test for kurtosis
Z_ast_b2 <- sqrt((9*A)/2) *(1 - 2/(9*A) - ((1 - 2/A)/(1 + Z2*sqrt(2/(A-4))))^(1/3))
# p-value 
pvalue <- 1 - pnorm(Z_ast_b2, lower.tail = TRUE)
# UJB critic value at 95%
z_lim <- qnorm(1-ci/2)
# =============== Plot =============== 
# Grid of points 
grid <- seq(-4, 4, 0.01)
# Rejection area 
grid_rej_l <- grid[grid >= z_lim]
grid_rej_r <- grid[grid <= -z_lim]
# Non-rejection area 
grid_norej <- grid[!(grid > z_lim | grid < -z_lim)]
ggplot()+
  geom_line(aes(grid, dnorm(grid)))+
  geom_segment(aes(z_lim, xend = z_lim, y = 0, yend = dnorm(z_lim)))+
  geom_segment(aes(-z_lim, xend = -z_lim, y = 0, yend = dnorm(-z_lim)))+
  geom_point(aes(Z_ast_b2, 0))+
  geom_ribbon(aes(x = grid_rej_l, ymin = 0, ymax = dnorm(grid_rej_l), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_rej_r, ymin = 0, ymax = dnorm(grid_rej_r), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_norej, ymin = 0, ymax = dnorm(grid_norej), fill = "norej"), alpha = 0.3)+
  geom_segment(aes(Z_ast_b2, xend = Z_ast_b2, y = 0, yend = dnorm(Z_ast_b2)))+
  scale_x_continuous(breaks = c(0, Z_ast_b2, z_lim, -z_lim), 
                     labels = c("0", format(c(Z_ast_b2, z_lim, -z_lim), digits = 4)))+
  labs(x = "Kurtosis statistic", y = "", fill = NULL)+
  scale_fill_manual(values = c(rej = "red", norej = "green"), 
                    labels = c(rej = "Rejection Area", norej = "Non Rejection Area")) + 
  theme_bw()+
  theme(
    legend.position = "top",
    panel.grid = element_blank()
  )
Figure 25.4: Kurtosis test with a simulated Normal sample of 50 observations with α=0.05.

25.5 D’Agostino-Pearson K2 test

Finally in Ralph B. D’agostino and Jr. (), there is also another omnibus test based on the statistics Z1(Xn), Z2(Xn), i.e.  K2=(Z1(Xn))2+(Z2(Xn))2χ2(2).

D’Agostino-Pearson K2 test
# K2-statistic test
K2 <- Z_ast_b1^2 + Z_ast_b2
# p-value 
pvalue <-  1 - pchisq(K2, df = 2)
# critic value at 95%
z_lim <- qchisq(1-ci, 2)
# =============== Plot =============== 
# Grid of points 
grid <- seq(0, 10, 0.01)
# Rejection area 
grid_rej <- grid[grid > z_lim]
# Non-rejection area 
grid_norej <- grid[grid < z_lim]
ggplot()+
  geom_line(aes(grid, dchisq(grid, 2)))+
  geom_segment(aes(z_lim, xend = z_lim, y = 0, yend = dchisq(z_lim, 2)))+
  geom_point(aes(K2, 0))+
  geom_ribbon(aes(x = grid_rej, ymin = 0, ymax = dchisq(grid_rej, 2), fill = "rej"), alpha = 0.3)+
  geom_ribbon(aes(x = grid_norej, ymin = 0, ymax = dchisq(grid_norej, 2), fill = "norej"), alpha = 0.3)+
  geom_segment(aes(K2, xend = K2, y = 0, yend = dchisq(K2, 2)))+
  scale_x_continuous(breaks = c(0, K2, z_lim), 
                     labels = c("0", format(c(K2, z_lim), digits = 4)))+
  labs(x = "K2 statistic", y = "", fill = NULL)+
  scale_fill_manual(values = c(rej = "red", norej = "green"), 
                    labels = c(rej = "Rejection Area", norej = "Non Rejection Area")) + 
  theme_bw()+
  theme(
    legend.position = "top",
    panel.grid = element_blank()
  )
Figure 25.5: Agostino normality test with a simulated Normal sample of 50 observations with α=0.05.

25.6 Kolmogorov-Smirnov Test

The Kolmogorov–Smirnov test can be used to verify whether a samples is drawn from a reference distribution. In the case of q sample with dimension n, the KS statistic is defined as: (25.1)KSn=supx|Fn(x)F(x)|. In this settings, the test statistic follows the Kolmogorov distribution, i.e.  FKS(nKSn<x)=12k=1(1)k1e2k2x2.

Kolmogorov distribution
# Infinite sum function
# k: approximation for infinity 
inf_sum <- function(x, k = 100){
  isum <- 0
  for(i in 1:k){
    isum <- isum + 2*(-1)^(i-1)*exp(-2*(x^2)*(i^2))
  }
  isum
}
# Kolmogorov distribution function  
pks <- function(x, k = 100){
  p <- c()
  for(i in 1:length(x)){
    p[i] <- 1-inf_sum(x[i], k = k)
  }
  p
}
# Kolmogorov quantile function  
qks <- function(p, k = 100){
  loss <- function(x, p){
    (pks(x, k = k) - p)^2
  }
  x <- c()
  for(i in 1:length(p)){
    x[i] <- optim(par = 2, method = "Brent", lower = 0, upper = 10, fn = loss, p = p[i])$par
  }
  x
}

The null distribution of this statistic is calculated under the null hypothesis that the sample is drawn from the reference distribution F, i.e. H0:Fn(X)=F(X)H1:Fn(X)F(X) For large samples, H0 is rejected at a given confidence level α if: nKSn>FKS1(FKS(nKSn<Kα)), where Kα represents the critical value and FKS1 the quantile function of the Kolmogorov distribution.

25.6.1 Example 1: KS test for normality

Let’s simulate 500 observations of a stationary normal random variable, i.e. XnN(0.2,1).

Kolmogorov test (normal)
# ================== Setups ==================
set.seed(1) # random seed
n <- 5000   # number of simulations 
k <- 1000   # index for Kolmogorov cdf approx
ci <- 0.05  # confidence level (alpha)
# ===========================================
# Simulated stationary sample 
x <- rnorm(n, 0.2, 1)
# Set the interval values for upper and lower band
grid <- seq(quantile(x, 0.015), quantile(x, 0.985), 0.01)
# Empirical cdf
cdf <- ecdf(x)
# Compute the KS-statistic 
ks_stat <- sqrt(n)*max(abs(cdf(grid) - pnorm(grid, mean = 0.2)))
# Compute the rejection level  
rejection_lev <- qks(1-ci, k = k)
# ================== Kable ==================
kab <- dplyr::tibble(
  n = n,
  alpha = paste0(format(ci*100, digits = 3), "%"),
  KS = ks_stat,
  rejection_lev = rejection_lev,
  H0 = ifelse(KS > rejection_lev, "Rejected", "Non-Rejected"))  %>%
  dplyr::mutate_if(is.numeric, format, digits = 4, scientific = FALSE)
colnames(kab) <- c("$n$","$\\alpha$","$KS_{n}$", 
                   "$\\textbf{Critical Level}$","$H_0$")
knitr::kable(kab, escape = FALSE)
1
Simulated stationary sample
2
Set the interval values for upper and lower band. This is done to avoid including outliers, however it is possible to use also the minimum and maximum.
3
Empirical cdf
4
Compute the KS-statistic as in .
5
Compute the rejection level
Table 25.1: KS-test for normality on a Normal’s random sample with α=0.05.
n α KSn Critical Level H0
5000 5% 0.8651 1.358 Non-Rejected

25.6.2 Example 2: KS test for normality

Let’s simulate 500 observations of a stationary t-student random variable with increasing degrees of freedom ν, i.e. Xnt(ν). Then, we compare the obtained result with a standard normal random variable, i.e. N(0,1). It is known that increasing the degrees of freedom of a student-t imply the convergence to a standard normal. Hence, we expect that from a certain ν awards the null hypothesis will be no more rejected.

Kolmogorov test (normal vs student-t)
# ================== Setups ==================
seed <- 1   # random seed
n <- 5000   # number of simulations 
k <- 1000   # index for Kolmogorov cdf approx
ci <- 0.05  # confidence level (alpha)
# grid for student-t degrees of freedom 
nu <- c(1,5,10,15,20,30)
# ===========================================
kabs <- list()
for(v in nu){
  set.seed(seed)
  # Simulated stationary sample 
  x <- rt(n, df = v)
  # Set the interval values for upper and lower band
  grid <- seq(quantile(x, 0.015), quantile(x, 0.985), 0.01)
  # Empirical cdf
  cdf <- ecdf(x)
  # Compute the KS-statistic 
  ks_stat <- sqrt(n)*max(abs(cdf(grid) - pnorm(grid, mean = 0, sd = 1)))
  # Compute the rejection level  
  rejection_lev <- qks(1-ci, k = k)
  kabs[[v]] <- dplyr::tibble(nu = v, 
                             n = n,  
                             alpha = paste0(format(ci*100, digits = 3), "%"),
                             KS = ks_stat, 
                             rej_lev = rejection_lev,
                             H0 = ifelse(KS > rej_lev, "Rejected", "Non-Rejected")
  ) 
}
# =============== Kable ===============
kab <- dplyr::bind_rows(kabs) %>%
  dplyr::mutate_if(is.numeric, format, digits = 4, scientific = FALSE)
colnames(kab) <- c("$\\nu$", "$n$", "$\\alpha$", "$KS_{n}$",
                   "$\\textbf{Critical Level}$", "$H_0$")
knitr::kable(kab, escape = FALSE)
Table 25.2: KS-test for normality on t-student’s random samples with α=0.05.
ν n α KSn Critical Level H0
1 5000 5% 9.356 1.358 Rejected
5 5000 5% 2.974 1.358 Rejected
10 5000 5% 1.751 1.358 Rejected
15 5000 5% 1.462 1.358 Rejected
20 5000 5% 1.285 1.358 Non-Rejected
30 5000 5% 1.190 1.358 Non-Rejected