As I was working out how Epi generates the basis for its spline functions (via the function Ns
), I was a little confused by how it handles the detrend
argument.
When detrend=T
I would have expected that Epi::Ns(...)
would more or less project the basis given by splines::ns(...)
onto the orthogonal complement of the column space of [1 t]
and finally extract the set of linearly independent columns (so that we have a basis).
However, this doesn’t appear to be the exactly the case; I tried
library(Epi)
x=seq(-0.75, 0.75, length.out=5)
Ns(x, knots=c(-0.5,0,0.5), Boundary.knots=c(-1,1), detrend=T)
and
library(splines)
detrend(ns(x, knots=c(-0.5,0,0.5), Boundary.knots=c(-1,1)), x)
The matrices produced by the above code are not the same, however, they do have the same column space (in this example) suggesting that if plugged in to a linear model, the fitted coefficients will be different but the fit (itself) will be the same.
The first question I had was; is this true in general?
The second question is why are the two different?
Regarding the second question - when detrend
is specified, Epi::Ns
gives a warning that fixsl
is ignored.
Diving into Epi github NS.r ... in the construction of the basis, in the call to Epi::Ns
above with detrend=T
, the worker ns.ld()
is called (a function almost identical to the guts of splines::ns()
), which passes c(NA,NA)
along to splines::spline.des
as the derivs
argument in determining a matrix const
;
const <- splines::spline.des( Aknots, Boundary.knots, 4, c(2-fixsl[1],2-fixsl[2]))$design
This is the difference between what happens in Ns(detrend=T)
and the call to ns()
above which passes c(2,2)
to splineDesign as the derivs
argument.
So that explains how they are different, but not why? Does anyone have an explanation for why fixsl=c(NA,NA)
is used instead of fixsl=c(F,F)
in Epi::Ns()
?
And does anyone have a proof/or an answer to the first question?
I think the orthogonal complement of const
's column space is used so that second (or desired) derivatives are zero at the boundary (via projection of the general spline basis) - but I'm not sure about this step as I haven't dug into the mathematics, I'm just going by my 'feel' for it. Perhaps if I understood this better, the reason that the differences in the result for const
from the call to splineDesign
/spline.des
(in ns()
and Ns()
respectively) would explain why the two matrices from the start are not the same, yet yield the same fit.
The fixsl=c(NA,NA)
was a bug that has been fixed since a while. See the commits on the CRAN Github mirror.
I have still sent an email to the maintainer to ask if the fix could be made a little bit more consistent with the condition, but in principle this could be closed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With