Here is an example of using the function “contour” from the base graphics package in R to draw a contour plot.

I will generate a random sample of size 1000 from a gamma distribution with certain shape and rate parameters. I’ll estimate the parameters using maximum likelihood, there is a very convenient function for this from the package “MASS” called “fitdistr”.

I’ll also draw the contours of the log-likelihood surface and add the boundary of an approximately 95% confidence region for the two parameters determined using the Wilks “minus twice log likelihood” chi-square approximation.

As you’ll see, the visualisation of the log likelihood function adds hugely to the information which we get from “fitdistr”. And the old-fashioned contour plot seems in this case a much better tool than the more “sexy” wire-frame plot.

One could go on to rotate the surface and even make a movie of it rotating using the package “rgl” but it seems to me that the contour plot is the way to go.

set.seed(110951)
x <- rgamma(1000, 6, 0.5)
library(MASS)
truehist(x, xlim = c(0, 35))
xpts <- seq(from = 0, to = 35, length = 1000)
lines(xpts, dgamma(xpts, 6, 0.5))

plot of chunk unnamed-chunk-1

fit <- fitdistr(x, "gamma")
## Warning: NaNs produced
## Warning: NaNs produced
fit
##     shape     rate  
##   6.04952   0.50731 
##  (0.26342) (0.02303)
NxPts <- 100
NyPts <- 100

xGridPts <- seq(from = 5, to = 7, length = NxPts)
yGridPts <- seq(from = 0.4, to = 0.6, length = NyPts)

xypairs <- expand.grid(xGridPts, yGridPts) 
       # coordinates of all gridpoints, rows of an N x 2 matrix
       # N = NxPts * NyPts

head(xypairs)
##    Var1 Var2
## 1 5.000  0.4
## 2 5.020  0.4
## 3 5.040  0.4
## 4 5.061  0.4
## 5 5.081  0.4
## 6 5.101  0.4
str(xypairs)
## 'data.frame':    10000 obs. of  2 variables:
##  $ Var1: num  5 5.02 5.04 5.06 5.08 ...
##  $ Var2: num  0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 ...
##  - attr(*, "out.attrs")=List of 2
##   ..$ dim     : int  100 100
##   ..$ dimnames:List of 2
##   .. ..$ Var1: chr  "Var1=5.000" "Var1=5.020" "Var1=5.040" "Var1=5.061" ...
##   .. ..$ Var2: chr  "Var2=0.4000" "Var2=0.4020" "Var2=0.4040" "Var2=0.4061" ...
LogLikFun <- function(shape, rate) sum(dgamma(x, shape = shape, rate = rate, log = TRUE))

LogLikOnGrid <- mapply(LogLikFun, xypairs[ , 1], xypairs[ ,2]) # another long vector

## Can you understand the error message when you do
##    LogLikOnGrid <- outer(xGridPts, yGridPts, LogLikFun)   ?
## Why doesn't "outer" work here??
## Can you come up with something better than my solution?
## You'll also notice that "mapply" is rather slow ...
## There is an explicit "for loop" going on inside of "mapply".
## There ought to be a "vectorised" way to do this.

dim(LogLikOnGrid) <- c(NxPts, NyPts) # convert to matrix


## I'll subtract the maximum from the log likelihood function.
## We are only interested in *differences*, not in its absolute
## value. Now I know the maximum of the surface is at height z = 0.
## Also I reshape as a matrix.

MaxLogLik <- max(LogLikOnGrid)
LogLikOnGrid <- matrix(LogLikOnGrid, NxPts, NyPts) - max(LogLikOnGrid)




contour(x = xGridPts, y = yGridPts, z = LogLikOnGrid)

contour(x = xGridPts, y = yGridPts, z = LogLikOnGrid, 
        levels = c( (0:-8)*10), col = "blue", add = TRUE)

contour(x = xGridPts, y = yGridPts, z = LogLikOnGrid, 
        levels = - qchisq(0.95, 2)/2, col = "red", add = TRUE)

plot of chunk unnamed-chunk-1

persp(x = xGridPts, y = yGridPts, z = LogLikOnGrid)

plot of chunk unnamed-chunk-1