Title: | B-Spline Interpolation and Regression |
---|---|
Description: | Build and use B-splines for interpolation and regression. In case of regression, equality constraints as well as monotonicity and/or positivity of B-spline weights can be imposed. Moreover, knot positions (not only spline weights) can be part of optimized parameters too. For this end, 'bspline' is able to calculate Jacobian of basis vectors as function of knot positions. User is provided with functions calculating spline values at arbitrary points. These functions can be differentiated and integrated to obtain B-splines calculating derivatives/integrals at any point. B-splines of this package can simultaneously operate on a series of curves sharing the same set of knots. 'bspline' is written with concern about computing performance that's why the basis and Jacobian calculation is implemented in C++. The rest is implemented in R but without notable impact on computing speed. |
Authors: | Serguei Sokol <[email protected]> |
Maintainer: | Serguei Sokol <[email protected]> |
License: | GPL-2 |
Version: | 2.2.2 |
Built: | 2024-10-30 05:00:35 UTC |
Source: | https://github.com/mathscell/bspline |
nD B-curve governed by (x,y,...) control points.
bcurve(xy, n = 3)
bcurve(xy, n = 3)
xy |
Real matrix of (x,y,...) coordinates, one control point per row. |
n |
Integer scalar, polynomial order of B-spline (3 by default) |
The curve will pass by the first and the last points in 'xy'. The tangents at the first and last points will coincide with the first and last segments of control points. Example of signature is inspired from this blog.
Function of one argument calculating B-curve. The argument is supposed to be in [0, 1] interval.
# simulate doctor's signature ;) set.seed(71); xy=matrix(rnorm(16), ncol=2) tp=seq(0,1,len=301) doc_signtr=bcurve(xy) plot(doc_signtr(tp), t="l", xaxt='n', yaxt='n', ann=FALSE, frame.plot=FALSE, xlim=range(xy[,1]), ylim=range(xy[,2])) # see where control points are text(xy, labels=seq(nrow(xy)), col=rgb(0, 0, 0, 0.25)) # join them by segments lines(bcurve(xy, n=1)(tp), col=rgb(0, 0, 1, 0.25)) # randomly curved wire in 3D space ## Not run: if (requireNamespace("rgl", quietly=TRUE)) { xyz=matrix(rnorm(24),ncol=3) tp=seq(0,1,len=201) curv3d=bcurve(xyz) rgl::plot3d(curv3d(tp), t="l", decorate=FALSE) } ## End(Not run)
# simulate doctor's signature ;) set.seed(71); xy=matrix(rnorm(16), ncol=2) tp=seq(0,1,len=301) doc_signtr=bcurve(xy) plot(doc_signtr(tp), t="l", xaxt='n', yaxt='n', ann=FALSE, frame.plot=FALSE, xlim=range(xy[,1]), ylim=range(xy[,2])) # see where control points are text(xy, labels=seq(nrow(xy)), col=rgb(0, 0, 0, 0.25)) # join them by segments lines(bcurve(xy, n=1)(tp), col=rgb(0, 0, 1, 0.25)) # randomly curved wire in 3D space ## Not run: if (requireNamespace("rgl", quietly=TRUE)) { xyz=matrix(rnorm(24),ncol=3) tp=seq(0,1,len=201) curv3d=bcurve(xyz) rgl::plot3d(curv3d(tp), t="l", decorate=FALSE) } ## End(Not run)
This function is analogous but not equivalent to splines:bs()
and splines2::bSpline()
.
It is also several times faster.
bsc(x, xk, n = 3L, cjac = FALSE)
bsc(x, xk, n = 3L, cjac = FALSE)
x |
Numeric vector, abscissa points |
xk |
Numeric vector, knots |
n |
Integer scalar, polynomial order (3 by default) |
cjac |
Logical scalar, if |
For n==0, step function is defined as constant on each interval
[xk[i]; xk[i+1][
, i.e. closed on the left and open on the right
except for the last interval which is closed on the right too. The
Jacobian for step function is considered 0 in every x point even if
in points where x=xk, the derivative is not defined.
For n==1, Jacobian is discontinuous in such points so for
these points we take the derivative from the right.
Numeric matrix (for cjac=FALSE), each column correspond to a B-spline calculated on x; or List (for cjac=TRUE) with components
basis matrix of dimension nx x nw
, where nx is the length
of x and nw=nk-n-1
is the number of basis vectors
array of dimension nx x (n+2) x nw
where n+2
is the number of support knots for each basis vector
[splines::bs()], [splines2::bSpline()]
x=seq(0, 5, length.out=101) # cubic basis matrix n=3 m=bsc(x, xk=c(rep(0, n+1), 1:4, rep(5, n+1)), n=n) matplot(x, m, t="l") stopifnot(all.equal.numeric(c(m), c(splines::bs(x, knots = 1:4, degree = n, intercept = TRUE))))
x=seq(0, 5, length.out=101) # cubic basis matrix n=3 m=bsc(x, xk=c(rep(0, n+1), 1:4, rep(5, n+1)), n=n) matplot(x, m, t="l") stopifnot(all.equal.numeric(c(m), c(splines::bs(x, knots = 1:4, degree = n, intercept = TRUE))))
Calculate B-spline values from their coefficients qw and knots xk
bsp(x, xk, qw, n = 3L)
bsp(x, xk, qw, n = 3L)
x |
Numeric vector, abscissa points at which B-splines should be calculated. They are supposed to be non decreasing. |
xk |
Numeric vector, knots of the B-splines. They are supposed to be non decreasing. |
qw |
Numeric vector or matrix, coefficients of B-splines. |
n |
Integer scalar, polynomial order of B-splines, by default cubic splines are calculated. |
This function does nothing else than calculate a dot-product between
a B-spline basis matrix calculated by bsc()
and coefficients qw
.
If qw is a matrix, each
column corresponds to a separate set of coefficients.
For x values falling outside of xk range, the B-splines values are set to 0.
To get a function calculating spline values at arbitrary points from xk
and qw
, cf. par2bsp()
.
Numeric matrix (column number depends on qw dimensions), B-spline values on x.
[bsc], [par2bsp]
Build and use B-splines for interpolation and regression. In case of regression, equality constraints as well as monotonicity requirement can be imposed. Moreover, knot positions (not only spline coefficients) can be part of optimized parameters too. User is provided with functions calculating spline values at arbitrary points. This functions can be differentiated to obtain B-splines calculating derivatives at any point. B-splines of this package can simultaneously operate on a series of curves sharing the same set of knots. 'bspline' is written with concern about computing performance that's why the basis calculation is implemented in C++. The rest is implemented in R but without notable impact on computing speed.
bsc
:"basis matrix (implemented in C++)
bsp
:"values of B-spline from its coefficients
dbsp
:"derivative of B-spline
par2bsp
:"build B-spline function from parameters
bsppar
:"retrieve B-spline parameters from its function
smbsp
:"build smoothing B-spline
fitsmbsp
:"build smoothing B-spline with optimized knot positions
diffn
:"finite differences
Useful links:
Retrieve parameters of B-splines
bsppar(f)
bsppar(f)
f |
Function, B-splines such that returned by par3bsp(), smbsp(), ... |
List having components: n - polynomial order, qw - coefficients, xk - knots
Derivative of B-spline
dbsp(f, nderiv = 1L, same_xk = FALSE)
dbsp(f, nderiv = 1L, same_xk = FALSE)
f |
Function, B-spline such as returned by |
nderiv |
Integer scalar >= 0, order of derivative to calculate (1 by default) |
same_xk |
Logical scalar, if TRUE, indicates to calculate derivative on the same knot grid as original function. In this case, coefficient number will be incremented by 2. Otherwise, extreme knots are removed on each side of the grid and coefficient number is maintained (FALSE by default). |
Function calculating requested derivative
x=seq(0., 1., length.out=11L) y=sin(2*pi*x) f=smbsp(x, y, nki=2L) d_f=dbsp(f) xf=seq(0., 1., length.out=101) # fine grid for plotting plot(xf, d_f(xf)) # derivative estimated by B-splines lines(xf, 2.*pi*cos(2*pi*xf), col="blue") # true derivative xk=bsppar(d_f)$xk points(xk, d_f(xk), pch="x", col="red") # knot positions
x=seq(0., 1., length.out=11L) y=sin(2*pi*x) f=smbsp(x, y, nki=2L) d_f=dbsp(f) xf=seq(0., 1., length.out=101) # fine grid for plotting plot(xf, d_f(xf)) # derivative estimated by B-splines lines(xf, 2.*pi*cos(2*pi*xf), col="blue") # true derivative xk=bsppar(d_f)$xk points(xk, d_f(xk), pch="x", col="red") # knot positions
Calculate dy/dx where x,y are first and the rest of columns in the entry matrix 'm'
diffn(m, ndiff = 1L)
diffn(m, ndiff = 1L)
m |
2- or more-column numeric matrix |
ndiff |
Integer scalar, order of finite difference (1 by default) |
Numeric matrix, first column is midpoints of x, the second and following are dy/dx
Calculate matrix for obtaining coefficients of first-derivative B-spline.
They can be calculated as dqw=Md %*% qw
. Here, dqw are coefficients
of the first derivative,
Md is the matrix returned by this function, and qw are the coefficients
of differentiated B-spline.
dmat(nqw = NULL, xk = NULL, n = NULL, f = NULL, same_xk = FALSE)
dmat(nqw = NULL, xk = NULL, n = NULL, f = NULL, same_xk = FALSE)
nqw |
Integer scalar, row number of qw matrix (i.e. degree of freedom of a B-spline) |
xk |
Numeric vector, knot positions |
n |
Integer scalar, B-spline polynomial order |
f |
Function from which previous parameters can be retrieved. If both f and any of previous parameters are given then explicitly set parameters take precedence over those retrieved from f. |
same_xk |
Logical scalar, the same meaning as in |
Numeric matrix of size nqw-1 x nqw
Indefinite integral of B-spline
ibsp(f, const = 0, nint = 1L)
ibsp(f, const = 0, nint = 1L)
f |
Function, B-spline such as returned by |
const |
Numeric scalar or vector of length |
nint |
Integer scalar >= 0, defines how many times to take integral (1 by default) |
If f is B-spline, then following identity is held: Dbsp(ibsp(f)) is identical to f. Generally, it does not work in the other sens: ibsp(Dbsp(f)) is not f but not very far. If we can get an appropriate constant C=f(min(x)) then we can assert that ibsp(Dbsp(f), const=C) is the same as f.
Function calculating requested integral
Normalized total variation of n-th finite differences is calculated for each column in
y
then averaged. These averaged values are fitted by a linear spline to
find knot positions that equalize the jumps of n-th derivative.
NB. This function is used internally in (fit)smbsp()
and a priori
has no interest to be called directly by user.
iknots(x, y, nki = 1L, n = 3L)
iknots(x, y, nki = 1L, n = 3L)
x |
Numeric vector |
y |
Numeric vector or matrix |
nki |
Integer scalar, number of internal knots to estimate (1 by default) |
n |
Integer scalar, polynomial order of B-spline (3 by default) |
Numeric vector, estimated knot positions
Find first and last+1 indexes iip s.t. x[iip] belongs to interval starting at xk[iik]
ipk(x, xk)
ipk(x, xk)
x |
Numeric vector, abscissa points (must be non decreasing) |
xk |
Numeric vector, knots (must be non decreasing) |
Integer matrix of size (2 x length(xk)-1)
. Indexes are 0-based
Knot Jacobian of B-spline with weights
jacw(jac, qws)
jacw(jac, qws)
jac |
Numeric array, such as returned by |
qws |
Numeric matrix, each column is a set of weights forming a B-spline. If qws is a vector, it is coerced to 1-column matrix. |
Numeric array of size nx x ncol(qw) x nk
, where nx=dim(jac)[1]
and nk is the number of knots dim(jac)[3]+n+1
(n being polynomial order).
Convert parameters to B-spline function
par2bsp(n, qw, xk, covqw = NULL, sdy = NULL, sdqw = NULL)
par2bsp(n, qw, xk, covqw = NULL, sdy = NULL, sdqw = NULL)
n |
Integer scalar, polynomial order of B-splines |
qw |
Numeric vector or matrix, coefficients of B-splines, one set per column in case of matrix |
xk |
Numeric vector, knots |
covqw |
Numeric Matrix, covariance matrix of qw (can be estimated in |
sdy |
Numeric vector, SD of each y column (can be estimated in |
sdqw |
Numeric Matrix, SD of qw thus having the same dimension
as qw (can be estimated in |
Function, calculating B-splines at arbitrary points and having
interface f(x, select)
where x
is a vector of abscissa points.
Parameter select
is passed to
qw[, select, drop=FALSE]
and can be missing. This function will return
a matrix of size length(x) x ncol(qw)
if select
is missing. Elsewhere,
a number of column will depend on select
parameter. Column names in
the result matrix will be inherited from qw
.
Polynomial formulation of B-spline
parr(xk, n = 3L)
parr(xk, n = 3L)
xk |
Numeric vector, knots |
n |
Integer scalar, polynomial order (3 by default) |
Numeric 3D array, the first index runs through n+1 polynomial coefficients; the second – through n+1 supporting intervals; and the last one through nk-n-1 B-splines (here nk=length(xk)). Knot interval of length 0 will have corresponding coefficients set to 0.
Polynomial B-spline Calculation of Basis Matrix
pbsc(x, xk, coeffs)
pbsc(x, xk, coeffs)
x |
Numeric,vector, abscissa points |
xk |
Numeric vector, knots |
coeffs |
Numeric 3D array, polynomial coefficients such as calculated by |
Polynomials are calculated recursively by Cox-de Boor formula. However, it is not applied to
final values but to polynomial coefficients. Multiplication by a linear functions gives
a raise of polynomial degree by 1.
Polynomial coefficients stored in the first dimension of coeffs
are used as in
the following formula p[1]*x^n + p[1]*x^(n-1) + ... + p[n+1]
.
Resulting matrix is the same as returned by bsc(x, xk, n=dim(coeffs)[1]-1)
Numeric matrix, basis vectors, one per column. Row number is length(x)
.
n=3 x=seq(0, 5, length.out=101) xk=c(rep(0, n+1), 1:4, rep(5, n+1)) # cubic polynomial coefficients coeffs=parr(xk) # basis matrix m=pbsc(x, xk, coeffs) matplot(x, m, t="l") stopifnot(all.equal.numeric(c(m), c(bsc(x, xk))))
n=3 x=seq(0, 5, length.out=101) xk=c(rep(0, n+1), 1:4, rep(5, n+1)) # cubic polynomial coefficients coeffs=parr(xk) # basis matrix m=pbsc(x, xk, coeffs) matplot(x, m, t="l") stopifnot(all.equal.numeric(c(m), c(bsc(x, xk))))
Optimize smoothing B-spline coefficients (smbsp) and knot positions (fitsmbsp) such that residual squared sum is minimized for all y columns.
smbsp( x, y, n = 3L, xki = NULL, nki = 1L, lieq = NULL, monotone = 0, positive = 0, mat = NULL, estSD = FALSE, tol = 1e-10 ) fitsmbsp( x, y, n = 3L, xki = NULL, nki = 1L, lieq = NULL, monotone = 0, positive = 0, control = list(), estSD = FALSE, tol = 1e-10 )
smbsp( x, y, n = 3L, xki = NULL, nki = 1L, lieq = NULL, monotone = 0, positive = 0, mat = NULL, estSD = FALSE, tol = 1e-10 ) fitsmbsp( x, y, n = 3L, xki = NULL, nki = 1L, lieq = NULL, monotone = 0, positive = 0, control = list(), estSD = FALSE, tol = 1e-10 )
x |
Numeric vector, abscissa points |
y |
Numeric vector or matrix or data.frame, ordinate values to be smoothed (one set per column in case of matrix or data.frame) |
n |
Integer scalar, polynomial order of B-splines (3 by default) |
xki |
Numeric vector, strictly internal B-spline knots, i.e. lying strictly
inside of |
nki |
Integer scalar, internal knot number (1 by default). When
nki==0, it corresponds to polynomial regression. If |
lieq |
List, equality constraints to respect by the smoothing spline,
one list item per y column. By default (NULL), no constraint is imposed.
Constraints are given as a 2-column matrix |
monotone |
Numeric scalar or vector, if |
positive |
Numeric scalar, if |
mat |
Numeric matrix of basis vectors, if NULL it is recalculated
by |
estSD |
Logical scalar, if TRUE, indicates to calculate: SD of each y column, covariance matrix and SD of spline coefficients. All these values can be retrieved with bsppar() call (FALSE by default). These estimations are made under assumption that all y points have uncorrelated noise. Optional constraints are not taken into account of SD. |
tol |
Numerical scalar, relative tolerance for small singular values
that should be considered as 0 if |
control |
List, passed through to |
If constraints are set, we use nlsic::lsie_ln()
to solve a
least squares
problem with equality constraints in least norm sens for each y column.
Otherwise, nlsic::ls_ln_svd()
is used for the whole y matrix.
The solution of least squares problem is a vector of B-splines coefficients qw
,
one vector per y
column. These vectors are used to define B-spline function
which is returned as the result.
NB. When nki >= length(x)-n-1
(be it from direct setting or calculated
from length(xki)
), it corresponds
to spline interpolation, i.e. the resulting spline will pass
exactly by (x,y) points (well, up to numerical precision).
Border and external knots are fixed, only strictly internal knots can move during optimization. The optimization process is constrained to respect a minimal distance between knots as well as to bound them to x range. This is done to avoid knots getting unsorted during iterations and/or going outside of a meaningful range.
Function, smoothing B-splines
respecting optional constraints (generated by par2bsp()
).
bsppar
for retrieving parameters of B-spline functions; par2bsp
for generating B-spline function; iknots
for estimation of knot positions
x=seq(0, 1, length.out=11) y=sin(pi*x)+rnorm(x, sd=0.1) # constraint B-spline to be 0 at the interval ends fsm=smbsp(x, y, nki=1, lieq=list(rbind(c(0, 0), c(1, 0)))) # check parameters of found B-splines bsppar(fsm) plot(x, y) # original "measurements" # fine grained x xfine=seq(0, 1, length.out=101) lines(xfine, fsm(xfine)) # fitted B-splines lines(xfine, sin(pi*xfine), col="blue") # original function # visualize knot positions xk=bsppar(fsm)$xk points(xk, fsm(xk), pch="x", col="red") # fit broken line with linear B-splines x1=seq(0, 1, length.out=11) x2=seq(1, 3, length.out=21) x3=seq(3, 4, length.out=11) y1=x1+rnorm(x1, sd=0.1) y2=-2+3*x2+rnorm(x2, sd=0.1) y3=4+x3+rnorm(x3, sd=0.1) x=c(x1, x2, x3) y=c(y1, y2, y3) plot(x, y) f=fitsmbsp(x, y, n=1, nki=2) lines(x, f(x))
x=seq(0, 1, length.out=11) y=sin(pi*x)+rnorm(x, sd=0.1) # constraint B-spline to be 0 at the interval ends fsm=smbsp(x, y, nki=1, lieq=list(rbind(c(0, 0), c(1, 0)))) # check parameters of found B-splines bsppar(fsm) plot(x, y) # original "measurements" # fine grained x xfine=seq(0, 1, length.out=101) lines(xfine, fsm(xfine)) # fitted B-splines lines(xfine, sin(pi*xfine), col="blue") # original function # visualize knot positions xk=bsppar(fsm)$xk points(xk, fsm(xk), pch="x", col="red") # fit broken line with linear B-splines x1=seq(0, 1, length.out=11) x2=seq(1, 3, length.out=21) x3=seq(3, 4, length.out=11) y1=x1+rnorm(x1, sd=0.1) y2=-2+3*x2+rnorm(x2, sd=0.1) y3=4+x3+rnorm(x3, sd=0.1) x=c(x1, x2, x3) y=c(y1, y2, y3) plot(x, y) f=fitsmbsp(x, y, n=1, nki=2) lines(x, f(x))