Lecture 29—Wednesday, December 3, 2008

What was covered?

Terminology defined

R functions and commands demonstrated

R function options

R packages used

Variance Heterogeneity

y normal distribution

Thus although it's permissible for different observations to come from normal distributions with different means, mu sub i, we require that each of these normal distributions share a common variance, sigma squared.

Variance Heterogeneity in an ANOVA Model

tadpoles <- read.table( 'http://www.unc.edu/courses/2008fall/ecol/563/001/data/lab1/gooddat.txt', header=TRUE)
prelim <- as.character(tadpoles$var)
treatment<-substring(prelim,6,9)
response<-as.numeric(substring(prelim,10,nchar(prelim)))
#obtain treatment means
treat.mean <- tapply(response, treatment, mean, na.rm=T)
#produce plot with customized y-axis labels
boxplot(response~treatment, horizontal=T, axes=F, outline=F, xlab='Mitotic activity', ylim=range(response,na.rm=T))
axis(1)
axis(2,las=2,at=1:12,labels=names(treat.mean))
box()
#overlay raw data
points(response, jitter(as.numeric(factor(treatment))), pch=16, col='seagreen', cex=.6)
#add group means
points(treat.mean,1:12,pch=8,col=2)

Fig. 1  Graphical summary of the results of the tadpole experiment

library(lattice)
apply(response,treatment, var, na.rm=T)->treat.var
dotplot(names(treat.var)~treat.var, xlab='Variance')
tapply(response, treatment, mad, na.rm=T)->treat.mad
dotplot(names(treat.mad)~treat.mad, xlab='MAD')
(a)   (b)  
Fig. 2  Dotplots of (a) group variances and (b) group MADs (median absolute deviations)
tapply(response, treatment, function(x) sum(!is.na(x))) -> grplength
grplength
CoD1 CoD2 CoS1 CoS2 NoD1 NoD2 NoS1 NoS2 RuD1 RuD2 RuS1 RuS2
  13   17   24   24   19   24   22   24   12   24   12   24

Because of all the missing data it turns out there is a substantial imbalance across the groups. So we can't appeal to robustness.

Weighted least squares

fac1<-factor(substring(treatment,1,2))
fac2<-factor(substring(treatment,3,3))
fac3<-factor(substring(treatment,4,4))
tapply(response, treatment,var, na.rm=T)->grpvar
frame.var<-data.frame(names(grpvar), grpvar)
colnames(frame.var)[1]<-'treatment'
frame.var2<-data.frame(treatment, response, fac1, fac2, fac3)
frame.var3<-merge(frame.var, frame.var2, all=T)
frame.var3$wght<-1/frame.var3$grpvar
#fit full model
lm(response~fac1*fac2*fac3, data=frame.var3, weight=wght)->out1a
#weighted: test 3-factor interaction
update(out1a,.~.-fac1:fac2:fac3)->out2a
anova(out2a,out1a)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac1:fac3 + fac2:fac3
Model 2: response ~ fac1 * fac2 * fac3
  Res.Df    RSS Df Sum of Sq      F Pr(>F)
1    229 227.79
2    227 227.00  2      0.79 0.3949 0.6742

#weighted: test fac1:fac3 interaction
update(out2a,.~.-fac1:fac3)->out3a
anova(out3a,out2a)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac2:fac3
Model 2: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac1:fac3 + fac2:fac3
  Res.Df     RSS Df Sum of Sq      F Pr(>F)
1    231 229.587
2    229 227.790  2     1.797 0.9034 0.4066

#weighted: test fac1:fac2 interaction
update(out3a,.~.-fac1:fac2)->out4a
anova(out4a,out3a)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3 + fac2:fac3
Model 2: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac2:fac3
  Res.Df     RSS Df Sum of Sq      F  Pr(>F)
1    233 234.326
2    231 229.587  2     4.739 2.3841 0.09444 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

#weighted: test fac2:fac3 interaction
update(out4a,.~.-fac2:fac3)->out5a
anova(out5a,out4a)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3
Model 2: response ~ fac1 + fac2 + fac3 + fac2:fac3
  Res.Df    RSS Df Sum of Sq      F   Pr(>F)
1    234 244.03
2    233 234.33  1      9.70 9.6449 0.002134 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

lm(response~fac1*fac2*fac3,data=frame.var3)->out1
#unweighted: test 3-factor interaction
update(out1,.~.-fac1:fac2:fac3)->out2
anova(out2,out1)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac1:fac3 + fac2:fac3
Model 2: response ~ fac1 * fac2 * fac3
  Res.Df     RSS Df Sum of Sq      F Pr(>F)
1    229 13.8533
2    227 13.7838  2    0.0695 0.5723  0.565

#unweighted: test fac1:fac3 interaction
update(out2,.~.-fac1:fac3)->out3
anova(out3,out2)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac2:fac3
Model 2: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac1:fac3 + fac2:fac3
  Res.Df    RSS Df Sum of Sq      F Pr(>F)
1    231 13.908
2    229 13.853  2     0.055 0.4542 0.6355

#unweighted: test fac1:fac2 interaction
update(out3,.~.-fac1:fac2)->out4
anova(out4,out3)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3 + fac2:fac3
Model 2: response ~ fac1 + fac2 + fac3 + fac1:fac2 + fac2:fac3
  Res.Df     RSS Df Sum of Sq      F  Pr(>F)
1    233 14.2100
2    231 13.9083  2    0.3017 2.5057 0.08384 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

#unweighted: test fac2:fac3 interaction
update(out4,.~.-fac2:fac3)->out5
anova(out5,out4)
Analysis of Variance Table

Model 1: response ~ fac1 + fac2 + fac3
Model 2: response ~ fac1 + fac2 + fac3 + fac2:fac3
  Res.Df   RSS Df Sum of Sq      F   Pr(>F)
1    234 14.68
2    233 14.21  1      0.47 7.7069 0.005948 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Checking the sensitivity of the weighted analysis to the choice of weights

hist(z$RuS2, probability=T, xlim=c(3.8,4.8), xlab='Response', main='Treatment Group RuS2', col='grey90, cex.main=1.05')
lines(density(z$RuS2), col=2)

density plot

Fig. 3  Distribution of response for treatment RuS2

y<-response[!is.na(response)]
x<-treatment[!is.na(response)]
z<-split(y,x)
boot.samp<-lapply(z,function(x) sample(x, replace=T))
z[1:2]
$CoD1
 [1] 4.166 3.247 3.289 3.195 3.431 3.180 3.140 3.301 3.253 3.155 3.119
[12] 3.250 4.454

$CoD2
 [1] 3.711 3.375 3.622 3.315 3.473 3.820 3.548 3.428 3.502 3.572 3.497
[12] 4.395 3.075 3.341 3.300 2.915 3.257

sort(z$CoD1)
 [1] 3.119 3.140 3.155 3.180 3.195 3.247 3.250 3.253 3.289 3.301 3.431
[12] 4.166 4.454
sort(boot.samp$CoD1)
 [1] 3.119 3.140 3.140 3.155 3.247 3.247 3.247 3.253 3.289 3.301 3.431
[12] 3.431 3.431
#num.boots is the number of times to resample
#myseed sets the random number seed for the bootstrap samples
boot.wt.func<-function(num.boots,myseed=10)
{
set.seed(myseed)
boot.results<-matrix(NA,num.boots,4)
#obtain bootstrap weights and run ANOVA tests
for(i in 1:num.boots) {
#split data into treatment groups
y<-response[!is.na(response)]
x<-treatment[!is.na(response)]
z<-split(y,x)
#resample each treatment group
boot.samp<-lapply(z,function(x) sample(x, replace=T))
#calculate weight for each treatment group
new.wt<-1/sapply(boot.samp,var)
#add weights to raw data
frame.wt<-data.frame(names(new.wt), new.wt)
colnames(frame.wt)<-c('treatment', 'wght')
frame.dat<-data.frame(treatment, response, fac1, fac2, fac3)
frame.boot<-merge(frame.wt, frame.dat, all=T)

#### carry out ANOVA tests ####
#test 3-factor interaction
lm(response~fac1*fac2*fac3, data=frame.boot, weight=wght) -> mod1
update(mod1,.~.-fac1:fac2:fac3) -> mod2
anova(mod2,mod1) -> anova.int3
p1 <- anova.int3["Pr(>F)"][2,1]

#test fac1 : fac3 interaction
update(mod2,.~.-fac1:fac3) -> mod3
anova(mod3,mod2) -> anova.int13
p2 <- anova.int13["Pr(>F)"][2,1]

#test fac1 : fac2 interaction
update(mod3,.~.-fac1:fac2) -> mod4
anova(mod4,mod3) -> anova.int12
p3 <- anova.int12["Pr(>F)"][2,1]

#test fac2 : fac3 interaction
update(mod4,.~.-fac2:fac3)->mod5
anova(mod5,mod4)->anova.int23
p4 <- anova.int23["Pr(>F)"][2,1]

boot.results[i,] <- c(p1, p2, p3, p4)
}
return(boot.results)
}
boot.wt.func(1000,15)->boot.dist
apply(boot.dist,2,function(x) quantile(x, c(.025, .25, .5, .75, .975))) -> out
colnames(out) <- c('3-factor','fac1 x fac3','fac1 x fac2','fac2 x fac3')
out
       3-factor fac1 x fac3 fac1 x fac2  fac2 x fac3
2.5%  0.6125987   0.2743471  0.04583161 0.0005427165
25%   0.6799251   0.3823111  0.08452139 0.0019939765
50%   0.7233457   0.4550797  0.11821048 0.0036579286
75%   0.7570539   0.5317377  0.18328789 0.0072876328
97.5% 0.8244707   0.8409729  0.42074773 0.0227337888
#expand left margin to make room for tick labels
par(mar=c(5.1,6.1,1.1,1.1))
#change bar ends to square
par(lend=2)
plot(range(out) ,c(.75,4.25), type='n', log='x', axes=F, ylab=' ', xlim=c(.0001,1), xlab='p-value')
axis(1, at=10^(-4:0), labels=c('.0001', '.001', '.01', '.1', '1'), cex.axis=.9)
axis(2, at=1:4, labels=colnames(out), las=2, cex.axis=.9)
box()
rbind(1:4,out)->out.full
apply(out.full, 2, function(x) segments(x[2], x[1], x[6], x[1], lwd=3))
apply(out.full, 2, function(x) segments(x[3], x[1], x[5], x[1], lwd=6, col='grey70'))
abline(v=.05, col=2, lty=2)
apply(out.full ,2, function(x) points(x[4], x[1], pch=16, col='white'))
apply(out.full,2, function(x) points(x[4], x[1], pch=1, col=1, cex=1.1))
segments(.045, 1.5, .06, 1.5, lwd=12, col='white')
text(.05, 1.5, expression(alpha==.05), col=2, cex=.9)

smear graph

Fig. 4  Probability smear graph summarizing the results from using bootstrapped weights

apply(boot.dist[,1:3],2,function(x) quantile(x,.05))
[1] 0.62826954 0.30110470 0.05350924
quantile(boot.dist[,4],.95)
       95%
0.01692254

The lower limit of the one-sided confidence interval for the fac1 × fac2 interaction p-value is 0.0535. Thus we see that 95% of the time the weights obtained through resampling yield p-values for the test of the factor 1 × factor 2 interaction that are larger than 0.05.

hist(boot.dist[,3], xlab='p-value', ylab='density', breaks=seq(0,.7,.025), main='', probability=T)
lines(density(boot.dist[,3]), col=2, lwd=2)

density

Fig. 5  Distribution of p-values for a test of the fac1 × fac2 interaction
using weights calculated from bootstrapped samples

Variance Heterogeneity in a Mixed Effects Model with a Non-negative Response

Model diagnostics for the kestrel nonlinear growth model

kestrel <- read.table('http://www.unc.edu/courses/2008fall/ecol/563/001/data/lab13/kestrel_data.csv', header=TRUE, sep=',', na.strings='.')
kestrel2<-kestrel[kestrel$egg==1 & !is.na(kestrel$nstlmass),]
kestrel3<-kestrel2[!is.na(kestrel2$sex),]
library(nlme)
#begin calculations to obtain initial estimates
as.numeric(names(table(kestrel2$nest)))[table(kestrel2$nest)>=4] -> good.nests
kestrel2.short<-kestrel2[kestrel2$nest %in% good.nests,]
just.enough<-kestrel2.short[,c("nstlmass", "age", "nest", "sex", "trtmnt")]
#least squares estimates from individual fits
out.nls<-nlsList(nstlmass~SSgompertz(age,a0,b0,b1)|nest, data=just.enough)
#make a data frame out of coefficient estimates
coef.mat<-data.frame(rownames(coef(out.nls)), coef(out.nls))
names(coef.mat)[1]<-'nest'
#make data frame of individual coefficient estimates
just.enough[tapply(1:nrow(just.enough), just.enough$nest, function(x) x[1]),]->abbrev
coef.nests<-merge(abbrev,coef.mat)
#initial estimates for b1
parm.means<- apply(coef(out.nls), 2, mean)
#initial estimates for a0
a0.means<-as.vector(tapply(coef.nests$a0, list(coef.nests$sex, coef.nests$trtmnt), mean))
amat<-matrix(c(1,1,1,1,0,1,0,1,0,0,1,1,0,0,0,1), ncol=4, nrow=4)
ainv<-solve(amat)
a0.guesses<-as.vector(ainv %*% a0.means)
#initial estimates for b0
tapply(coef.nests$b0, coef.nests$sex, mean) -> b0sex.means
guesses.b0sex<-c(a0.guesses, b0sex.means[1], b0sex.means[2]-b0sex.means[1], parm.means[3])
#fit model
a0b0.intsex <- nlme(nstlmass~SSgompertz(age,a0,b0,b1), fixed=list(a0~sex*trtmnt, b0~sex, b1~1), random=a0~1|nest, data=kestrel3, na.action=na.omit, start=guesses.b0sex, control=nlmeControl(maxIter=1000, niterEM=500, msMaxIter=500))

Variance heterogeneity

#Fig. 6a: plot residuals against fitted values
plot(predict(a0b0.intsex), residuals(a0b0.intsex, type='pearson'), xlab='Predicted values', ylab='Pearson residuals')
#Fig. 6b: plot residuals at each age
boxplot(residuals(a0b0.intsex,type='pearson')~factor(kestrel3$age), xlab='Age', ylab='Pearson Residuals', col='gray85', outline =F, ylim=range(residuals(a0b0.intsex,type='pearson')))
points(jitter(as.numeric(factor(kestrel3$age)), amount=.1), residuals(a0b0.intsex, type='pearson'), pch=19, cex=.6, col=4)
#add means to boxplots
points(1:6, tapply(residuals(a0b0.intsex, type='pearson'), factor(kestrel3$age), mean), pch=8, col=2)
(a) residual plot (b) residual boxplot
Fig. 6 (a) Residuals from Gompertz model plotted against predicted values. There is a noticeable increase in spread from left to right particularly as reflected by the negative residual values. (b) Residuals plotted against age. The spread appears to increase with age. This is especially noticeable for the first three age classes.

As can be seen residual variance increases both with age and the level of the predicted value. Much of the increased variability is probably due mostly to the increased spread in the negative residuals, which is largely a consequence of the model mean moving away from zero, the lower boundary restriction on the data.

Temporal correlation

plot(ACF(a0b0.intsex, form=~age), alpha=.05)
plot(ACF(a0b0.intsex, form=~age), alpha=.01)
(a) ACF plot (b) ACF with Bonferroni
Fig. 7  Autocorrelation plot of the residuals treating them in age order. Displayed confidence bands use (a) α = .05 and hence no correction for multiple testing; (b) α = .01 using a Bonferroni correction.

Normality

library(car)
qq.plot(residuals(a0b0.intsex, type='pearson'), ylab='Pearson residuals')

normal probability plot

Fig. 8  Normal probability plot of level-1 residuals

An ad hoc approach to dealing with variance heterogeneity

tapply(residuals(a0b0.intsex, type='pearson'), kestrel3$age,var) -> vars
plot(as.numeric(names(vars)), vars, xlab='Age', ylab='Residual Variance')
lines(lowess(vars~as.numeric(names(vars))), lty=1, col=2)
abline(lm(vars~as.numeric(names(vars))), lty=2, col=4)
legend(5, 130, c('lowess', 'linear regression'), col=c(2,4), lty=c(1,2), cex=.9, bty='n')

variance plot

Fig. 9 Variance of the Pearson residuals at each level of age with locally weighted (lowess) and linear regression lines superimposed.
kestrel3$ageclass<-as.numeric(factor(kestrel3$age))
var1<-update(a0b0.intsex, weights=varFixed(~ageclass))
var2<-update(a0b0.intsex, weights=varPower(form=~ageclass))
var3<-update(a0b0.intsex, weights=varConstPower(form=~ageclass))
var4<-update(a0b0.intsex, weights=varExp(form=~ageclass))
sapply(list(a0b0.intsex, var1, var2, var3, var4), AIC)
600.0304 578.2691 557.2259 559.2259 570.5778

Table 2   A comparison of the variance models

Variance Model

R function

K

AIC

None

9

600.03

varFixed

varFixed

9

578.27

varPower

varPower

10

557.23

varConstPower

varConstPower

11

559.23

VarExp

varExp

10

570.57

The AIC-best model expresses the variance as a power of age. Diagnostic plots for this model are shown in Fig. 10 below.

#Fig 10a: plot residuals against fitted values
plot(predict(var2), residuals(var2, type='pearson'), xlab='Predicted values', ylab='Pearson residuals')
#Fig 10b: plot residuals at each age
boxplot(residuals(var2, type='pearson')~factor(kestrel3$age), xlab='Age', ylab='Pearson Residuals', col='gray85', outline =F, ylim=range(residuals(var2, type='pearson')))
points(jitter(as.numeric(factor(kestrel3$age)), amount=.1), residuals(var2, type='pearson'), pch=19, cex=.6, col=4)
points(1:6, tapply(residuals(var2, type='pearson'), factor(kestrel3$age), mean), pch=8, col=2)
#Fig 10c: autocorrelation plot
plot(ACF(var2, form=~age), alpha=.05)
#Fig 10d: normal probability plot of the residuals
qq.plot(residuals(var2, type='pearson'), ylab='Pearson residuals')
(a) fig10a (b) fig10b
(c) fig10c (d) fig10d
Fig. 10  Diagnostic plots for the best Gompertz in which the residual variance is allowed to vary as a power of the age class variable
summary(var2)
Nonlinear mixed-effects model fit by maximum likelihood
  Model: nstlmass ~ SSgompertz(age, a0, b0, b1)
 Data: kestrel3
    AIC      BIC    logLik
557.226 581.1704 -268.6130

Random effects:
 Formula: a0 ~ 1 | nest
        a0.(Intercept) Residual
StdDev:    0.003781860 1.509470

Variance function:
 Structure: Power of variance covariate
 Formula: ~ageclass
 Parameter estimates:
   power
1.609958
Fixed effects: list(a0 ~ sex * trtmnt, b0 ~ sex, b1 ~ 1)
                          Value Std.Error DF   t-value p-value
a0.(Intercept)        160.96021 22.700680 57   7.09055  0.0000
a0.sexm               -43.64042 15.471584 57  -2.82068  0.0066
a0.trtmntcontrol        7.50702  8.820041 57   0.85113  0.3983
a0.sexm:trtmntcontrol  26.60904 12.220438 57   2.17742  0.0336
b0.(Intercept)          2.74664  0.137231 57  20.01480  0.0000
b0.sexm                -0.21784  0.125478 57  -1.73612  0.0879
b1                      0.91324  0.008117 57 112.51116  0.0000
guesses.var2a<-c(a0.guesses, parm.means[2:3])
var2a <- nlme(nstlmass~SSgompertz(ageclass,a0,b0,b1), fixed=list(a0~sex*trtmnt, b0~1, b1~1), random=a0~1|nest, data=kestrel3, na.action=na.omit, start=guesses.var2a, weights=varPower(form=~ageclass))
sapply(list(var2,var2a),AIC)
557.2259 558.3585
#Variance components of model with modeled variance
VarCorr(var2)
nest = pdLogChol(list(a0 ~ 1))
             Variance      StdDev
         1.430247e-05 0.003781860
Residual 2.278501e+00 1.509470331
#Variance components of original model
VarCorr(a0b0.intsex)
nest = pdLogChol(list(a0 ~ 1))
          Variance    StdDev
         174.74069 13.218952
Residual  54.49631  7.382162
var2.gnls <- gnls(nstlmass~SSgompertz(ageclass,a0,b0,b1), params=list(a0~sex*trtmnt, b0~1, b1~1), data=kestrel3, na.action=na.omit, start=guesses.var2a, weights=varPower(form=~ageclass))
sapply(list(var2,var2.gnls),AIC)

557.2259 556.3585

A direct approach to dealing with variance heterogeneity: a gamma probability model

Using WinBUGS

Fitting the normal Gompertz mixed effects model in WinBUGS

Gompertz model with normal errors: "gompertz bugs.txt"
model{
#normal likelihood--level 1 model
for(i in 1:n) {
y[i]~dnorm(y.hat[i], tau.y)
#Gompertz mean
y.hat[i]<-a0[nest[i]]*exp(-b0[nest[i]]*pow(b1[nest[i]],age[i]))
}
#priors for level-1 parameters
tau.y<-pow(sigma.y,-2)
sigma.y~dunif(0,100)
#level-2 model for Gompertz parameters
for(j in 1:J){
a0[j]~dnorm(a.hat[j],tau.a)
a.hat[j]<-mu.a+g1*sex[j]+g2*trt[j]+g3*sextrt[j]
b0[j]<-mu.b0+g4*sex[j]
b1[j]<-mu.b1
}
#priors for level-2 parameters
mu.a~dnorm(0,.0001)
tau.a<-pow(sigma.a,-2)
sigma.a~dunif(0,1000)
mu.b0~dnorm(0,.0001)
mu.b1~dnorm(0,.0001)
g1~dnorm(0,.0001)
g2~dnorm(0,.0001)
g3~dnorm(0,.0001)
g4~dnorm(0,.0001)
#extra derived quantities
a0.femtrt<-mu.a
a0.femcontrol<-mu.a+g2
a0.maletrt<-mu.a+g1
a0.malecontrol<-mu.a+g1+g2+g3
b0.fem<-mu.b0
b0.male<-mu.b0+g4
}
setwd('D:\\Ecology 563 Fall 2008\\lecture29')
library(arm)
y<-kestrel3$nstlmass
age<-kestrel3$age
n<-length(y)
nest<-as.numeric(factor(kestrel3$nest))
J<-length(unique(kestrel3$nest))
sex<-as.numeric(tapply(kestrel3$sex,kestrel3$nest, function(x) x[1]))-1
trt<-as.numeric(tapply(kestrel3$trt,kestrel3$nest, function(x) x[1]))-1
sextrt<-sex*trt
kestrel.data<-list("y", "age", "n", "nest", "J", "sex", "trt", "sextrt")
kestrel.inits<-function() {list(a0=rnorm(J), mu.a=runif(1), g1=rnorm(1), g2=rnorm(1), g3=rnorm(1), g4=rnorm(1), mu.b0=runif(1), mu.b1=runif(1), sigma.y=runif(1), sigma.a=runif(1))}
kestrel.parameters<-c("a0", "mu.a", "mu.b0", "mu.b1", "g1", "g2", "g3", "g4", "sigma.y", "sigma.a")
kestrel.out<-bugs(kestrel.data,kestrel.inits,kestrel.parameters, "gompertz bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=10, debug=T)
kestrel.out<-bugs(kestrel.data,kestrel.inits,kestrel.parameters, "gompertz bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=1000, debug=T)
kestrel.out2<-bugs(kestrel.data,kestrel.out$last.values,kestrel.parameters, "gompertz bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=10000, debug=T)
max(kestrel.out2$summary[,"Rhat"])
[1] 1.018114
min(kestrel.out2$summary[,"n.eff"])
[1] 110
kestrel.out3<-bugs(kestrel.data, kestrel.out2$last.values, kestrel.parameters, "gompertz bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=20100, n.burnin=100, debug=T)
min(kestrel.out3$summary[,"n.eff"])
[1] 15
max(kestrel.out3$summary[,"Rhat"])
[1] 1.192872
kestrel.out4<-bugs(kestrel.data, kestrel.out3$last.values, kestrel.parameters, "gompertz bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=50100, n.burnin=100, debug=T)
max(kestrel.out3$summary[,"Rhat"])
[1] 1.081149
min(kestrel.out3$summary[,"n.eff"])
[1] 70
kestrel.parameters2<-c("mu.a", "g1", "g2", "g3", "g4", "sigma.y", "sigma.a","a0.femtrt","a0.femcontrol", "a0.maletrt", "a0.malecontrol", "b0.fem", "b0.male")
kestrel.out5<-bugs(kestrel.data, kestrel.out3$last.values, kestrel.parameters2, "gompertz bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=100100, n.burnin=100, debug=T)

Graph of parameter estimates: frequentist versus Bayesian

#fig 11a
#create a matrix of Bayesian results
sum.table1<-cbind((1:3)+.075, kestrel.out5$summary[2:4,"50%"], kestrel.out5$summary[2:4,"2.5%"], kestrel.out5$summary[2:4,"97.5%"], kestrel.out5$summary[2:4,"25%"], kestrel.out5$summary[2:4,"75%"])
#labels for y-axis
b.names<-c('male', 'control', expression('male' %*%'control'))
#expand left margin
par(mar=c(5.1,7.1,2.1,1.1))
#change bar ends to square
par(lend=2)
#set up plot
plot(range(sum.table1[,2:4]), c(1,3), type='n', axes=F, ylab='', xlab=expression(paste('Effect on ',a[0])), ylim=c(.5,3.75))
axis(1,cex.axis=.8)
axis(2,at=1:3, labels=b.names, cex.axis=.8, las=2)
#draw bayesian results
apply(sum.table1, 1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75'))
apply(sum.table1, 1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45'))
apply(sum.table1, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table1, 1, function(x) points(x[2], x[1], pch=1, cex=1.4))
box()
abline(v=0,col=2,lty=2)
mtext('Female, Treatment',at=0,side=3,line=.5,cex=.8)
#create a matrix of frequentist results
sumtable.nlme1<-cbind((1:3)-.075, summary(a0b0.intsex)$tTable[2:4,1], summary(a0b0.intsex)$tTable[2:4,2])
nlme.df<-summary(a0b0.intsex)$tTable[2,3]
#draw frequentist results
apply(sumtable.nlme1,1, function(x) segments(x[2]+qt(.025,nlme.df)*x[3], x[1], x[2]+qt(.975,nlme.df)*x[3], x[1], lwd=4, col='lightpink'))
apply(sumtable.nlme1,1, function(x) segments(x[2]+qt(.25,nlme.df)*x[3], x[1], x[2]+qt(.75,nlme.df)*x[3], x[1], lwd=6, col='red'))
apply(sumtable.nlme1,1,function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sumtable.nlme1,1, function(x) points(x[2], x[1], pch=1, cex=1.4))
legend(-80,3.85, c('Bayesian','frequentist'), col=c('grey45','red'), lty=1, lwd=4, bty='n', cex=.8)

#fig 11b
sum.table2<-c(1+.025, kestrel.out5$summary[5,"50%"], kestrel.out5$summary[5,"2.5%"], kestrel.out5$summary[5,"97.5%"], kestrel.out5$summary[5,"25%"], kestrel.out5$summary[5,"75%"])
sumtable.nlme3<-c(1-.025, summary(a0b0.intsex)$tTable[6,1], summary(a0b0.intsex)$tTable[6,2])
nlme.df<-summary(a0b0.intsex)$tTable[6,3]
b.names<-'male'
par(mar=c(5.1,5.1,2.1,1.1))
plot(c(min(c(sum.table2[2:4], sumtable.nlme3[2]+ qt(.025,nlme.df)*sumtable.nlme3[3])), .10), c(.5,1.75), type='n', axes=F, ylab='', xlab=expression(paste('Effect on ', b[0])), ylim=c(.85,1.25))
axis(1,cex.axis=.8)
axis(2,at=1, labels=b.names, cex.axis=.8, las=2)
#Bayesian
x<-sum.table2
segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75')
segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45')
points(x[2],x[1],pch=16,col='white', cex=1.1)
points(x[2],x[1],pch=1, cex=1.2)
box()
abline(v=0,col=2,lty=2)
mtext('Female',at=0,side=3,line=.5,cex=.8)
#frequentist
x<-sumtable.nlme3
segments(x[2]+qt(.025,nlme.df)*x[3], x[1], x[2]+qt(.975,nlme.df)*x[3], x[1], lwd=4, col='lightpink')
segments(x[2]+qt(.25,nlme.df)*x[3], x[1], x[2]+qt(.75,nlme.df)*x[3], x[1], lwd=6, col='red')
points(x[2],x[1],pch=16,col='white', cex=1.1)
points(x[2],x[1],pch=1, cex=1.2)
legend(-1.3, 1.25, c('Bayesian', 'frequentist'), col=c('grey45', 'red'), lty=1, lwd=4, bty='n', cex=.8)
#reset line ends back to the default value
par(lend=0)

(a) smear graph 1 (b) smear graph 2
Fig. 11  Bayesian and frequentist estimates for the effect parameters. In (a) we have estimates for the two main effects, sex and treatment, and their interaction in the model for a0 and in (b) just the main effect of sex in the b0 equation. The y-axis is labeled by the effect that is represented. Recall that each "effect" is measured relative to the reference group, "female, treatment" (indicated as the vertical red dashed line at 0). Displayed are 95% confidence (credibility) intervals, 50% confidence (credibility) intervals, and point estimates (mean or median of posterior distribution).

Graph of the treatment-sex combination estimates: frequentist versus Bayesian

a0 fixed

For "male controls" the values of the predictors are sex = 1 and trtmnt = 1. Thus we have

level 2 male control

vector product

where 1 is the displayed vector of ones and vector alphahat is the vector of parameter estimates.

linear combination

where the elements c1, c2, c3, and c4 are typically real numbers and are referred to generically as scalars. In the formula for mean asymptote of the male controls we had c1 = 1, c2 = 1, c3 = 1, and c4 = 1.

sigma alpha

is the variance-covariance matrix of the parameter estimates vector alphahat, then the variance of cT alphahat is the following.

quadratic form

The expression on the right is called a quadratic form. Because we can extract the parameter variance-covariance matrix from a model object with R's vcov function, we can calculate the variance of any linear combination of parameter estimates we desire. Because we've estimated seven regression parameters (four for a0, two for b0, and one for b1), vcov(a0b0.intsex) returns a 7 × 7 covariance matrix. Thus our scalar vector c will contain 7 components. When we desire the estimate variance of a group mean for the b0 parameter, say, then necessarily the components of c corresponding to the a0 and b1 parameters should be set to zero.

#Fig. 12a
#extract derived quantities from WinBUGS output
sum.table<-cbind((1:4)+.075, kestrel.out5$summary[8:11,"50%"], kestrel.out5$summary[8:11,"2.5%"], kestrel.out5$summary[8:11,"97.5%"], kestrel.out5$summary[8:11,"25%"], kestrel.out5$summary[8:11,"75%"])
b.names<-c('female treatment', 'female control', 'male treatment', 'male control')
par(mar=c(5.1,7.1,2.1,1.1))
par(lend=2)
plot(range(sum.table[,2:4]), c(1,3), type='n', axes=F, ylab='', xlab=expression(paste('Estimate of ',a[0])), ylim=c(.5,4.75))
axis(1,cex.axis=.8)
axis(2,at=1:4, labels=b.names, cex.axis=.8, las=2)
#Bayesian results
apply(sum.table,1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75'))
apply(sum.table,1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45'))
apply(sum.table, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table, 1, function(x) points(x[2], x[1], pch=1, cex=1.4))
box()
#frequentist calculations
#linear combinations for means of treatment-sex combinations
a0.femtreat<-c(1,0,0,0,0,0,0) %*% fixef(a0b0.intsex)
a0.maletreat<-c(1,1,0,0,0,0,0) %*% fixef(a0b0.intsex)
a0.femcontrol<-c(1,0,1,0,0,0,0) %*% fixef(a0b0.intsex)
a0.malecontrol<-c(1,1,1,1,0,0,0) %*% fixef(a0b0.intsex)
#quadratic forms for variance extraction
sd.a0.femtreat<-sqrt(c(1,0,0,0,0,0,0) %*% vcov(a0b0.intsex)%*% c(1,0,0,0,0,0,0))
sd.a0.maletreat<-sqrt(c(1,1,0,0,0,0,0) %*% vcov(a0b0.intsex)%*% c(1,1,0,0,0,0,0))
sd.a0.femcontrol<-sqrt(c(1,0,1,0,0,0,0) %*% vcov(a0b0.intsex) %*% c(1,0,1,0,0,0,0))
sd.a0.malecontrol<-sqrt(c(1,1,1,1,0,0,0) %*% vcov(a0b0.intsex) %*% c(1,1,1,1,0,0,0))
sumtable.nlme<-cbind((1:4)-.075, c(a0.femtreat, a0.femcontrol, a0.maletreat, a0.malecontrol), c(sd.a0.femtreat, sd.a0.femcontrol, sd.a0.maletreat, sd.a0.malecontrol))
a0b0.intsex$fixDF$X[1]->nlme.df
#frequentist
apply(sumtable.nlme,1, function(x) segments(x[2]+qt(.025,nlme.df)*x[3], x[1], x[2]+qt(.975,nlme.df)*x[3], x[1], lwd=4, col='lightpink'))
apply(sumtable.nlme,1, function(x) segments(x[2]+qt(.25,nlme.df)*x[3], x[1], x[2]+qt(.75,nlme.df)*x[3], x[1], lwd=6, col='red'))
apply(sumtable.nlme, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sumtable.nlme, 1, function(x) points(x[2], x[1], pch=1, cex=1.4))
legend(40,4.85,c('Bayesian', 'frequentist'), col=c('grey45', 'red'), lty=1, lwd=4, bty='n', cex=.8)

#Fig. 12b
#extract derived quantities from WinBUGS output
sum.table4<-cbind((1:2)+.075, kestrel.out5$summary[12:13,"50%"], kestrel.out5$summary[12:13,"2.5%"], kestrel.out5$summary[12:13,"97.5%"], kestrel.out5$summary[12:13,"25%"], kestrel.out5$summary[12:13,"75%"])
#frequentist calculations
#linear combinations for means of treatment-sex combinations
b0.fem<-c(0,0,0,0,1,0,0) %*% fixef(a0b0.intsex)
b0.male<-c(0,0,0,0,1,1,0 )%*% fixef(a0b0.intsex)
#quadratic forms for variance extraction
sd.b0.fem<-sqrt(c(0,0,0,0,1,0,0) %*% vcov(a0b0.intsex) %*%c (0,0,0,0,1,0,0))
sd.b0.male<-sqrt(c(0,0,0,0,1,1,0) %*% vcov(a0b0.intsex) %*% c(0,0,0,0,1,1,0))
sumtable.nlme4<-cbind((1:2)-.075, c(b0.fem,b0.male), c(sd.b0.fem,sd.b0.male))
a0b0.intsex$fixDF$X[1]->nlme.df
b.names<-c('female', 'male')
par(mar=c(5.1,4.1,2.1,1.1))
par(lend=2)
plot(range(range(cbind(sum.table4[,2:4]), sumtable.nlme4[,2]+ qt(.025,nlme.df)* sumtable.nlme4[,3])), c(1,2), type='n', axes=F, ylab='', xlab=expression(paste('Estimate of ',b[0])), ylim=c(.65,2.9))
axis(1,cex.axis=.8)
axis(2,at=1:2, labels=b.names, cex.axis=.8, las=2)
#Bayesian graph
apply(sum.table4,1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75'))
apply(sum.table4,1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45'))
apply(sum.table4, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.1))
apply(sum.table4, 1, function(x) points(x[2], x[1], pch=1, cex=1.2))
box()
#frequentist graph
apply(sumtable.nlme4, 1, function(x) segments(x[2]+qt(.025,nlme.df)*x[3], x[1], x[2]+qt(.975,nlme.df)*x[3], x[1], lwd=4, col='lightpink'))
apply(sumtable.nlme4, 1, function(x) segments(x[2]+qt(.25,nlme.df)*x[3], x[1], x[2]+qt(.75,nlme.df)*x[3], x[1], lwd=6, col='red'))
apply(sumtable.nlme4, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.1))
apply(sumtable.nlme4, 1, function(x) points(x[2], x[1], pch=1, cex=1.2))
legend(3, 3, c('Bayesian', 'frequentist'), col=c('grey45', 'red'), lty=1, lwd=4, bty='n', cex=.8)
#reset line ends back to the default value.
par(lend=0)

 

(a) fig 12a (b) fig 12b
Fig. 12  Bayesian and frequentist estimates for the estimated group means. In (a) we have estimates of mean a0 for each combination of sex and treatment. In (b) we have the estimated mean b0 for the two sexes. Displayed are 95% confidence (credibility) intervals, 50% confidence (credibility) intervals, and point estimates (mean or median of the posterior distribution).

Using WinBUGS—gamma regression model 1

Parameterization of the Gamma Distribution

gamma mean

Thus we can eliminate the shape parameter, α, and rewrite it in terms of the mean and rate parameters, μ and β. This is one of two possible parameterizations we could use. We'll consider the second parameterization later.

Non-negativity Constraints on Parameters

a ~ dnorm(0, .001)I(0,)

Here the lower argument is set to zero. The fact that the upper argument is not specified means the upper bound is infinite. The results is that dnorm(0, .001)I(0,) forces negative values that are generated from the specified normal distribution to be censored as zeroes. The BUGS manual stresses that this construction does not formally yield a truncated probability distribution. Consequently it should not be used when truncation is truly desired.

Priors for Gamma Distribution Parameters

gamma variance

The choice of α = .001 and β = .001 yields a gamma distribution with mean = 1 and variance = 1000. Consequently these choices permit a very wide range of positive values of the parameter to be possible. Note: For the current problem this range turns out to be too wide as it led to occasional trap errors, so I used a slightly more informative dgamma(.01, .01) prior for the rate parameter β.

As β decreases, the variance increases and hence the precision decreases. Likewise when β increases the precision also increases. Thus when we use a prior for β that places most of its mass near the origin this is roughly equivalent to placing a prior on the precision that is concentrated near zero. Note: Although we haven't done so in this course, it is common to use a prior such as dgamma(.001, .001) to model the precision parameter in a normal distribution. See, e.g., McCarthy (2007).

Gompertz model gamma parameterization #1: "gompertz gamma 1 bugs.txt"
model{
#gamma likelihood--level 1 model
for(i in 1:n) {
y[i]~dgamma(alpha[i],beta)
alpha[i] <- y.hat[i]*beta
#Gompertz mean
y.hat[i]<-a0[nest[i]]*exp(-b0[nest[i]]*pow(b1[nest[i]],age[i]))
}
#priors for level-1 parameters
beta ~ dgamma(.01,.01)
#level-2 model for Gompertz parameters
for(j in 1:J){
#the next use of I(0,) is technically incorrect!
a0[j]~dnorm(a.hat[j],tau.a)I(0,)
a.hat[j]<-mu.a+g1*sex[j]+g2*trt[j]+g3*sextrt[j]
b0[j]<-mu.b0+g4*sex[j]
b1[j]<-mu.b1
}
#priors for level-2 parameters
mu.a~dnorm(0,.0001)I(0,)
tau.a<-pow(sigma.a,-2)
sigma.a~dunif(0,1000)
mu.b0~dnorm(0,.0001)I(0,)
mu.b1~dnorm(0,.0001)I(0,)
g1~dnorm(0,.0001)
g2~dnorm(0,.0001)
g3~dnorm(0,.0001)
g4~dnorm(0,.0001)
#extra quantities
a0.femtrt<-mu.a
a0.femcontrol<-mu.a+g2
a0.maletrt<-mu.a+g1
a0.malecontrol<-mu.a+g1+g2+g3
b0.fem<-mu.b0
b0.male<-mu.b0+g4
}
kestrel.data<-list("y","age","n","nest","J","sex","trt","sextrt")
kestrel.g.inits<-function() {list(a0=runif(J,50,250), mu.a=runif(1,50,250), mu.b0=runif(1,1,2), mu.b1=runif(1), beta=runif(1), sigma.a=runif(1), g1=rnorm(1), g2=rnorm(1), g3=rnorm(1), g4=rnorm(1))}
kestrel.g.parameters<-c("a0","mu.a","mu.b0","mu.b1", "beta", "sigma.a", "g1", "g2", "g3", "g4")
kestrel.g.out2<-bugs(kestrel.data, kestrel.g.inits, kestrel.g.parameters, "gompertz gamma 1 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=10, debug=T)
kestrel.g.out2<-bugs(kestrel.data, kestrel.g.inits, kestrel.g.parameters, "gompertz gamma 1 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=1000, debug=T)
max(kestrel.g.out2$summary[,"Rhat"])
[1] 1.038671
kestrel.g.out3<-bugs(kestrel.data, kestrel.g.out2$last.values, kestrel.g.parameters, "gompertz gamma 1 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=10000, debug=T)
max(kestrel.g.out3$summary[,"Rhat"])
[1] 1.037423
min(kestrel.g.out3$summary[,"n.eff"])
[1] 93
kestrel.g.parameters2<-c("a0","mu.a","mu.b0","mu.b1", "beta", "sigma.a", "g1", "g2", "g3", "g4","a0.femtrt","a0.femcontrol", "a0.maletrt", "a0.malecontrol", "b0.fem", "b0.male")
kestrel.g.out4<-bugs(kestrel.data, kestrel.g.out3$last.values, kestrel.g.parameters2, "gompertz gamma 1 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=50100, n.burnin=100, debug=T)
max(kestrel.g.out4$summary[,"Rhat"])
[1] 1.012623
min(kestrel.g.out4$summary[,"n.eff"])
[1] 160
results<-data.frame(c('normal', 'gamma'), c(kestrel.out5$summary["deviance","50%"], kestrel.g.out4$summary["deviance","50%"]), c(kestrel.out5$DIC, kestrel.g.out4$DIC), c(kestrel.out5$pD, kestrel.g.out4$pD))
colnames(results)<-c('model', 'deviance', 'DIC', 'pD')
results
   model deviance     DIC     pD
1 normal    558.6 579.134 19.415
2  gamma    552.3 570.948 17.540

Graph of parameter estimate: gamma 1 versus normal

#fig 13a
#create a matrix of Bayesian normal results
gnames<-c("g1","g2","g3")
sum.table1<-cbind((1:3)+.075, kestrel.out5$summary[gnames,"50%"], kestrel.out5$summary[gnames,"2.5%"], kestrel.out5$summary[gnames,"97.5%"], kestrel.out5$summary[gnames,"25%"], kestrel.out5$summary[gnames,"75%"])
#create a matrix of Bayesian gamma results
sum.table.g1<-cbind((1:3)-.075, kestrel.g.out4$summary[gnames,"50%"], kestrel.g.out4$summary[gnames,"2.5%"], kestrel.g.out4$summary[gnames,"97.5%"], kestrel.g.out4$summary[gnames,"25%"], kestrel.g.out4$summary[gnames,"75%"])
#labels for y-axis
b.names<-c('male', 'control', expression('male' %*%'control'))
#expand left margin
par(mar=c(5.1,7.1,2.1,1.1))
#change bar ends to square
par(lend=2)
#set up plot
plot(range(cbind(sum.table1[,2:4],sum.table.g1[,2:4])), c(1,3), type='n', axes=F, ylab='', xlab=expression(paste('Effect on ',a[0])), ylim=c(.5,3.75))
axis(1,cex.axis=.8)
axis(2,at=1:3, labels=b.names, cex.axis=.8, las=2)
#draw bayesian-normal results
apply(sum.table1, 1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75'))
apply(sum.table1, 1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45'))
apply(sum.table1, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table1, 1, function(x) points(x[2], x[1], pch=1, cex=1.4))
box()
abline(v=0,col=2,lty=2)
mtext('Female, Treatment',at=0,side=3,line=.5,cex=.8)
#draw bayesian gamma results
apply(sum.table.g1, 1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='lightpink'))
apply(sum.table.g1, 1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='red'))
apply(sum.table.g1,1,function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table.g1,1, function(x) points(x[2],x[1], pch=1, cex=1.4))
legend(-80,3.85, c('Bayesian-normal','Bayesian-gamma'), col=c('grey45','red'), lty=1, lwd=4, bty='n', cex=.8)

#fig 13b
sum.table1b<-c(1+.025, kestrel.out5$summary["g4","50%"], kestrel.out5$summary["g4","2.5%"], kestrel.out5$summary["g4","97.5%"], kestrel.out5$summary["g4","25%"], kestrel.out5$summary["g4","75%"])
sum.table.g1b<-c(1-.025, kestrel.g.out4$summary["g4","50%"], kestrel.g.out4$summary["g4","2.5%"], kestrel.g.out4$summary["g4","97.5%"], kestrel.g.out4$summary["g4","25%"], kestrel.g.out4$summary["g4","75%"])
b.names<-'male'
par(mar=c(5.1,5.1,2.1,1.1))
plot(c(min(c(sum.table1b[2:4], sum.table.g1b[2:4])), .10), c(.5,1.75), type='n', axes=F, ylab='', xlab=expression(paste('Effect on ', b[0])), ylim=c(.85,1.25))
axis(1,cex.axis=.8)
axis(2,at=1, labels=b.names, cex.axis=.8, las=2)
#Bayesian normal
x<-sum.table2
segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75')
segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45')
points(x[2],x[1],pch=16,col='white', cex=1.1)
points(x[2],x[1],pch=1, cex=1.2)
box()
abline(v=0,col=2,lty=2)
mtext('Female',at=0,side=3,line=.5,cex=.8)
#Bayesian gamma
x<-sum.table.g1b
segments(x[3], x[1], x[4], x[1], lwd=4, col='lightpink')
segments(x[5], x[1], x[6], x[1], lwd=6, col='red')
points(x[2],x[1],pch=16,col='white', cex=1.1)
points(x[2],x[1],pch=1, cex=1.2)
legend(-1.3, 1.25, c('Bayesian-normal', 'Bayesian-gamma'), col=c('grey45', 'red'), lty=1, lwd=4, bty='n', cex=.8)
#reset line ends back to the default value.
par(lend=0)
(a) gamma errors 1a (b) gamma errors 2
Fig. 13  Bayesian estimates for the effect parameters for different probability models: normal and gamma. In (a) we have estimate for the two main effects, sex and treatment, and their interaction in the model for a0 and in (b) just the main effect of sex in the b0 equation. The y-axis is labeled by the effect that is represented by that estimate relative to the reference group, "female, treatment group" (indicated at the top of the graph labeling an effect of zero). Displayed are 95% confidence (credibility) intervals, 50% confidence (credibility) intervals, and point estimates (mean or median of posterior distribution).

Using WinBUGS—gamma regression model 2

gamma parameterization 2

Gompertz model gamma parameterization #2: "gompertz gamma 2 bugs.txt"
model{
#gamma likelihood--level 1 model
for(i in 1:n) {
y[i]~dgamma(alpha,beta[i])
beta[i] <- alpha / y.hat[i]
#Gompertz mean
y.hat[i]<-a0[nest[i]]*exp(-b0[nest[i]]*pow(b1[nest[i]],age[i]))
}
#priors for level-1 parameters
logalpha ~ dnorm(0,.000001)
alpha<-exp(logalpha)
#level-2 model for Gompertz parameters
for(j in 1:J){
#the next use of I(0,) is technically incorrect!
a0[j]~dnorm(a.hat[j],tau.a)I(0,)
a.hat[j]<-mu.a+g1*sex[j]+g2*trt[j]+g3*sextrt[j]
b0[j]<-mu.b0+g4*sex[j]
b1[j]<-mu.b1
}
#priors for level-2 parameters
mu.a~dnorm(0,.0001)I(0,)
tau.a<-pow(sigma.a,-2)
sigma.a~dunif(0,1000)
mu.b0~dnorm(0,.0001)I(0,)
mu.b1~dnorm(0,.0001)I(0,)
g1~dnorm(0,.0001)
g2~dnorm(0,.0001)
g3~dnorm(0,.0001)
g4~dnorm(0,.0001)
#extra quantities
a0.femtrt<-mu.a
a0.femcontrol<-mu.a+g2
a0.maletrt<-mu.a+g1
a0.malecontrol<-mu.a+g1+g2+g3
b0.fem<-mu.b0
b0.male<-mu.b0+g4
}
kestrel.data<-list("y","age","n","nest","J","sex","trt","sextrt")
kestrel.g2.inits<-function() {list(a0=runif(J,50,250), mu.a=runif(1,50,250), mu.b0=runif(1,1,2), mu.b1=runif(1), alpha=runif(1), sigma.a=runif(1), g1=rnorm(1), g2=rnorm(1), g3=rnorm(1), g4=rnorm(1))}
kestrel.g2.parameters<-c("a0","mu.a","mu.b0","mu.b1", "alpha", "sigma.a", "g1", "g2", "g3", "g4")
kestrel.g2.out2<-bugs(kestrel.data, kestrel.g2.inits, kestrel.g2.parameters, "gompertz gamma 2 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=10, debug=T)
kestrel.g2.out2<-bugs(kestrel.data, kestrel.g2.inits, kestrel.g2.parameters, "gompertz gamma 2 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=1000, debug=T)
max(kestrel.g2.out2$summary[,"Rhat"])
[1] 5.294687
kestrel.g2.out3<-bugs(kestrel.data, kestrel.g2.out2$last.values, kestrel.g2.parameters, "gompertz gamma 2 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=10000, debug=T)
max(kestrel.g2.out3$summary[,"Rhat"])
[1] 1.045594
min(kestrel.g2.out3$summary[,"n.eff"])
[1] 61
kestrel.g2.out4<-bugs(kestrel.data, kestrel.g2.out3$last.values, kestrel.g2.parameters, "gompertz gamma 2 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=20000, n.burnin=100, debug=T)
max(kestrel.g2.out4$summary[,"Rhat"])
[1] 1.093501
min(kestrel.g2.out4$summary[,"n.eff"])
[1] 27
kestrel.g2.parameters2<-c("a0","mu.a","mu.b0","mu.b1", "alpha", "sigma.a", "g1", "g2", "g3", "g4","a0.femtrt","a0.femcontrol", "a0.maletrt", "a0.malecontrol", "b0.fem", "b0.male")
kestrel.g2.out5<-bugs(kestrel.data, kestrel.g2.out4$last.values, kestrel.g2.parameters2, "gompertz gamma 2 bugs.txt", n.chains=3, bugs.directory="C:/Program Files/WinBUGS14/", n.iter=50100, n.burnin=100, debug=T)
max(kestrel.g2.out5$summary[,"Rhat"])
[1] 1.008313
min(kestrel.g2.out5$summary[,"n.eff"])
[1] 260
results<-data.frame(c('normal', 'gamma 1','gamma 2'), c(kestrel.out5$summary["deviance","50%"], kestrel.g.out3$summary["deviance","50%"], kestrel.g2.out5$summary["deviance","50%"]), c(kestrel.out5$DIC, kestrel.g.out3$DIC, kestrel.g2.out5$DIC), c(kestrel.out5$pD, kestrel.g.out3$pD, kestrel.g2.out5$pD))
colnames(results)<-c('model', 'deviance', 'DIC', 'pD')
results
    model deviance     DIC     pD
1  normal    558.6 579.134 19.415
2 gamma 1    552.7 571.336 17.719
3 gamma 2    555.2 566.327 11.131

Comparing the estimates from the normal, gamma 1, and gamma 2 models

#fig 14a -- a0 Gompertz parameter
#create a matrix of Bayesian normal results
gnames<-c("g1","g2","g3")
sum.table1<-cbind((1:3)+.125, kestrel.out5$summary[gnames,"50%"], kestrel.out5$summary[gnames,"2.5%"], kestrel.out5$summary[gnames,"97.5%"], kestrel.out5$summary[gnames,"25%"], kestrel.out5$summary[gnames,"75%"])
#create a matrix of Bayesian gamma 1 results
sum.table.g1<-cbind((1:3), kestrel.g.out4$summary[gnames,"50%"], kestrel.g.out4$summary[gnames,"2.5%"], kestrel.g.out4$summary[gnames,"97.5%"], kestrel.g.out4$summary[gnames,"25%"], kestrel.g.out4$summary[gnames,"75%"])
#create a matrix of Bayesian gamma 2 results
sum.table.g2<-cbind((1:3)-.125, kestrel.g2.out5$summary[gnames,"50%"], kestrel.g2.out5$summary[gnames,"2.5%"], kestrel.g2.out5$summary[gnames,"97.5%"], kestrel.g2.out5$summary[gnames,"25%"], kestrel.g2.out5$summary[gnames,"75%"])
#labels for y-axis
b.names<-c('male', 'control', expression('male' %*%'control'))
#expand left margin
par(mar=c(5.1,7.1,2.1,1.1))
#change bar ends to square
par(lend=2)
#set up plot
plot(range(cbind(sum.table1[,2:4],sum.table.g1[,2:4], sum.table.g2[,2:4])), c(1,3), type='n', axes=F, ylab='', xlab=expression(paste('Effect on ',a[0])), ylim=c(.5,3.75))
axis(1,cex.axis=.8)
axis(2,at=1:3, labels=b.names, cex.axis=.8, las=2)
#draw bayesian-normal results
apply(sum.table1, 1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75'))
apply(sum.table1, 1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45'))
apply(sum.table1, 1, function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table1, 1, function(x) points(x[2], x[1], pch=1, cex=1.4))
box()
abline(v=0,col=2,lty=2)
mtext('Female, Treatment',at=0,side=3,line=.5,cex=.8)
#draw bayesian gamma 1 results
apply(sum.table.g1, 1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='lightpink'))
apply(sum.table.g1, 1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='red'))
apply(sum.table.g1,1,function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table.g1,1, function(x) points(x[2],x[1], pch=1, cex=1.4))
#draw bayesian gamma 2 results
apply(sum.table.g2, 1, function(x) segments(x[3], x[1], x[4], x[1], lwd=4, col='lightblue'))
apply(sum.table.g2, 1, function(x) segments(x[5], x[1], x[6], x[1], lwd=6, col='blue'))
apply(sum.table.g2,1,function(x) points(x[2], x[1], pch=16, col='white', cex=1.3))
apply(sum.table.g2,1, function(x) points(x[2],x[1], pch=1, cex=1.4))
legend(-80,3.85, c('normal','gamma 1' ,'gamma 2'), col=c('grey45','red', 'blue'), lty=1, lwd=4, bty='n', cex=.8)

#fig 14b -- b0 Gompertz parameter
sum.table1b<-c(1+.05, kestrel.out5$summary["g4","50%"], kestrel.out5$summary["g4","2.5%"], kestrel.out5$summary["g4","97.5%"], kestrel.out5$summary["g4","25%"], kestrel.out5$summary["g4","75%"])
sum.table.g1b<-c(1, kestrel.g.out4$summary["g4","50%"], kestrel.g.out4$summary["g4","2.5%"], kestrel.g.out4$summary["g4","97.5%"], kestrel.g.out4$summary["g4","25%"], kestrel.g.out4$summary["g4","75%"])
sum.table.g2b<-c(1-.05, kestrel.g2.out5$summary["g4","50%"], kestrel.g2.out5$summary["g4","2.5%"], kestrel.g2.out5$summary["g4","97.5%"], kestrel.g2.out5$summary["g4","25%"], kestrel.g2.out5$summary["g4","75%"])
b.names<-'male'
par(mar=c(5.1,5.1,2.1,1.1))
plot(c(min(c(sum.table1b[2:4], sum.table.g1b[2:4], sum.table.g2b[2:4])), .10), c(.5,1.75), type='n', axes=F, ylab='', xlab=expression(paste('Effect on ', b[0])), ylim=c(.85,1.25))
axis(1, cex.axis=.8)
axis(2, at=1, labels=b.names, cex.axis=.8, las=2)
#Bayesian normal
x<-sum.table1b
segments(x[3], x[1], x[4], x[1], lwd=4, col='grey75')
segments(x[5], x[1], x[6], x[1], lwd=6, col='grey45')
points(x[2], x[1], pch=16, col='white', cex=1.1)
points(x[2], x[1], pch=1, cex=1.2)
box()
abline(v=0, col=2, lty=2)
mtext('Female', at=0, side=3, line=.5, cex=.8)
#Bayesian gamma 1
x<-sum.table.g1b
segments(x[3], x[1], x[4], x[1], lwd=4, col='lightpink')
segments(x[5], x[1], x[6], x[1], lwd=6, col='red')
points(x[2], x[1], pch=16, col='white', cex=1.1)
points(x[2], x[1], pch=1, cex=1.2)
#Bayesian gamma 2
x<-sum.table.g2b
segments(x[3], x[1], x[4], x[1], lwd=4, col='lightblue')
segments(x[5], x[1], x[6], x[1], lwd=6, col='blue')
points(x[2], x[1], pch=16, col='white', cex=1.1)
points(x[2], x[1], pch=1, cex=1.2)
legend(-1.3, 1.25, c('normal', 'gamma 1', 'gamma 2'), col=c('grey45', 'red', 'blue'), lty=1, lwd=4, bty='n', cex=.8)
#reset line ends back to the default value
par(lend=0)
(a) fig 14a (b) fig 14b
Fig. 14  Bayesian estimates for the effect parameters for three different probability models: a normal and two parameterizations of the gamma distribution. In (a) we have estimates for the two main effects of sex and treatment as well as their interaction in the equation for the a0 Gompertz parameter and in (b) we have just the main effect of sex in the b0 equation. The y-axis is labeled by the effect that is represented by that estimate relative to the reference group (indicated at the top of the graph with an effect of zero). Displayed are 95% confidence (credibility) intervals, 50% confidence (credibility) intervals, and point estimates (median of posterior distribution).
cbind(kestrel.out4$summary[c("mu.a", "mu.b0", "mu.b1", "g1", "g2", "g3", "g4"), "50%"], kestrel.g.out4$summary[c("mu.a", "mu.b0", "mu.b1", "g1", "g2", "g3", "g4"), "50%"], kestrel.g2.out5$summary[c("mu.a", "mu.b0", "mu.b1", "g1", "g2", "g3", "g4"), "50%"]) -> models.out
colnames(models.out)<-c('normal', 'gamma 1', 'gamma 2')
models.out

         normal  gamma 1  gamma 2
mu.a  110.10000 127.5500 157.1000
mu.b0   2.99500   2.6175   2.7365
mu.b1   0.86440   0.8961   0.9132
g1    -46.49500 -49.9300 -52.2500
g2     11.37000  12.7400  10.8550
g3     30.69000  31.4850  29.9800
g4     -0.66755  -0.4209  -0.3129

Comparing the predictions from the normal, gamma 1, and gamma 2 models

#number of observations per nest
apply(table(kestrel3$nest, kestrel3$age),1,sum) -> num.ages
#obtain one value of sex and trtmnt per nest
tapply(kestrel3$sex, kestrel3$nest, function(x) x[1])-1 -> unique.sex
tapply(kestrel3$trtmnt, kestrel3$nest, function(x) x[1])-1 -> unique.trt
#collect nest-level values in single data frame
model.dat<-data.frame(as.numeric(names(num.ages)), num.ages, unique.trt, unique.sex, kestrel.g2.out5$summary[1:18,'50%'], kestrel.g.out4$summary[1:18,'50%'], kestrel.out4$summary[1:18,'50%'])
colnames(model.dat)<-c('nest', 'num.ages', 'trtmnt', 'sex', 'G2.a0', 'G1.a0', 'N.a0')
#Gompertz parameters for gamma 2 model
model.dat$G2.b0<-kestrel.g2.out5$summary['mu.b0','50%']+ kestrel.g2.out5$summary['g4','50%']*model.dat$sex
model.dat$G2.b1<-kestrel.g2.out5$summary['mu.b1','50%']
#Gompertz parameters for gamma 1 model
model.dat$G1.b0<-kestrel.g.out4$summary['mu.b0','50%']+ kestrel.g.out4$summary['g4','50%']*model.dat$sex
model.dat$G1.b1<-kestrel.g.out4$summary['mu.b1','50%']
#Gompertz parameters for normal model
model.dat$N.b0<-kestrel.out4$summary['mu.b0','50%']+ kestrel.out4$summary['g4','50%']*model.dat$sex
model.dat$N.b1<-kestrel.out4$summary['mu.b1','50%']
#combine nest-level data with full data set
model.dat.short<-model.dat[,c(1:2,5:(ncol(model.dat)))]
kestrel3.model<-merge(kestrel3, model.dat.short)
library(lattice)
xyplot(nstlmass~age|factor(nest), data=kestrel3.model, xlab='Age', ylab='Nestling Mass', subscripts=T, panel=function(x,y,subscripts) {
#raw data
panel.xyplot(x,y,pch=16)
#normal Gompertz curve
panel.curve(SSgompertz(x, kestrel3.model$N.a0[subscripts][1], kestrel3.model$N.b0[subscripts][1], kestrel3.model$N.b1[subscripts][1]), lwd=2, col='gray60')
#gamma 1 Gompertz curve
panel.curve(SSgompertz(x, kestrel3.model$G1.a0[subscripts][1], kestrel3.model$G1.b0[subscripts][1], kestrel3.model$G1.b1[subscripts][1]), lwd=2, col='red', lty=2)
#gamma 2 Gompertz curve
panel.curve(SSgompertz(x, kestrel3.model$G2.a0[subscripts][1], kestrel3.model$G2.b0[subscripts][1], kestrel3.model$G2.b1[subscripts][1]), lwd=1, col='blue', lty=1)
},
key=list(lines=list(lty=c(1,2,1), col=c('gray60',2,4), lwd=c(2,2,1)), text=list(c('Normal','Gamma 1', 'Gamma 2'), cex=c(.75,.75,.75)), border=0, x=.8, y=.85, corner=c(0,0)))

fig. 15

Fig. 15  Comparing fitted subject-specific Gompertz curves from different probability models

Comparing the estimated normal, gamma 1, and gamma 2 probability distributions

#get a list of nests with 5 or more observations
my.nests<- unique(kestrel3.model.short[kestrel3.model.short$num.ages>=5, "nest"])
#function for drawing probability distributions
nest.function<-function(i) {
model.nest<-kestrel3.model[kestrel3.model$nest==my.nests[i],]
histogram(~nstlmass|factor(age), data=model.nest, type='density', xlab='Nestling Mass', subscripts=T, main=paste('Nest ', my.nests[i]), xlim=c(-20,170), ylim=c(-.002, .1), panel=function(x,subscripts) {
#normal distribution
panel.curve(dnorm(x,mean=SSgompertz( model.nest$age[subscripts][1], model.nest$N.a0[subscripts][1], model.nest$N.b0[subscripts][1], model.nest$N.b1[subscripts][1]), sd=kestrel.out4$summary["sigma.y",'50%']), lwd=2, col='gray60')
#gamma 1 distribution
panel.curve(dgamma(x,SSgompertz(model.nest$age[subscripts][1], model.nest$G1.a0[subscripts][1], model.nest$G1.b0[subscripts][1], model.nest$G1.b1[subscripts][1])* kestrel.g.out4$summary['beta','50%'], kestrel.g.out4$summary['beta','50%']), lwd=2, col='red', lty=2)
#gamma 2 distribution
panel.curve(dgamma(x, kestrel.g2.out5$summary['alpha', '50%'], kestrel.g2.out5$summary['alpha','50%']/ SSgompertz(model.nest$age[subscripts][1], model.nest$G2.a0[subscripts][1], model.nest$G2.b0[subscripts][1], model.nest$G2.b1[subscripts][1])), lwd=1, col='blue', lty=1)
#raw data
panel.points(x, 0, pch=15)
}, par.settings = list(par.main.text = list(cex = 1)), key=list(columns=3, lines=list(lty=c(1,2,1), col=c('gray60',2,4), lwd=c(2,2,1)), text=list(c('Normal', 'Gamma 1', 'Gamma 2'), cex=c(.75,.75,.75))))
}
#draw graph for ninth nest in the list
nest.function(9)
fig. 16
Fig. 16  Probability distributions for nestling mass. The label appearing at the top of a panel is the age of the nestling. The blue square at the bottom of each panel indicates the observed mass of the nestling.

gamma 1 var

Using SAS to Reproduce the Results from nlme and WinBUGS

Preparing the data for SAS

setwd('D:/Ecology 563 Fall 2008/lecture29')
kestrel3<- kestrel2[!is.na(kestrel2$sex),]
for.sas<- kestrel3[,c("nest", "age", "nstlmass")]
for.sas$trtmnt<- as.numeric(kestrel3$trtmnt)-1
for.sas$sex<- as.numeric(kestrel3$sex)-1
write.table(for.sas, 'kestrelsas.csv', sep=',', row.names=F)

sas screen

Fig. 17  Start-up screen for SAS 9.1

PROC IMPORT OUT= WORK.KESTREL2
            DATAFILE= "D:\Ecology 563 Fall 2008\lecture29\kestrelsas.csv"
            DBMS=CSV REPLACE;
      GETNAMES=YES;
   DATAROW
=2;
RUN;

proc contents data=kestrel2;
run;

editor window

Fig. 18  SAS Editor window with SAS code

submit button

Fig. 19  Location of the submit button on the SAS toolbar

contents procedure

Fig. 20  Output from the CONTENTS procedure

Running a nonlinear mixed effects model in SAS

*normal model;
proc nlmixed data=kestrel2;
parms  a0=127 a0gen=-49.9 a0trt=12 a0int=34.5
b0=2.56 b0gen=-.93 b1=.8 s2u1=225  s2e=10 ;
a0ind=a0+a0gen*sex+a0trt*trtmnt+a0int*sex*trtmnt+u1; *individual a0;
b0ind=b0+b0gen*sex; *individual b0;
b1ind=b1; *individual b1;
eta=a0ind*exp(-b0ind*b1**age); *Gompertz formula;
model nstlmass~normal(eta,s2e);
random u1~normal(0,s2u1) subject=nest;
run;

   Iter     Calls    NegLogLike        Diff     MaxGrad       Slope

     29        58    291.025945    3.009E-6    0.051791    -7.52E-7

             NOTE: GCONV convergence criterion satisfied.

                                    Fit Statistics

                       -2 Log Likelihood                  582.1
                       AIC (smaller is better)            600.1
                       AICC (smaller is better)           602.6
                       BIC (smaller is better)            608.1

                                 Parameter Estimates

                       Standard
  Parameter  Estimate     Error    DF  t Value  Pr > |t|   Alpha     Lower     Upper  Gradient

  a0           112.89   10.3727    17    10.88    <.0001    0.05   91.0018    134.77  0.001503
  a0gen      -51.3346   13.2582    17    -3.87    0.0012    0.05  -79.3069  -23.3623  -0.00371
  a0trt        8.3383   11.4471    17     0.73    0.4763    0.05  -15.8129   32.4895  -0.00229
  a0int       37.1117   16.5013    17     2.25    0.0381    0.05    2.2970   71.9264  0.005007
  b0           2.9813    0.2419    17    12.33    <.0001    0.05    2.4711    3.4916  0.005889
  b0gen       -0.7148    0.2518    17    -2.84    0.0113    0.05   -1.2460   -0.1835  0.003008
  b1           0.8657   0.01098    17    78.87    <.0001    0.05    0.8425    0.8889  -0.05179
  s2u1         224.70    128.86    17     1.74    0.0993    0.05  -47.1803    496.58  0.004316
  s2e         53.7607    9.7246    17     5.53    <.0001    0.05   33.2435   74.2778  -0.00016

Running a nonlinear mixed effects gamma regression model in SAS

sas gamma mean

(1a)


(1b)

So, as before we have two different ways to include the mean as an explicit parameter in the gamma distribution corresponding to what we called the gamma 1 and the gamma 2 model before when we fit this model in WinBUGS.

gamma probability sas

with the corresponding log-likelihood.

gamma loglike sas

marginal loglik

(2)

gompertz mean

*gamma 2 model using gamma function;
proc nlmixed data=kestrel2;
parms  a0=127 a0gen=-49.9 a0trt=12 a0int=34.5
b0=2.56 b0gen=-.93 b1=.8 s2u1=225  logalpha=-1 ;
alpha=exp(logalpha); *gamma shape parameter constrained to be positive;
a0ind=a0+a0gen*sex+a0trt*trtmnt+a0int*sex*trtmnt+u1; *individual a0;
b0ind=b0+b0gen*sex; *individual b0;
b1ind=b1; *individual b1;
eta=a0ind*exp(-b0ind*b1**age); *Gompertz formula for the mean;
phi=eta/alpha; *gamma 2 model substitution;
model nstlmass~gamma(alpha,phi);
random u1~normal(0,s2u1) subject=nest;
ods output ParameterEstimates=parmests; *extract parameter estimates;
ods output FitStatistics=fits; *extract logLik and AIC;
run;
*gamma 2 model using log-likelihood
proc nlmixed data=kestrel2;
parms  a0=127 a0gen=-49.9 a0trt=12 a0int=34.5
b0=2.56 b0gen=-.93 b1=.8 s2u1=225  logalpha=-1 ;
alpha=exp(logalpha); *gamma shape parameter constrained to be positive;
a0ind=a0+a0gen*sex+a0trt*trtmnt+a0int*sex*trtmnt+u1; *individual a0;
b0ind=b0+b0gen*sex; *individual b0;
b1ind=b1; *individual b1;
eta=a0ind*exp(-b0ind*b1**age); *Gompertz formula for the mean;
phi=eta/alpha; *gamma 2 model substitution;
yvar=nstlmass;*response variable;
ll=(alpha-1)*log(yvar)- alpha*yvar/eta-lgamma(alpha) + alpha*log(alpha)- alpha*log(eta);*gamma 2 log-likelihood;
model yvar~general(ll);
random u1~normal(0,s2u1) subject=nest;
ods output ParameterEstimates=parmests; *extract parameter estimates;
ods output FitStatistics=fits; *extract logLik and AIC;
run;

   Iter     Calls    NegLogLike        Diff     MaxGrad       Slope

     39        97    273.370747    3.943E-7    0.006569    -9.62E-7

             NOTE: GCONV convergence criterion satisfied.

                                    Fit Statistics

                       -2 Log Likelihood                  546.7
                       AIC (smaller is better)            564.7
                       AICC (smaller is better)           567.3
                       BIC (smaller is better)            572.8

                                 Parameter Estimates

                       Standard
  Parameter  Estimate     Error    DF  t Value  Pr > |t|   Alpha     Lower     Upper  Gradient

  a0           154.37   19.8155    17     7.79    <.0001    0.05    112.56    196.17  -0.00001
  a0gen      -53.0026   15.3797    17    -3.45    0.0031    0.05  -85.4509  -20.5542  -3.87E-6
  a0trt        8.4424    9.3765    17     0.90    0.3805    0.05  -11.3403   28.2252  6.693E-6
  a0int       30.5906   12.6521    17     2.42    0.0271    0.05    3.8970   57.2842  9.836E-7
  b0           2.7156    0.1284    17    21.16    <.0001    0.05    2.4448    2.9864  0.000913
  b0gen       -0.7148    0.2518    17    -2.84    0.0113    0.05   -1.2460   -0.1835  0.003008
  b1           0.9110  0.007970    17   114.31    <.0001    0.05    0.8942    0.9278  0.006569
  s2u1        11.0982   49.6237    17     0.22    0.8257    0.05  -93.5987    115.80  -1.47E-6
  logalpha     3.3921    0.1724    17    19.67    <.0001    0.05    3.0284    3.7559  0.000099

Returning SAS output to R

proc print data=parmests;
run;
proc print data=fits;
run;
PROC EXPORT DATA = WORK.PARMESTS
   OUTFILE= "D:\Ecology 563 Fall 2008\lecture29\sas gamma ests.csv"
   DBMS=CSV REPLACE;
RUN;
PROC EXPORT DATA = WORK.FITS
   OUTFILE= "D:\Ecology 563 Fall 2008\lecture29\sas gamma ests.csv"
   DBMS=CSV REPLACE;
RUN;
#back in R
setwd('D:/Ecology 563 Fall 2008/lecture29')
sas.parms<-read.table('sas gamma ests.csv', header=T, sep=',')
sas.fits<-read.table('sas gamma fits.csv', header=T, sep=',')
sas.fits
                     Descr Value
1        -2 Log Likelihood 546.7
2  AIC (smaller is better) 564.7
3 AICC (smaller is better) 567.3
4  BIC (smaller is better) 572.8

dim(sas.parms)
[1]  9 10
sas.parms[1:4,]
  Parameter Estimate StandardError DF tValue  Probt Alpha    Lower    Upper   Gradient
1        a0 154.3700       19.8155 17   7.79 <.0001  0.05 112.5600 196.1700 -1.000e-05
2     a0gen -53.0026       15.3797 17  -3.45 0.0031  0.05 -85.4509 -20.5542 -3.870e-06
3     a0trt   8.4424        9.3765 17   0.90 0.3805  0.05 -11.3403  28.2252  6.693e-06
4     a0int  30.5906       12.6521 17   2.42 0.0271  0.05   3.8970  57.2842  9.836e-07
c(sapply(list(a0b0.intsex, var2.gnls),AIC), sas.fits[2,2])
600.0308 556.3585 564.7000

Using AIC to Compare Mixed Effect Models with Transformed and Untransformed Responses

Normal mixed effects model log-likelihood with a transformed response variable

laird ware.

This is the usual Laird-Ware formulation where X is the design matrix for the fixed effects portion of the model, Z is the design matrix for the random effects portion, β is the vector of fixed coefficients, and b is the vector of random coefficients. We assume that

  1. ranef dist and
  2. level 1 var 

with b independent of ε.

multivariate normal.

(3)

Here det(sigma) denotes the determinant of the matrix Σ. Let errors in the mixed effects model equation mixed effects model. Because e is the sum of two independent normal random variables each with mean 0, it follows that e has a (multivariate) normal distribution with mean 0 and covariance matrix obtained as follows.

variance derivation

density

This joint density also is the likelihood of our model for the given data set. Using the notation likelihood notation for the likelihood, or more simply likelihood notation alt, the likelihood of the mixed effects model is given by eqn (4).

likelihood

(4)

scalar w likelihood.

We then showed that we could express this likelihood function solely in terms of the original variable y as follows.

scalar logy likelihood

The general result is that if T(y) has density function scalar density w, then the likelihood expressed in terms of the original variable y is the following.

transformation formula

(5)

transformation  equations

where conceivably T1 to Tn are different functions. The original distributionmultivariate y is unknown to us, but we do know that the transformed observations have joint density multivariate w. The general change-of-variables formula that allows us to rewrite this density in w as a density in terms of y is the following.

multivariate transformation  formula

(6)

Jacobian

Now in our case the transformations T1 to Tn are quite simple.

matrix of logs

So, the required Jacobian is easy to compute.

Jacobian of logs

(7)

Combining eqns (4), (6), and (7) we obtain the following expression for the likelihood of w = log y under the random effects model written in terms of the individual components of y.

final transformation

(8)

Implementing the log-likelihood transformation formula in R for a random intercepts model

Using eqn (8) directly

log-likelihood 2

(9a)

I set up the function I call trans.loglik so that it takes four arguments—three required and one optional. The arguments are the following.

  1. lme.model: the first argument is the lme object that is produced when a random intercepts model is fit using the lme function of the nlme package.
  2. lme.group: the second argument is the structural variable, the variable that defines how observations are grouped in the data set. This is the variable that was specified to the right of the vertical bar, |, in the random argument of lme.
  3. lme.y: the third argument is the untransformed response variable.
  4. log.tran: the fourth argument is a logical argument that indicates whether or not the response variable is log-transformed in the fitted lme model. The default setting for this argument is TRUE indicating that a log transformation was used when the model was fit.

I include the fourth argument as a check on the code. When the fourth argument is set to FALSE and the lme model specified in the first argument was fit to the raw response, then the trans.loglik function should agree with the values returned by the logLik and AIC functions when applied to the same model.

#loglik for a log-transformed normal random intercepts model
trans.loglik<-function(lme.model, lme.group, lme.y, log.tran=T)
{
#number of observations and number of groups
n.obs<-length(lme.group)
n.grp<-length(unique(lme.group))
#extract variance components
tau2<-as.numeric(VarCorr(lme.model)[1,1])
sigma2<-as.numeric(VarCorr(lme.model)[2,1])
#design matrix for random intercepts
zmat<-outer(as.numeric(factor(lme.group)), 1:n.grp,'==')+0
#variance matrix
zmat%*%t(zmat)*tau2/sigma2+diag(n.obs)->Sig.theta
inv.Sig<-solve(Sig.theta)
#components of multivariate density: deviation from mean
if(log.tran) y.diff<-log(lme.y)-predict(lme.model, level=0) else y.diff<-lme.y-predict(lme.model, level=0)
#exponential term in the original likelihood
exp.term<- -t(y.diff) %*% inv.Sig %*% y.diff/(2*sigma2)
#log-likelihood: log(Jacobian) is in blue
LL<- if(log.tran) -.5*(n.obs*log(2*pi*sigma2)+ log(det(Sig.theta)))+ exp.term -sum(log(lme.y)) else -.5*(n.obs*log(2*pi*sigma2)+ log(det(Sig.theta)))+ exp.term
AIC.model<- -2*LL + 2*attr(logLik(lme.model),'df')
my.out<-c(LL,AIC.model)
names(my.out)<-c('loglik','AIC')
my.out
}
kestrel<- read.table('http://www.unc.edu/courses/2008fall/ecol/563/001/data/lab13/kestrel_data.csv', header=TRUE, sep=',', na.strings='.')
kestrel2<-kestrel[kestrel$egg==1 & !is.na(kestrel$nstlmass),]

The grouping variable is kestrel2$nest and the raw response is kestrel2$nstlmass.

library(nlme)
#log-transformed response
out1.log<-lme(log(nstlmass)~age, data=kestrel2, random=~1|nest, method='ML')
trans.loglik(out1.log,kestrel2$nest,kestrel2$nstlmass)
   loglik      AIC
-322.7917 653.5833
#untransformed response
out1<-lme(nstlmass~age, data=kestrel2, random=~1|nest, method='ML')
trans.loglik(out1,kestrel2$nest,kestrel2$nstlmass,F)
   loglik      AIC
-326.7366 661.4732

As a check, the second set of results for the untransformed response should match the the values returned by logLik and AIC.

c(logLik(out1),AIC(out1))
[1] -326.7366 661.4732
#Gompertz model without any level-2 predictors
c(logLik(a0.int),AIC(a0.int))
-305.6560 621.3121

Using the mvtnorm package

dmvnorm

(9b)

Using the dmvnorm function simplifies our code somewhat. I call the new function trans.loglik2.

#loglik for a log-transformed normal random intercepts model
#uses dmvnorm function from mvtnorm package
trans.loglik2<-function(lme.model, lme.group, lme.y, log.tran=T)
{
#number of observations and number of groups
n.obs<-length(lme.group)
n.grp<-length(unique(lme.group))
#extract variance components
tau2<-as.numeric(VarCorr(lme.model)[1,1])
sigma2<-as.numeric(VarCorr(lme.model)[2,1])
#design matrix for random intercepts
zmat<-outer(as.numeric(factor(lme.group)), 1:n.grp, '==')+0
#variance matrix
zmat%*%t(zmat)*tau2+sigma2*diag(n.obs)->Sigma
#multivariate normal log-likelihood
LL<- if(log.tran) log(dmvnorm(log(lme.y), predict(lme.model, level=0), Sigma)) - sum(log(lme.y)) else log(dmvnorm(lme.y, predict(lme.model, level=0), Sigma))
AIC.model<- -2*LL + 2*attr(logLik(lme.model),'df')
my.out<-c(LL,AIC.model)
names(my.out)<-c('loglik','AIC')
my.out
}
library(mvtnorm)
trans.loglik2(out1.log,kestrel2$nest,kestrel2$nstlmass)
   loglik      AIC
-322.7917 653.5833
trans.loglik2(out1,kestrel2$nest,kestrel2$nstlmass,F)
   loglik      AIC
-326.7366 661.4732

The results are the same as those obtained using the trans.loglik function above.

Cited references

Course Home Page



Jack Weiss
Phone: (919) 962-5930
E-Mail: jack_weiss@unc.edu
Address: Curriculum in Ecology, Box 3275, University of North Carolina, Chapel Hill, 27516
Copyright © 2009
Last Revised--September 8, 2009
URL: http://www.unc.edu/courses/2008fall/ecol/563/001/docs/lectures/lecture29.htm