###
###
### Ramsey & Silverman (2002) Applied Functional Data Analysis
### (Springer)
###
### ch. 6. Human growth
###
library(fda)
##
## sec. 6.1. Introduction
##
##
## sec. 6.2. Height measurements at three scales
##
str(growth)
# pp. 84-85. Figure 6.1. the first 10 females
# of the Berkeley growth study
op <- par(cex=1.1)
with(growth, matplot(age, hgtf[, 1:10], type="b", pch="o",
ylab="Height (cm.)") )
par(op)
# Figure 6.2. Heights of one boy during one school year
# A monotone smooth requires some effort.
# (1) set up the basis
nbasis.onechild <- 33
# Establish a B-spline basis
# with nbasis.onechild basis function
hgtbasis <- with(onechild,
create.bspline.basis(range(day), nbasis.onechild))
tst <- create.bspline.basis(onechild$day)
# set up the functional data object for W <- log Dh
# Start by creating a functional 0 from hgtbasis
cvec0 <- rep(0,nbasis.onechild)
Wfd0 <- fd(cvec0, hgtbasis)
# set parameters for the monotone smooth
# with smoothing lambda = 1e-1 or 1e-12
WfdPar.1 <- fdPar(Wfd0, 2, lambda=.1)
WfdPar.12 <- fdPar(Wfd0, 2, lambda=1e-12)
# -------------- carry out the monotone smooth ---------------
# The monotone smooth is
# beta[1]+beta[2]*integral(exp(Wfdobj)),
# where Wfdobj is a functional data object
smoothList.1 <- with(onechild,
smooth.monotone(x=day, y=height, WfdParobj=WfdPar.1) )
smoothList.12 <- with(onechild,
smooth.monotone(x=day, y=height, WfdParobj=WfdPar.12) )
#str(smoothList)
#attach(smoothList)
# Create a fine grid at which to evaluate the smooth
dayfine <- with(onechild, seq(day[1],day[length(day)],len=151))
# eval.monfd = integral(exp(Wfdobj))
# This is monotonically increasing, since exp(Wfdobj)>0.
#yhat <- with(smoothList,
# beta[1] + beta[2]*eval.monfd(onechild$day, Wfdobj))
yhatfine.1 <- with(smoothList.1,
beta[1] + beta[2]*eval.monfd(dayfine, Wfdobj))
yhatfine.12 <- with(smoothList.12,
beta[1] + beta[2]*eval.monfd(dayfine, Wfdobj))
plot(onechild, ylab="Height (cm.)") # raw data
lines(dayfine, yhatfine.1, lwd=2) # lambda=0.1: reasonable
lines(dayfine, yhatfine.12, lty=2)
# lambda=1e-12:too close to a straight line
# p. 86, Figure 6.3. Growth of the length of the tibia of a newborn;
# data not available
##
## sec. 6.3. Velocity and acceleration
##
# p. 87, Figure 6.4. Estimated growth rate of the first girl
nage <- length(growth$age)
norder.growth <- 6
nbasis.growth <- nage + norder.growth - 2
# 35
rng.growth <- range(growth$age)
# 1 18
wbasis.growth <- create.bspline.basis(rng.growth,
nbasis.growth, norder.growth,
growth$age)
# starting values for coefficient
cvec0.growth <- matrix(0,nbasis.growth,1)
Wfd0.growth <- fd(cvec0.growth, wbasis.growth)
Lfdobj.growth <- 3 # penalize curvature of acceleration
lambda.growth <- 10^(-0.5) # smoothing parameter
growfdPar <- fdPar(Wfd0.growth, Lfdobj.growth, lambda.growth)
# --------------------- Now smooth the data --------------------
smoothGirl1 <- with(growth, smooth.monotone(x=age,
y=hgtf[, 1], WfdParobj=growfdPar, conv=0.001, active=TRUE,
dbglev=0) )
#*** The default active = c(FALSE, rep(TRUE, ncvec-1))
# This tells smooth.monotone to estimate
# force the first coeffeicient of W to 0,
# and estimate only the others.
#*** That starts velocity unrealistically low.
#*** Fix this with active=TRUE
# to estimate all elements of W.
# Create a fine grid at which to evaluate the smooth
agefine <- with(growth, seq(age[1], age[nage], len=151))
# Lfdobj = 1 for first derivative, growth rate or velocity
smoothG1.1 <- with(smoothGirl1,
beta[2]*eval.monfd(agefine, Wfdobj, Lfdobj=1))
plot(agefine, smoothG1.1, type="l",
xlab="Year", ylab="Growth velocity (cm/year)")
axis(3, labels=FALSE)
axis(4, labels=FALSE)
# p. 88, Figure 6.5, Estimated growth velocity of a 10-year old boy
str(onechild)
nDays <- dim(onechild)[1]
norder.oneCh <- 6
nbasis.oneCh <- nDays+norder.oneCh-2
rng.days <- range(onechild$day)
# B-spline basis
wbasis.oneCh <- create.bspline.basis(rng.days,
nbasis.oneCh, norder.oneCh, onechild$day)
# starting values for coefficients
cvec0.oneCh <- matrix(0, nbasis.oneCh, 1)
Wfd0.oneCh <- fd(cvec0.oneCh, wbasis.oneCh)
Lfdobj.oneCh <- 3
# penalize curvature of acceleration
growfdPar.oneCh100 <- fdPar(Wfd0.oneCh, Lfdobj.oneCh,
lambda=100 )
# now smooth the data
zmat.oneCh <- matrix(1, nDays, 1)
smoothOneCh100 <- with(onechild, smooth.monotone(x=day,
y=height, WfdParobj=growfdPar.oneCh100,
conv=0.001, active=TRUE, dbglev=0) )
dayFine <- with(onechild, seq(day[1], day[nDays], len=151))
sm.OneCh100 <- with(smoothOneCh100,
beta[2]*eval.monfd(dayFine, Wfdobj, Lfdobj=1) )
plot(dayFine, sm.OneCh100, type="l", xlab="day",
ylab="Growth velocity (cm/day)" )
axis(3, labels=FALSE)
axis(4, labels=FALSE)
# Close to Figure 6.5
# closer than with lambda = 10 or 1000
# p. 88, Figure 6.6. Estimated growth velocity of a baby
# Data not available.
# p. 89, Figure 6.7.
#Estimated growth acceleration for 10 girls in the Berkeley Growth Study
# Similar to Figure 6.4, but acceleration not velocity
# and for 10 girls not one
nage <- length(growth$age)
norder.growth <- 6
nbasis.growth <- nage + norder.growth - 2
# 35
rng.growth <- range(growth$age)
# 1 18
wbasis.growth <- create.bspline.basis(rng.growth,
nbasis.growth, norder.growth,
growth$age)
str(wbasis.growth)
# starting values for coefficient
cvec0.growth <- matrix(0,nbasis.growth,1)
Wfd0.growth <- fd(cvec0.growth, wbasis.growth)
Lfdobj.growth <- 3 # penalize curvature of acceleration
# --------------------- Now smooth the data --------------------
# Create a fine grid at which to evaluate the smooth
nptsFine <- 151
#############################################
# Experimented with other values of lambda.
# Relative to Figure 6.7,
# lambda = 0.01 undersmoothed, while 0.1 oversmoothed.
# NOTE: This script was prepared years after the original
# figures were prepared, without knowledge of
# exactly how the original figures were prepared.
lambda.gr2.3 <- .03
growfdPar2.3 <- fdPar(Wfd0.growth, Lfdobj.growth, lambda.gr2.3)
ncasef <- 10
smoothGirls2.3 <- vector("list", ncasef)
for(icase in 1:ncasef){
smoothGirls2.3[[icase]] <- with(growth, smooth.monotone(x=age,
y=hgtf[, icase], WfdParobj=growfdPar2.3, conv=0.001,
active=TRUE, dbglev=0) )
cat(icase, "")
}
agefine <- with(growth, seq(age[1], age[nage], len=nptsFine))
smoothGirlsAcc2.3 <- array(NA, dim=c(nptsFine, ncasef))
for(icase in 1:ncasef)
smoothGirlsAcc2.3[, icase] <- with(smoothGirls2.3[[icase]],
beta[2]*eval.monfd(agefine, Wfdobj, Lfdobj=2) )
op <- par(cex=1.5)
matplot(agefine, smoothGirlsAcc2.3, type="l", ylim=c(-4, 2),
xlab="age", ylab="Growth acceleration (cm/year^2)",
bty="n")
lines(agefine, apply(smoothGirlsAcc2.3, 1, mean), lwd=3)
abline(h=0, lty="dashed")
par(op)
# Good match
#############################################
##
## sec. 6.4. An equation for growth
##
# p. 91, Figure 6.8. Relative acceleration and its integral
#Estimated growth acceleration for 10 girls in the Berkeley Growth Study
# Similar to Figure 6.7, but acceleration / velocity
smoothGirlsVel2.3 <- array(NA, dim=c(nptsFine, ncasef))
for(icase in 1:ncasef)
smoothGirlsVel2.3[, icase] <- with(smoothGirls2.3[[icase]],
beta[2]*eval.monfd(agefine, Wfdobj, Lfdobj=1) )
smoothGirlsRelAcc2.3 <- (smoothGirlsAcc2.3 /
smoothGirlsVel2.3)
matplot(agefine, smoothGirlsRelAcc2.3, type="l", ylim=c(-2, 0.5),
xlab="Year", ylab="w(t)")
abline(h=0, lty="dashed")
diff(range(quantile(diff(agefine))))
d.agefine <- mean(diff(agefine))
smoothGirlsIntRelAcc2.3 <- (apply(smoothGirlsRelAcc2.3, 2, cumsum)
* d.agefine)
str(smoothGirlsIntRelAcc2.3)
matplot(agefine, smoothGirlsIntRelAcc2.3, type="l", ylim=c(-4, 0.5),
xlab="Year", ylab="W(t)")
abline(h=0, lty="dashed")
##
## sec. 6.5. Timing or phase variation in growth
##
# p. 92, Figure 6.9. Acceleration curves
# differing in amplitude only and phase only
# This figure was conceptual, not real data.
# Recreate roughly by reading numbers off the figure
Fig6.9 <- cbind(Age=3:20,
accel=c(-0.6, -0.5, -0.41, -0.4, -0.4, -0.405,
-0.4, -0.25, 0.2, 1.9, 2.8, -1.8, -4.7,
-3.2, -0.7, -0.2, -0.02, 0) )
Fig6.9basis6 <- create.bspline.basis(Fig6.9[, 1], norder=5)
# If Lfdobj = 0 (default), smooth.basisPar goes crazy
# between points
# Lfdobj = 3 works OK with lambda = 0.01
Fig6.9s3.01 <- smooth.basisPar(argvals=Fig6.9[, 1], y=Fig6.9[, 2],
fdobj=Fig6.9basis6, 3, .01)
plot(Fig6.9s3.01$fd, ylim=range(Fig6.9[, 2]))
Fig6.9age <- seq(5, 20, length=101)
Fig6.9a <- eval.fd(Fig6.9age, Fig6.9s3.01$fd)
plot(Fig6.9age, 1.2*Fig6.9a, type="l", xlab="Age", ylab="")
lines(Fig6.9age, Fig6.9a, lty=2, col=2)
lines(Fig6.9age, 0.8*Fig6.9a, lty=3, col=3)
title("Amplitude variation")
Fig6.9b1 <- seq(3, 18, length=101)
Fig6.9b2 <- seq(4, 19, length=101)
Fig6.9b1. <- eval.fd(Fig6.9b1, Fig6.9s3.01$fd)
Fig6.9b2. <- eval.fd(Fig6.9b2, Fig6.9s3.01$fd)
plot(Fig6.9age, Fig6.9a, type="l", xlab="Age", ylab="")
lines(Fig6.9age, Fig6.9b1., lty=2, col=2)
lines(Fig6.9age, Fig6.9b2., lty=3, col=3)
title("Phase variation")
# p. 93, Figure 6.10. Time warping functions
# for 10 Berkeley girls
# ---------------------------------------------------------------------
# Register the velocity curves for the girls
# ---------------------------------------------------------------------
# Duplicate setup from Figure 6.7
(nage <- length(growth$age))
# 31
norder.growth <- 6
(nbasis.growth <- nage + norder.growth - 2)
# 35
(rng.growth <- range(growth$age))
# 1 18
wbasis.growth <- create.bspline.basis(rng.growth,
nbasis.growth, norder.growth,
growth$age)
# Specify what to smooth, namely the rate of change of curvature
Lfdobj.growth <- 2
# Specify smoothing weight (see Figure 6.7 above)
lambda.gr2.3 <- .03
nage <- length(growth$age)
norder.growth <- 6
nbasis.growth <- nage + norder.growth - 2
rng.growth <- range(growth$age)
# 1 18
wbasis.growth <- create.bspline.basis(rangeval=rng.growth, norder=norder.growth,
breaks=growth$age)
# Smooth all girls; subset later
cvec0.growth <- matrix(0,nbasis.growth,1)
Wfd0.growth <- fd(cvec0.growth, wbasis.growth)
growfdPar2.3 <- fdPar(Wfd0.growth, Lfdobj.growth, lambda.gr2.3)
# Create a functional data object for all the girls
smG.fd.all <- with(growth, smooth.basis(age, hgtf, growfdPar2.3))
# register the girls.
smGv <- deriv(smG.fd.all$fd, 1)
et.G <- system.time(
smG.reg.1 <- register.fd(smGv,
WfdParobj=c(Lfdobj=Lfdobj.growth, lambda=lambda.gr2.3))
)
et.G/60
#save(list=c("smG.reg.1", "et.G"), file="registerGirls.Rdata")
#load("registerGirls.Rdata")
class(smG.reg.1)
# list
sapply(smG.reg.1, class)
# regfd Wfd shift
# "fd" "fd" "numeric"
nPts <- 151
agefine <- with(growth, seq(age[1], age[nage], len=nPts))
smG.warpmat01 <- eval.monfd(agefine, smG.reg.1$Wfd)
smG.warpmat <- 1+17*smG.warpmat01 / rep(smG.warpmat01[nPts,], each=nPts)
range(smG.warpmat)
# 1 18
str(smG.warpmat)
matplot(agefine, smG.warpmat[, 1:10], type="l")
lines(c(1,18), c(1, 18), lty="dashed", lwd=2, col="black")
# Matches the general look and feel of Figure 6.10
# but not the details.
# This difference is probably due to improvements made
# to the algorithms since those used to produce the book.
##
## sec. 6.6. Amplitude and phase variation in growth
##
# p. 94, Figure 6.11. Height acceleration for boys and girls
# (cm / year^2)
smG.regmean <- mean(smG.reg.1$regfd)
# Create a functional data object for all the boys
smB.fd.all <- with(growth, smooth.basis(age, hgtm, growfdPar2.3))
# register the boys
smBv <- deriv(smB.fd.all$fd, 1)
(et.B <- system.time(
smB.reg.1 <- register.fd(smBv,
WfdParobj=c(Lfdobj=Lfdobj.growth, lambda=lambda.gr2.3))
))
et.B/60
#save(list=c("smB.reg.1", "et.B"), file="registerBoys.Rdata")
#load("registerBoys.Rdata")
smB.regmean <- mean(smB.reg.1$regfd)
str(smB.regmean)
all.equal(smB.regmean$basis, smG.regmean$basis)
# TRUE
all.equal(smB.regmean$fdnames, smG.regmean$fdnames)
# TRUE
smBG.regmean <- with(smB.regmean, fd(cbind(coefs, smG.regmean$coefs),
basis, fdnames) )
plot(smBG.regmean)
plot(deriv(smBG.regmean))
# Not as smooth as Figure 6.11 but similar.
# A better match might be achieved with more smoothing.
# However, registering the girls took 45 minutes and the boys took 30
# 2007.09.22. Therefore, I won't push that now.
plot(deriv(smBG.regmean), seq(2, 18, length=101))
# without the initial drop absent from Figure 6.11.
# p. 95, Figure 6.12. Registering boys' height velocity to girls
#plot(smBG.regmean)
# After some experimentation, lambda = 0.1,
# gave a rough match to Figure 6.12
smBG.regG.1 <- register.fd(smG.regmean, smBG.regmean, WfdParobj=c(lambda=.1))
sapply(smBG.regG.1, class)
# regfd Wfd shift
# "fd" "fd" "numeric"
smB.warpG.1 <- eval.monfd(agefine, smBG.regG.1$Wfd[1])
smB.wrpG.1 <- 1+17*smB.warpG.1 / rep(smB.warpG.1[nPts,], each=nPts)
range(smB.wrpG.1)
# 1 18
#smG.warpB. <- 1+17*smG.warpB / rep(smG.warpB[nPts,], each=nPts)
#range(smG.warpB.)
# 1 18
str(smB.warpG.1)
op <- par(mfrow=c(2,2))
plot(agefine, smB.wrpG.1, type="l")
lines(c(1,18), c(1, 18), lty="dashed", lwd=2, col="black")
plot(deriv(smBG.regG.1$regfd))
par(op)
# p. 96, Figure 6.13. Principal components of registered growth acceleration
# for girls
sapply(smG.reg.1, class)
pca.G <- pca.fd(deriv(smG.reg.1$regfd), nharm=3)
class(pca.G)
#[1] "pca.fd"
str(pca.G)
plot(pca.G)
pcaVm.G <- varmx.pca.fd(pca.G)
plot(pcaVm.G)
op <- par(mfrow=c(2,2))
plot(pcaVm.G, cex.main=.9, seq(4, 18, len=101))
par(op)
# Matches Figure 6.13 in broad outline
# but many details are different.
# Might more smoothing produce greater agreement?
# p. 96, Figure 6.14. PCA of the warping functions
pca.G.w <- pca.fd(deriv(smG.reg.1$Wfd), nharm=3)
pcaVm.G.w <- varmx.pca.fd(pca.G.w)
op <- par(mfrow=c(2,2))
plot(pcaVm.G.w, cex.main=.9, seq(4, 18, len=101))
par(op)
# Differences between the images produced here
# and Figure 6.14 in the book may be due either one of two things:
#
# (a) This script was produced some time after the book was produced,
# without access to information about exactly what smoothing was used
# in the book. More careful experimentation with alternative
# smoothing might produce a closer match to the book.
#
# (b) The algorithms have been improved since the book was written,
# and the current images seem at least as good and perhaps
# better representations of the data then those in the book.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.