https://github.com/cran/fda
Tip revision: aac3e3f7207f4732067b6e1d1b735f5d7855664f authored by J. O. Ramsay on 06 July 2007, 00:00:00 UTC
version 1.2.4
version 1.2.4
Tip revision: aac3e3f
smooth.monotone.R
smooth.monotone <- function(x, y, WfdParobj, wt=rep(1,nobs),
zmat=matrix(1,nobs,1), conv=.0001, iterlim=20,
active=c(FALSE,rep(TRUE,ncvec-1)), dbglev=1) {
# Smooths the relationship of Y to X using weights in WT by fitting a
# monotone function of the form
# f(x) = b_0 + b_1 D^{-1} exp W(x)
# where W is a function defined over the same range as X,
# W + ln b_1 = log Df and w = D W = D^2f/Df.
# The constant term b_0 in turn can be a linear combinations of covariates:
# b_0 = zmat * c.
# The fitting criterion is penalized mean squared error:
# PENSSE(lambda) = \sum w_i[y_i - f(x_i)]^2 +
# \lambda * \int [L W(x)]^2 dx
# where L is a linear differential operator defined in argument Lfdobj,
# and w_i is a positive weight applied to the observation.
# The function W(x) is expanded by the basis in functional data object
# Wfdobj. The coefficients of this expansion are called "coefficients"
# in the comments, while the b's are called "regression coefficients"
# Arguments:
# X ... vector of argument values
# Y ... vector of function values to be fit
# WT ... a vector of weights
# WFDPAROBJ ... functional parameter object for W(x). The coefficient array
# for WFDPAROBJ$FD has a single column, and these are the
# starting values for the iterative minimization of mean squared error.
# ZMAT ... a matrix of covariate values for the constant term.
# It defaults to a column of one's
# CONV ... convergence criterion, 0.0001 by default
# ITERLIM ... maximum number of iterations, 20 by default
# ACTIVE ... vector of 1's and 0's indicating which coefficients
# are to be optimized (1) or remain fixed (0). All values
# are 1 by default, except that if a B-spline basis is used,
# the first value is set to 0.
# DBGLEV ... Controls the level of output on each iteration. If 0,
# no output, if 1, output at each iteration, if higher, output
# at each line search iteration. 1 by default.
# Returns a list containing:
# WFDOBJ ... functional data object for W(x). Its coefficients are
# those that optimize fit.
# BETA ... final regression coefficient values
# FLIST ... A list containing the final function value, gradient and
# gradient norm.
# ITERNUM ... number of iterations
# ITERHIST ... ITERNUM+1 by 5 array containing iteration history
# Last modified 30 April 2007 by Spencer Graves
# Last modified 26 October 2005
# initialize some arrays
x <- as.vector(x)
nobs <- length(x) # number of observations
# the starting values for the coefficients are in FD object WFDOBJ
if (!(inherits(WfdParobj, "fdPar"))) stop(
"Argument 'WfdParobj' is not a functional data object.")
Wfdobj <- WfdParobj$fd
Lfdobj <- WfdParobj$Lfd
basisobj <- Wfdobj$basis # basis for W(x)
nbasis <- basisobj$nbasis # number of basis functions
type <- basisobj$type
cvec <- Wfdobj$coefs
ncvec <- length(cvec)
# check some arguments
if (any(wt < 0)) stop("One or more weights are negative.")
if (all(wt == 0)) stop("All weights are zero.")
zdim <- dim(zmat)
if (zdim[1] != nobs) stop(
"First dimension of ZMAT not correct.")
# set up some variables
ncov <- zdim[2] # number of covariates
ncovp1 <- ncov + 1 # index for regression coef. for monotone fn.
wtroot <- sqrt(wt)
wtrtmt <- wtroot %*% matrix(1,1,ncovp1)
yroot <- y*wtroot
climit <- c(-100*rep(1,nbasis), 100*rep(1,nbasis))
inact <- !active # indices of inactive coefficients
# set up cell for storing basis function values
JMAX <- 15
basislist <- vector("list", JMAX)
# initialize matrix Kmat defining penalty term
lambda = WfdParobj$lambda
if (lambda > 0) Kmat <- lambda*getbasispenalty(basisobj, Lfdobj)
else Kmat <- NULL
# Compute initial function and gradient values
result <- fngrad.smooth.monotone(y, x, zmat, wt, Wfdobj, lambda,
Kmat, inact, basislist)
Flist <- result[[1]]
beta <- result[[2]]
Dyhat <- result[[3]]
# compute the initial expected Hessian
hessmat <- hesscal.smooth.monotone(beta, Dyhat, wtroot,
lambda, Kmat, inact)
# evaluate the initial update vector for correcting the initial cvec
result <- linesearch.smooth.monotone(Flist, hessmat, dbglev)
deltac <- result[[1]]
cosangle <- result[[2]]
# initialize iteration status arrays
iternum <- 0
status <- c(iternum, Flist$f, Flist$norm, beta)
if (dbglev >= 1) {
cat("\nIter. PENSSE Grad Length Intercept Slope")
cat("\n")
cat(iternum)
cat(" ")
cat(round(status[2],4))
cat(" ")
cat(round(status[3],4))
cat(" ")
cat(round(beta[1],4))
cat(" ")
cat(round(beta[ncovp1],4))
}
iterhist <- matrix(0,iterlim+1,length(status))
iterhist[1,] <- status
if (iterlim == 0)
return ( list( "Wfdobj" = Wfdobj, "beta" = beta, "Flist" = Flist,
"iternum" = iternum, "iterhist" = iterhist ) )
# ------- Begin iterations -----------
MAXSTEPITER <- 10
MAXSTEP <- 100
trial <- 1
reset <- FALSE
linemat <- matrix(0,3,5)
betaold <- beta
cvecold <- cvec
Foldlist <- Flist
dbgwrd <- dbglev >= 2
for (iter in 1:iterlim)
{
iternum <- iternum + 1
# initialize logical variables controlling line search
dblwrd <- c(FALSE,FALSE)
limwrd <- FALSE
stpwrd <- FALSE
ind <- 0
ips <- 0
# compute slope at 0 for line search
linemat[2,1] <- sum(deltac*Flist$grad)
# normalize search direction vector
sdg <- sqrt(sum(deltac^2))
deltac <- deltac/sdg
dgsum <- sum(deltac)
linemat[2,1] <- linemat[2,1]/sdg
# initialize line search vectors
linemat[,1:4] <- outer(c(0, linemat[2,1], Flist$f),rep(1,4))
stepiter <- 0
if (dbglev >= 2) {
cat("\n")
cat(paste(" ", stepiter, " "))
cat(format(round(t(linemat[,1]),6)))
}
# return with error condition if initial slope is nonnegative
if (linemat[2,1] >= 0) {
if (dbgwrd >= 2) print("Initial slope nonnegative.")
ind <- 3
break
}
# return successfully if initial slope is very small
if (linemat[2,1] >= -1e-7) {
if (dbglev >= 2) print("Initial slope too small")
ind <- 0
break
}
# first step set to trial
linemat[1,5] <- trial
# Main iteration loop for linesearch
cvecnew <- cvec
Wfdnew <- Wfdobj
for (stepiter in 1:MAXSTEPITER)
{
# ensure that step does not go beyond limits on parameters
limflg <- FALSE
# check the step size
result <-
stepchk(linemat[1,5], cvec, deltac, limwrd, ind,
climit, active, dbgwrd)
linemat[1,5] <- result[[1]]
ind <- result[[2]]
limwrd <- result[[3]]
if (linemat[1,5] <= 1e-7)
{
# Current step size too small ... terminate
if (dbglev >= 2) {
print("Stepsize too small")
# print(avec[5])
}
if (limflg) ind <- 1 else ind <- 4
break
}
# compute new function value and gradient
cvecnew <- cvec + linemat[1,5]*deltac
Wfdnew$coefs <- as.matrix(cvecnew)
result <- fngrad.smooth.monotone(y, x, zmat, wt, Wfdnew, lambda,
Kmat, inact, basislist)
Flist <- result[[1]]
beta <- result[[2]]
Dyhat <- result[[3]]
linemat[3,5] <- Flist$f
# compute new directional derivative
linemat[2,5] <- sum(deltac*Flist$grad)
if (dbglev >= 2) {
cat("\n")
cat(paste(" ", stepiter, " "))
cat(format(round(t(linemat[,5]),6)))
}
# compute next line search step, also test for convergence
result <- stepit(linemat, ips, ind, dblwrd, MAXSTEP, dbglev)
linemat <- result[[1]]
ips <- result[[2]]
ind <- result[[3]]
dblwrd <- result[[4]]
trial <- linemat[1,5]
# ind == 0 mean convergence
if (ind == 0 | ind == 5) break
}
# end iteration loop
cvec <- cvecnew
Wfdobj <- Wfdnew
# check that function value has not increased
if (Flist$f > Foldlist$f) {
# if it has, terminate iterations with a message
if (dbglev >= 2) {
cat("Criterion increased: ")
cat(format(round(c(Foldlist$f, Flist$f),4)))
cat("\n")
}
# reset parameters and fit
beta <- betaold
cvec <- cvecold
Wfdobj$coefs <- cvec
Flist <- Foldlist
deltac <- -Flist$grad
if (reset) {
# This is the second time in a row that
# this has happened ... quit
if (dbglev >= 2) cat("Reset twice, terminating.\n")
return ( list( "Wfdobj" = Wfdobj, "beta" = beta, "Flist" = Flist,
"iternum" = iternum, "iterhist" = iterhist ) )
} else {
reset <- TRUE
}
} else {
if (abs(Foldlist$f - Flist$f) < conv) {
cat("\n")
break
}
cvecold <- cvec
betaold <- beta
Foldlist <- Flist
hessmat <- hesscal.smooth.monotone(beta, Dyhat, wtroot,
lambda, Kmat, inact)
# udate the line search direction
result <- linesearch.smooth.monotone(Flist, hessmat, dbglev)
deltac <- result[[1]]
cosangle <- result[[2]]
reset <- FALSE
}
# store iteration status
status <- c(iternum, Flist$f, Flist$norm, beta)
iterhist[iter+1,] <- status
if (dbglev >= 1) {
cat("\n")
cat(iternum)
cat(" ")
cat(round(status[2],4))
cat(" ")
cat(round(status[3],4))
cat(" ")
cat(round(beta[1],4))
cat(" ")
cat(round(beta[ncovp1],4))
}
}
return ( list( "Wfdobj" = Wfdobj, "beta" = beta, "Flist" = Flist,
"iternum" = iternum, "iterhist" = iterhist ) )
}
# ----------------------------------------------------------------
linesearch.smooth.monotone <- function(Flist, hessmat, dbglev)
{
deltac <- -symsolve(hessmat,Flist$grad)
cosangle <- -sum(Flist$grad*deltac)/sqrt(sum(Flist$grad^2)*sum(deltac^2))
if (dbglev >= 2) {
cat(paste("\nCos(angle) =",format(round(cosangle,4))))
if (cosangle < 1e-7) {
if (dbglev >=2) cat("\nCosine of angle too small\n")
deltac <- -Flist$grad
}
}
return(list(deltac, cosangle))
}
# ----------------------------------------------------------------
fngrad.smooth.monotone <- function(y, x, zmat, wt, Wfdobj, lambda,
Kmat, inact, basislist)
{
ncov <- ncol(zmat)
ncovp1 <- ncov + 1
nobs <- length(x)
cvec <- Wfdobj$coefs
nbasis <- length(cvec)
h <- monfn(x, Wfdobj, basislist)
Dyhat <- mongrad(x, Wfdobj, basislist)
xmat <- cbind(zmat,h)
Dxmat <- array(0,c(nobs,ncovp1,nbasis))
Dxmat[,ncovp1,] <- Dyhat
wtroot <- sqrt(wt)
wtrtmt <- wtroot %*% matrix(1,1,ncovp1)
yroot <- y*wtroot
xroot <- xmat*wtrtmt
# compute regression coefs.
beta <- lsfit(xmat, y, wt, int=FALSE)$coef
# update fitted values
yhat <- xmat %*% beta
# update residuals and function values
res <- y - yhat
f <- mean(res^2*wt)
grad <- matrix(0,nbasis,1)
for (j in 1:nbasis) {
Dxroot <- Dxmat[,,j]*wtrtmt
yDx <- crossprod(yroot,Dxroot) %*% beta
xDx <- crossprod(xroot,Dxroot)
grad[j] <- crossprod(beta,(xDx+t(xDx))) %*% beta - 2*yDx
}
grad <- grad/nobs
if (lambda > 0) {
grad <- grad + 2 * Kmat %*% cvec
f <- f + t(cvec) %*% Kmat %*% cvec
}
if (any(inact)) grad[inact] <- 0
norm <- sqrt(sum(grad^2)) # gradient norm
Flist <- list("f"=f,"grad"=grad,"norm"=norm)
return(list(Flist, beta, Dyhat))
}
# ----------------------------------------------------------------
hesscal.smooth.monotone <- function(beta, Dyhat, wtroot, lambda,
Kmat, inact)
{
nbet <- length(beta)
Dydim <- dim(Dyhat)
nobs <- Dydim[1]
nbasis <- Dydim[2]
temp <- beta[nbet]*Dyhat
temp <- temp*(wtroot %*% matrix(1,1,nbasis))
hessmat <- 2*crossprod(temp)/nobs
# adjust for penalty
if (lambda > 0) hessmat <- hessmat + 2*Kmat
# adjust for inactive coefficients
if (any(inact)) {
eyemat <- diag(rep(1,nbasis))
hessmat[inact, ] <- 0
hessmat[ ,inact] <- 0
hessmat[inact,inact] <- eyemat[inact,inact]
}
return(hessmat)
}