Marie Mørch
Aim: Implement a spline smoother using LOOCV for selecting the parameter \(\lambda\). If we minimize \[ RSS(s)=\sum_{i=1}^n(y_i-s(x_i))^2+\lambda\int s''(t)^2dt \] we get a cubic spline with knots in \(x_i\). A cubic spline is piecewise polynomial of degree 3 on the form \[ f=\sum_i \beta_i \phi_i \] where \(\phi_i\) are basis functions.
In vector notation we have \[ \hat{s} = \Phi \hat{\beta} \] where \(\phi_{ij}=\phi_j(x_i)\) and \[ RSS(s,\lambda) = (Y-\Phi \beta )^T(Y-\Phi \beta) + \lambda \beta^T \Omega \beta \] where \(\Omega_{ij}= \int \phi_i''(t)\phi_j''(t)dt\). This is minimized by \[ \hat{\beta}=(\Phi^T \Phi + \lambda \Omega)^{-1} \Phi^T Y \] and the resulting smoother is \[ \hat{s}=\Phi (\Phi^T \Phi + \lambda \Omega)^{-1} \Phi^T Y \]
The spline smoother is a linear smoother (\(\hat{s}=SY\) ) so we can use the formula \[ CVRSS = \sum_i (Y_i- \hat{s}_i^{(-i)})^2 = \sum_i \left(\frac{Y_i-\hat{s}_i}{1- S_{ii}}\right)^2 \]
Phi <- function(x){
knot <- c(rep(min(x),3), x, rep(max(x),3))
splineDesign(knots=knot,x)
}
Omega <- function(x){
bsplinepen(create.bspline.basis(range(x), breaks = sort(x)))
}
Smatrix <- function(phi, omega, l){
phi%*%solve(t(phi)%*%phi+l*omega)%*%t(phi)
}
cvrss <- function(y, l, phi, omega){
S<- Smatrix(phi, omega, l)
shat <- S%*%y
sum((y-shat)^2/(1-diag(S))^2)
}
lambda.opt <- function(y, phi, omega){
optim(1,cvrss, phi=phi, omega=omega, y=y, method = "BFGS")$par
}smoothspline <- function(x,y){
phi <- Phi(x)
omega <- Omega(x)
lambda <- lambda.opt(y,phi, omega)
S <- Smatrix(phi,omega, lambda)
shat <- S%*%y
mySpline <- structure(list(x=x, s=shat, y = y, lambda =lambda, Omega=omega, Phi=phi) ,class = "mySpline")
mySpline
}summary.mySpline <- function(x){
cat(paste("Optimal lambda:", x$lambda, "\n \n"))
tmp<-data.frame(
list(xval = x$x,
yval = x$y,
smoothval = x$s))
colnames(tmp) <- c("x values", "y values", "Smoothed values")
tmp
}
print.mySpline <- function(x){
cat(paste("Call: smoothspline \n \n"))
cat("Smoothed values: \n")
cat(x$s)
}plot.mySpline <- function(x, ...){
p<-ggplot() + geom_point(aes(x=x$x, y=x$y)) + geom_line( aes(x=x$x, y=x$s))
p
}
lambdaplot <- function(x) UseMethod("lambdaplot")
lambdaplot.mySpline <- function(sm){
lambdaopt <-sm$lambda
yopt <- cvrss(sm$y, sm$lambda, sm$Phi, sm$Omega)
lambdas <- seq(from = 0.001, to= lambdaopt*20, length.out = 300)
cvrssval <- sapply(lambdas, function(lambdas) cvrss(sm$y, lambdas,sm$Phi, sm$Omega))
p<-ggplot()+ geom_line(aes(x=lambdas, y=cvrssval))
p+ labs(title = paste("Optimal lambda", round(lambdaopt,2))) + geom_point(aes(x=lambdaopt, y=yopt), col="red") +xlab("Lambdas") + ylab("CVRSS")
}Comparison with the R-smoother:
x_sim1 <- seq(-100,100,3)
y_sim1 <- x_sim1^2 + rnorm(length(x_sim1), mean = 0, sd = 3000)
x_sim2 <- seq(-6,6,0.1)
y_sim2 <- cos(x_sim2) + rnorm(length(x_sim2), mean = 0, sd = 1) spline <- function(x,y, omega, phi, phiTphi){
structure(list(x=x, shat=NULL, y = y, lambda = NULL, Omega = omega, Phi = phi, phiTphi = phiTphi, knots=NULL) ,class = "mySpline")
}
newSmatrix <- function(phi, phiTphi, omega, l){
phi%*%solve(phiTphi+l*omega)%*%t(phi)
}
newcvrss <- function(y, l, phi,phiTphi, omega){
S<- newSmatrix(phi, phiTphi, omega, l)
shat <- S%*%y
sum((y-shat)^2/(1-diag(S))^2)
}
newoptlambda <- function(y, phi, phiTphi, omega){
optim(1,newcvrss, phi=phi, phiTphi= phiTphi, omega=omega, y=y, method = "BFGS")$par
}smoothsplineOpt <- function(x,y){
omega <- Omega(x)
phi <- Phi(x)
phiTphi <- crossprod(phi)
mySpline <- spline(x,y, omega, phi, phiTphi)
lambda <- newoptlambda(mySpline$y,mySpline$Phi,mySpline$phiTphi, mySpline$Omega)
S <- newSmatrix(mySpline$Phi,mySpline$phiTphi,mySpline$Omega, lambda)
shat <- S%*%y
mySpline$shat <- shat
mySpline$lambda <- lambda
mySpline
} library(microbenchmark)
microbenchmark(smoothspline(radius,temperature), smoothsplineOpt(radius,temperature))## Unit: milliseconds
## expr min lq mean median
## smoothspline(radius, temperature) 80.41388 87.05506 100.05890 92.40779
## smoothsplineOpt(radius, temperature) 68.66599 74.19768 83.04732 79.03629
## uq max neval
## 101.07965 274.4574 100
## 85.78186 236.5452 100
Plot of the \(\Phi\):
Plot of the \(\Omega\):
sparsePhi <- function(x){
knot <- c(rep(min(x),3), x, rep(max(x),3))
splineDesign(knots=knot,x, sparse=TRUE)
}
sparseOmega <- function(x){
Matrix(bsplinepen(create.bspline.basis(range(x), breaks = sort(x))), sparse = TRUE)
}
sparseSmatrix <- function(phi, phiTphi, omega, l){
Matrix(phi%*%solve(phiTphi+l*omega)%*%t(phi), sparse = TRUE)
}
sparsecvrss <- function(y, l, phi,phiTphi, omega){
S<- sparseSmatrix(phi, phiTphi, omega, l)
shat <- S%*%y
sum((y-shat)^2/(1-diag(S))^2)
}
sparseoptlambda <- function(y, phi, phiTphi, omega){
optim(1,sparsecvrss, phi=phi, phiTphi= phiTphi, omega=omega, y=y, method = "BFGS")$par
}
sparsesmoothspline <- function(x,y){
omega <- sparseOmega(x)
phi <- sparsePhi(x)
phiTphi <- crossprod(phi)
mySpline <- spline(x,y, omega, phi, phiTphi)
lambda <- sparseoptlambda(mySpline$y,mySpline$Phi,mySpline$phiTphi, mySpline$Omega)
S <- sparseSmatrix(mySpline$Phi,mySpline$phiTphi,mySpline$Omega, lambda)
shat <- as.numeric(S%*%y)
mySpline$shat <- shat
mySpline$lambda <- lambda
mySpline
} microbenchmark(sparsesmoothspline(radius,temperature), smoothsplineOpt(radius,temperature) )## Unit: milliseconds
## expr min lq mean
## sparsesmoothspline(radius, temperature) 130.74860 146.97782 171.3157
## smoothsplineOpt(radius, temperature) 76.92858 87.00962 102.8447
## median uq max neval
## 162.3281 176.55588 512.9753 100
## 92.5502 99.50594 428.8546 100
microbenchmark(sparsesmoothspline(xsim,ysim), smoothsplineOpt(xsim,ysim), smoothspline(xsim,ysim), times = 10L)## Unit: seconds
## expr min lq mean median
## sparsesmoothspline(xsim, ysim) 2.159418 2.288117 2.569798 2.534678
## smoothsplineOpt(xsim, ysim) 4.819525 5.107443 5.381594 5.224918
## smoothspline(xsim, ysim) 6.217133 6.362084 6.782426 6.656636
## uq max neval
## 2.882165 3.136082 10
## 5.484642 6.513259 10
## 6.736122 7.949948 10
smoothsplineOpt <- function(x,y, nknots=NULL){
if(is.null(nknots)){
knot<-c(rep(min(x),3), x, rep(max(x),3))
phi<- splineDesign(knots=knot,x)
omega <- bsplinepen(create.bspline.basis(range(x),breaks = x))
}else{
index <- round(seq.int(1, length(x), length.out = nknots-6))
knot <- c(rep(min(x[index]),3), x[index], rep(max(x[index]),3))
phi<- splineDesign(knots=knot,x)
omega <- bsplinepen(create.bspline.basis(range(x),breaks = x[index]))
}
phiTphi <- crossprod(phi)
mySpline <- spline(x,y, omega, phi, phiTphi)
lambda <- newoptlambda(mySpline$y,mySpline$Phi,mySpline$phiTphi, mySpline$Omega)
S <- newSmatrix(mySpline$Phi,mySpline$phiTphi,mySpline$Omega, lambda)
shat <- S%*%y
mySpline$shat <- shat
mySpline$lambda <- lambda
mySpline$knots <- knot
mySpline
} microbenchmark(smoothsplineOpt(radius,temperature, nknots=10), smoothsplineOpt(radius,temperature, nknots=20), smoothsplineOpt(radius,temperature, nknots=40), smoothsplineOpt(radius,temperature, nknots=60), smoothsplineOpt(radius,temperature))## Unit: milliseconds
## expr min lq
## smoothsplineOpt(radius, temperature, nknots = 10) 5.924302 6.663621
## smoothsplineOpt(radius, temperature, nknots = 20) 9.815475 11.029920
## smoothsplineOpt(radius, temperature, nknots = 40) 21.987687 24.885275
## smoothsplineOpt(radius, temperature, nknots = 60) 49.606955 56.096676
## smoothsplineOpt(radius, temperature) 70.562831 77.327953
## mean median uq max neval
## 7.677046 7.084069 9.082674 11.41140 100
## 12.383532 11.833853 13.482996 19.79536 100
## 33.047422 26.799630 29.276666 328.31105 100
## 62.593037 59.547345 62.897350 208.56993 100
## 80.610207 79.634486 83.202838 99.43354 100
plot((s80$shat- s20$shat)^2,col="purple", type="l",xlab=("radius"),ylab=("temperature"))
lines((s80$shat- s40$shat)^2, col="blue")
lines((s80$shat- s60$shat)^2, col="red")plot((s80$shat- s40$shat)^2,col="blue", type="l",xlab=("radius"),ylab=("temperature"))
lines((s80$shat- s60$shat)^2, col="red")Rsm<-smooth.spline(radius,temperature)
Rsm$fit$nk## [1] 59