smooth.construct.so.smooth.spec {mgcv} | R Documentation |
Soap film smoother constructer
Description
Sets up basis functions and wiggliness penalties for soap film smoothers (Wood, Bravington and Hedley, 2008). Soap film smoothers are based on the idea of constructing a 2-D smooth as a film of soap connecting a smoothly varying closed boundary. Unless smoothing very heavily, the film is distorted towards the data. The smooths are designed not to smooth across boundary features (peninsulas, for example).
The so
version sets up the full smooth. The sf
version sets up just the boundary interpolating
soap film, while the sw
version sets up the wiggly component of a soap film (zero on the boundary).
The latter two are useful for forming tensor products with soap films, and can be used with gamm
and gamm4
. To use these to simply set up a basis, then call via the wrapper smooth.construct2
or smoothCon
.
Usage
## S3 method for class 'so.smooth.spec'
smooth.construct(object,data,knots)
## S3 method for class 'sf.smooth.spec'
smooth.construct(object,data,knots)
## S3 method for class 'sw.smooth.spec'
smooth.construct(object,data,knots)
Arguments
object |
A smooth specification object as produced by a |
data |
A list or data frame containing the arguments of the smooth. |
knots |
list or data frame with two named columns specifying the knot locations within
the boundary. The column names should match the names of the arguments of the smooth. The number
of knots defines the *interior* basis dimension (i.e. it is *not* supplied via argument |
Details
For soap film smooths the following *must* be supplied:
- k
the basis dimension for each boundary loop smooth.
- xt$bnd
the boundary specification for the smooth.
- knots
the locations of the interior knots for the smooth.
When used in a GAM then k
and xt
are supplied via s
while knots
are
supplied in the knots
argument of gam
.
The bnd
element of the xt
list is a list of lists (or data frames),
specifying the loops that define the boundary. Each boundary loop list must contain
2 columns giving the co-ordinates of points defining a boundary loop (when joined
sequentially by line segments). Loops should not intersect (not checked). A point is
deemed to be in the region of interest if it is interior to an odd number of boundary
loops. Each boundary loop list may also contain a column f
giving known
boundary conditions on a loop.
The bndSpec
element of xt
, if non-NULL, should contain
- bs
the type of cyclic smoothing basis to use: one of
"cc"
and"cp"
. If not"cc"
then a cyclic p-spline is used, and argumentm
must be supplied.- knot.space
set to "even" to get even knot spacing with the "cc" basis.
- m
1 or 2 element array specifying order of "cp" basis and penalty.
Currently the code will not deal with more than one level of nesting of loops, or with separate loops without an outer enclosing loop: if there are known boundary conditions (identifiability constraints get awkward).
Note that the function locator
provides a simple means for defining boundaries
graphically, using something like bnd <-as.data.frame(locator(type="l"))
,
after producing a plot of the domain of interest (right click to stop). If the real boundary is
very complicated, it is probably better to use a simpler smooth boundary enclosing the true boundary,
which represents the major boundary features that you don't want to smooth across, but doesn't follow
every tiny detail.
Model set up, and prediction, involves evaluating basis functions which are defined as the solution to PDEs. The
PDEs are solved numerically on a grid using sparse matrix methods, with bilinear interpolation used to obtain
values at any location within the smoothing domain. The dimension of the PDE solution grid can be controlled
via element nmax
(default 200) of the list supplied as argument xt
of s
in a gam
formula: it gives the number of cells to use on the longest grid side.
A little theory: the soap film smooth f(x,y)
is defined as the solution of
f_{xx} + f_{yy} = g
subject to the condition that f=s
, on the boundary curve, where
s
is a smooth function (usually a cyclic penalized regression
spline). The function g
is defined as the solution of
g_{xx}+g_{yy}=0
where g=0
on the boundary curve and
g(x_k,y_k)=c_k
at the ‘knots’ of the surface; the
c_k
are model coefficients.
In the simplest case, estimation of the coefficients of f
(boundary
coefficients plus c_k
's) is by minimization of
\|z-f\|^2 + \lambda_s J_s(s) + \lambda_f J_f(f)
where J_s
is usually some cubic spline type wiggliness penalty on
the boundary smooth and J_f
is the integral of
(f_xx+f_yy)^2
over the interior of the boundary. Both
penalties can be expressed as quadratic forms in the model coefficients. The
\lambda
's are smoothing parameters, selectable by GCV, REML, AIC,
etc. z
represents noisy observations of f
.
Value
A list with all the elements of object
plus
sd |
A list defining the PDE solution grid and domain boundary, and including the sparse LU factorization of the PDE coefficient matrix. |
X |
The model matrix: this will have an |
S |
List of smoothing penalty matrices (in smallest non-zero submatrix form). |
irng |
A vector of scaling factors that have been applied to the model matrix, to ensure nice conditioning. |
In addition there are all the elements usually added by smooth.construct
methods.
WARNINGS
Soap film smooths are quite specialized, and require more setup than most smoothers (e.g. you have to supply the boundary and the interior knots, plus the boundary smooth basis dimension(s)). It is worth looking at the reference.
Author(s)
Simon N. Wood simon.wood@r-project.org
References
Wood, S.N., M.V. Bravington and S.L. Hedley (2008) "Soap film smoothing", J.R.Statist.Soc.B 70(5), 931-955. doi:10.1111/j.1467-9868.2008.00665.x
https://www.maths.ed.ac.uk/~swood34/
See Also
Examples
require(mgcv)
##########################
## simple test function...
##########################
fsb <- list(fs.boundary())
nmax <- 100
## create some internal knots...
knots <- data.frame(v=rep(seq(-.5,3,by=.5),4),
w=rep(c(-.6,-.3,.3,.6),rep(8,4)))
## Simulate some fitting data, inside boundary...
set.seed(0)
n<-600
v <- runif(n)*5-1;w<-runif(n)*2-1
y <- fs.test(v,w,b=1)
names(fsb[[1]]) <- c("v","w")
ind <- inSide(fsb,x=v,y=w) ## remove outsiders
y <- y + rnorm(n)*.3 ## add noise
y <- y[ind];v <- v[ind]; w <- w[ind]
n <- length(y)
par(mfrow=c(3,2))
## plot boundary with knot and data locations
plot(fsb[[1]]$v,fsb[[1]]$w,type="l");points(knots,pch=20,col=2)
points(v,w,pch=".");
## Now fit the soap film smoother. 'k' is dimension of boundary smooth.
## boundary supplied in 'xt', and knots in 'knots'...
nmax <- 100 ## reduced from default for speed.
b <- gam(y~s(v,w,k=30,bs="so",xt=list(bnd=fsb,nmax=nmax)),knots=knots)
plot(b) ## default plot
plot(b,scheme=1)
plot(b,scheme=2)
plot(b,scheme=3)
vis.gam(b,plot.type="contour")
################################
# Fit same model in two parts...
################################
par(mfrow=c(2,2))
vis.gam(b,plot.type="contour")
b1 <- gam(y~s(v,w,k=30,bs="sf",xt=list(bnd=fsb,nmax=nmax))+
s(v,w,k=30,bs="sw",xt=list(bnd=fsb,nmax=nmax)) ,knots=knots)
vis.gam(b,plot.type="contour")
plot(b1)
##################################################
## Now an example with known boundary condition...
##################################################
## Evaluate known boundary condition at boundary nodes...
fsb[[1]]$f <- fs.test(fsb[[1]]$v,fsb[[1]]$w,b=1,exclude=FALSE)
## Now fit the smooth...
bk <- gam(y~s(v,w,bs="so",xt=list(bnd=fsb,nmax=nmax)),knots=knots)
plot(bk) ## default plot
##########################################
## tensor product example...
##########################################
set.seed(9)
n <- 10000
v <- runif(n)*5-1;w<-runif(n)*2-1
t <- runif(n)
y <- fs.test(v,w,b=1)
y <- y + 4.2
y <- y^(.5+t)
fsb <- list(fs.boundary())
names(fsb[[1]]) <- c("v","w")
ind <- inSide(fsb,x=v,y=w) ## remove outsiders
y <- y[ind];v <- v[ind]; w <- w[ind]; t <- t[ind]
n <- length(y)
y <- y + rnorm(n)*.05 ## add noise
knots <- data.frame(v=rep(seq(-.5,3,by=.5),4),
w=rep(c(-.6,-.3,.3,.6),rep(8,4)))
## notice NULL element in 'xt' list - to indicate no xt object for "cr" basis...
bk <- gam(y~ te(v,w,t,bs=c("sf","cr"),k=c(25,4),d=c(2,1),
xt=list(list(bnd=fsb,nmax=nmax),NULL))+
te(v,w,t,bs=c("sw","cr"),k=c(25,4),d=c(2,1),
xt=list(list(bnd=fsb,nmax=nmax),NULL)),knots=knots)
par(mfrow=c(3,2))
m<-100;n<-50
xm <- seq(-1,3.5,length=m);yn<-seq(-1,1,length=n)
xx <- rep(xm,n);yy<-rep(yn,rep(m,n))
tru <- matrix(fs.test(xx,yy),m,n)+4.2 ## truth
image(xm,yn,tru^.5,col=heat.colors(100),xlab="v",ylab="w",
main="truth")
lines(fsb[[1]]$v,fsb[[1]]$w,lwd=3)
contour(xm,yn,tru^.5,add=TRUE)
vis.gam(bk,view=c("v","w"),cond=list(t=0),plot.type="contour")
image(xm,yn,tru,col=heat.colors(100),xlab="v",ylab="w",
main="truth")
lines(fsb[[1]]$v,fsb[[1]]$w,lwd=3)
contour(xm,yn,tru,add=TRUE)
vis.gam(bk,view=c("v","w"),cond=list(t=.5),plot.type="contour")
image(xm,yn,tru^1.5,col=heat.colors(100),xlab="v",ylab="w",
main="truth")
lines(fsb[[1]]$v,fsb[[1]]$w,lwd=3)
contour(xm,yn,tru^1.5,add=TRUE)
vis.gam(bk,view=c("v","w"),cond=list(t=1),plot.type="contour")
#############################
# nested boundary example...
#############################
bnd <- list(list(x=0,y=0),list(x=0,y=0))
seq(0,2*pi,length=100) -> theta
bnd[[1]]$x <- sin(theta);bnd[[1]]$y <- cos(theta)
bnd[[2]]$x <- .3 + .3*sin(theta);
bnd[[2]]$y <- .3 + .3*cos(theta)
plot(bnd[[1]]$x,bnd[[1]]$y,type="l")
lines(bnd[[2]]$x,bnd[[2]]$y)
## setup knots
k <- 8
xm <- seq(-1,1,length=k);ym <- seq(-1,1,length=k)
x=rep(xm,k);y=rep(ym,rep(k,k))
ind <- inSide(bnd,x,y)
knots <- data.frame(x=x[ind],y=y[ind])
points(knots$x,knots$y)
## a test function
f1 <- function(x,y) {
exp(-(x-.3)^2-(y-.3)^2)
}
## plot the test function within the domain
par(mfrow=c(2,3))
m<-100;n<-100
xm <- seq(-1,1,length=m);yn<-seq(-1,1,length=n)
x <- rep(xm,n);y<-rep(yn,rep(m,n))
ff <- f1(x,y)
ind <- inSide(bnd,x,y)
ff[!ind] <- NA
image(xm,yn,matrix(ff,m,n),xlab="x",ylab="y")
contour(xm,yn,matrix(ff,m,n),add=TRUE)
lines(bnd[[1]]$x,bnd[[1]]$y,lwd=2);lines(bnd[[2]]$x,bnd[[2]]$y,lwd=2)
## Simulate data by noisy sampling from test function...
set.seed(1)
x <- runif(300)*2-1;y <- runif(300)*2-1
ind <- inSide(bnd,x,y)
x <- x[ind];y <- y[ind]
n <- length(x)
z <- f1(x,y) + rnorm(n)*.1
## Fit a soap film smooth to the noisy data
nmax <- 60
b <- gam(z~s(x,y,k=c(30,15),bs="so",xt=list(bnd=bnd,nmax=nmax)),
knots=knots,method="REML")
plot(b) ## default plot
vis.gam(b,plot.type="contour") ## prettier version
## trying out separated fits....
ba <- gam(z~s(x,y,k=c(30,15),bs="sf",xt=list(bnd=bnd,nmax=nmax))+
s(x,y,k=c(30,15),bs="sw",xt=list(bnd=bnd,nmax=nmax)),
knots=knots,method="REML")
plot(ba)
vis.gam(ba,plot.type="contour")