The two-piece normal, binormal, split normal, or asymmetric normal (Wallis 2014), is obtained by continuously joining two half-normal densities with different scale parameters but with the same location parameter: \[s(x \mid \mu, \sigma_1, \sigma_2) = \dfrac{2}{\sigma_1 + \sigma_2} \left[\phi\left(\dfrac{x-\mu}{\sigma_1}\right)I(x<\mu) + \phi\left(\dfrac{x-\mu}{\sigma_2}\right)I(x\geq\mu)\right],\] where \(\phi(x) = \dfrac{1}{\sqrt{2\pi}}\exp\left(-\dfrac{x^2}{2}\right)\). This distribution has a peculiar history as it has been re-invented several times, under different levels of generality, and different parameterisations (Rubio and Steel 2020; Wallis 2014). The two-piece normal density is unimodal by construction, it is continuous, and differentiable everywhere. However, it is not twice differentiable at the mode. Simulating from the two-piece normal distribution is easy using the twopiece
R package, as illustrated in our first example. The two-piece normal is a member of the larger family of two-piece distributions (Rubio and Steel 2020).
The two-piece normal distribution has been applied in many areas such as finance, medicine, economy, psychology, and etcetera (Rubio and Steel 2020). See The Two Piece Normal Distribution and Fan Charts for applications in finance.
Rubio and Steel (2014) studied different types of priors for two-piece distributions. For illustrative purposes, we will adopt the following priors for the parameters \((\mu,\sigma_1,\sigma_2)\):
\[\begin{align} \mu &\sim N(0,100),\\ \sigma_1 &\sim InvGamma(0.01,0.01),\\ \sigma_2 &\sim InvGamma(0.01,0.01). \end{align}\]
Next, we present two illustrative examples. Example 1 describes how to simulate from the two-piece normal distribution using the twopiece
R package, how to sample from the posterior distribution of \((\mu,\sigma_1,\sigma_2)\) using rstan
, and how to calculate the posterior predictive distribution. Example 2 presents a real data application. The rstan
code used in both examples can be found at the end of this document.
rm(list=ls())
#------------------------------------------------
# Data simulation
#------------------------------------------------
# install.packages("devtools")
#devtools::install_github("medewitt/twopiece")
library(twopiece)
N <- 1000 # Sample size
set.seed(123) # Seed
# Simulated sample from a twopiece normal using the twopiece parameterisation
y <- rtp3(N,10,3,1,FUN=rnorm,param="tp")
# Data list
datasim <- list(y = y, N = N)
#------------------------------------------------
# Posterior simulation
#------------------------------------------------
library(rstan)
fit1 <- stan(
file = "TPN.stan", # Stan program
data = datasim, # named list of data
chains = 1, # number of Markov chains
warmup = 1000, # number of warmup iterations per chain
iter = 11000, # total number of iterations per chain
cores = 2, # number of cores (could use one per chain)
refresh = 0 # no progress shown
)
# Trace plots and histograms (thinned samples)
index = seq(1,10000, by = 5) # thinning index
# thinned samples
mup <- extract(fit1, "mu")$mu[index]
sigma1p <- extract(fit1, "sigma1")$sigma1[index]
sigma2p <- extract(fit1, "sigma2")$sigma2[index]
# summaries
summary(mup)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 9.553 9.975 10.069 10.066 10.159 10.458
summary(sigma1p)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 2.653 2.994 3.062 3.063 3.129 3.424
summary(sigma2p)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.6875 0.8705 0.9233 0.9268 0.9804 1.2051
# traceplots
plot(mup, type="l", ylab = expression(mu), cex.axis = 1.5, cex.lab = 1.5)
plot(sigma1p,type="l", ylab = expression(sigma[1]), cex.axis = 1.5, cex.lab = 1.5)
plot(sigma2p,type="l", ylab = expression(sigma[2]), cex.axis = 1.5, cex.lab = 1.5)
# histograms
hist(mup, breaks = 30, probability = T,
main = "", xlab = expression(mu), cex.axis = 1.5, cex.lab = 1.5)
points(density(mup), type="l", col="red", lwd = 2)
box()
hist(sigma1p, breaks = 30, probability = T,
main = "", xlab = expression(sigma[1]), cex.axis = 1.5, cex.lab = 1.5)
points(density(sigma1p), type="l", col="red", lwd = 2)
box()
hist(sigma2p, breaks = 30, probability = T,
main = "", xlab = expression(sigma[2]), cex.axis = 1.5, cex.lab = 1.5)
points(density(sigma2p), type="l", col="red", lwd = 2)
box()
#------------------------------------------------
# Posterior predictive density function
#------------------------------------------------
dpred <- Vectorize(function(x){
val <- vector()
for(i in 1:length(index)) val[i] <- dtp3(x,mup[i],sigma1p[i],sigma2p[i], FUN=dnorm, param ="tp")
return(mean(val))
})
hist(y,probability = T, breaks = 20, xlim = c(0,15),
main="Predictive PDF", xlab = "y", cex.axis = 1.5, cex.lab = 1.5)
curve(dpred, 0, 15, n = 250, add = T, col="red", lwd = 2 )
box()
rm(list=ls())
# Data: BMI of 100 female Australian athletes
y <- c(20.56,20.67,21.86,21.88,18.96,21.04,21.69,20.62,22.64,19.44,25.75,21.20,22.03,25.44,22.63,
21.86,22.27,21.27,23.47,23.19, 23.17,24.54,22.96,19.76,23.36,22.67,24.24,24.21,20.46,20.81,
20.17,23.06,24.40,23.97,22.62,19.16,21.15,21.40,21.03,21.77,21.38,21.47,24.45,22.63,22.80,
23.58,20.06,23.01,24.64,18.26,24.47,23.99,26.24,20.04,25.72,25.64,19.87,23.35,22.42,20.42,
22.13,25.17,23.72,21.28,20.87,19.00,22.04,20.12,21.35,28.57,26.95,28.13,26.85,25.27,
31.93,16.75,19.54,20.42,22.76,20.12,22.35,19.16,20.77,19.37,22.37,17.54,19.06,20.30,20.15,
25.36,22.12,21.25,20.53,17.06,18.29,18.37,18.93,17.79,17.05,20.31)
summary(y)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 16.75 20.27 21.82 21.99 23.39 31.93
N <- length(y)
# Data list
datasim <- list(y = y, N = N)
#------------------------------------------------
# Posterior simulation
#------------------------------------------------
library(rstan)
fit1 <- stan(
file = "TPN.stan", # Stan program
data = datasim, # named list of data
chains = 1, # number of Markov chains
warmup = 1000, # number of warmup iterations per chain
iter = 11000, # total number of iterations per chain
cores = 2, # number of cores (could use one per chain)
refresh = 0 # no progress shown
)
# Trace plots and histograms (thinned samples)
index = seq(1,10000, by = 5) # thinning index
# thinned samples
mup <- extract(fit1, "mu")$mu[index]
sigma1p <- extract(fit1, "sigma1")$sigma1[index]
sigma2p <- extract(fit1, "sigma2")$sigma2[index]
# summaries
summary(mup)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 18.45 20.37 20.80 20.76 21.16 22.60
summary(sigma1p)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.6642 1.5769 1.8018 1.8172 2.0459 3.1841
summary(sigma2p)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 2.197 3.103 3.371 3.395 3.667 4.969
# traceplots
plot(mup, type="l", ylab = expression(mu), cex.axis = 1.5, cex.lab = 1.5)
plot(sigma1p,type="l", ylab = expression(sigma[1]), cex.axis = 1.5, cex.lab = 1.5)
plot(sigma2p,type="l", ylab = expression(sigma[2]), cex.axis = 1.5, cex.lab = 1.5)
# histograms
hist(mup, breaks = 30, probability = T,
main = "", xlab = expression(mu), cex.axis = 1.5, cex.lab = 1.5)
points(density(mup), type="l", col="red", lwd = 2)
box()
hist(sigma1p, breaks = 30, probability = T,
main = "", xlab = expression(sigma[1]), cex.axis = 1.5, cex.lab = 1.5)
points(density(sigma1p), type="l", col="red", lwd = 2)
box()
hist(sigma2p, breaks = 30, probability = T,
main = "", xlab = expression(sigma[2]), cex.axis = 1.5, cex.lab = 1.5)
points(density(sigma2p), type="l", col="red", lwd = 2)
box()
#------------------------------------------------
# Posterior predictive density function
#------------------------------------------------
dpred <- Vectorize(function(x){
val <- vector()
for(i in 1:length(index)) val[i] <- dtp3(x,mup[i],sigma1p[i],sigma2p[i], FUN=dnorm, param ="tp")
return(mean(val))
})
hist(y, probability = T, breaks = 20, xlim = c(15,35),
main="Predictive PDF", xlab = "BMI", cex.axis = 1.5, cex.lab = 1.5)
curve(dpred, 15, 35, n = 250, add = T, col="red", lwd = 2 )
box()
data {
int < lower = 1 > N; // Sample size
vector[N] y; // Sample
}
parameters {
real mu; // mean
real < lower = 0 > sigma1;
real < lower = 0 > sigma2;
}
model {
// Priors
target += normal_lpdf(mu | 0, 100);
target += inv_gamma_lpdf(sigma1 | 0.01, 0.01);
target += inv_gamma_lpdf(sigma2 | 0.01, 0.01);
// Likelihood
for(n in 1:N){
if(y[n] < mu)
{
target += normal_lpdf( (y[n]-mu)/sigma1 | 0 , 1) + log(2) - log(sigma1 + sigma2);
}
if(y[n] >= mu){
target += normal_lpdf( (y[n]-mu)/sigma2 | 0 , 1) + log(2) - log(sigma1 + sigma2);
}
}
}
generated quantities {
}
References
Rubio, F.J., and M.F.J. Steel. 2014. “Inference in Two-Piece Location-Scale Models with Jeffreys Priors.” Bayesian Analysis 9 (1): 1–22.
———. 2020. “The Family of Two-Piece Distributions.” Significance 17 (1): 12–13.
Wallis, K.F. 2014. “The Two-Piece Normal, Binormal, or Double Gaussian Distribution: Its Origin and Rediscoveries.” Statistical Science 29 (1): 106–12.