How are knots placed in periodic splines in splines2 package?

I am interested in understanding how does the splines2 library from R place the knots when computing periodic splines. I understand the logic when splines are not periodic, for example, I know that if the knots are not specified, the number of knots is equal to

$$knots = df - degree$$

where 'df' refers to the degrees of freedom of the spline basis, and 'degree' refers to the degree of the polinomial, and they are placed on the quantiles. For example:

library(splines2)
library(splines)

df = 8
degree = 3
x = seq(0, 1, length.out=15)

knots = seq(0, 1, length.out = df - degree + 2)
knots = knots[2:(length(knots)-1)]
# df - degree = length(knots)

spline_basis1 =  splines2::mSpline(x, degree=degree, df=df)
spline_basis2 =  splines2::mSpline(x, degree=degree, knots=knots)


spline_basis1 and spline_basis2 will return the same result. But if I consider periodic splines, this is no longer true:

# Periodic splines
spline_basis1 =  splines2::mSpline(x, degree=degree, df=df, periodic=T)
spline_basis2 =  splines2::mSpline(x, degree=degree, knots=knots, periodic=T)


My guess is that when computing periodic splines, the number of parameters to be estimated (the degrees of freedom) is different, but I would like to understand how are knots placed by default when splines are periodic.

• Doesn't the documentation answer that?
– whuber
Commented Jul 12, 2021 at 12:45
• The documentation (cran.r-project.org/web/packages/splines2/splines2.pdf) explains the number of knots chosen but not where they are placed. I took them based on the quantiles but obtain a different solution than the default one. Commented Jul 12, 2021 at 14:26
• Thanks -- I see the documentation only states "... at suitable quantiles... ." The best way to obtain an answer is to inspect the source code.
– whuber
Commented Jul 12, 2021 at 14:30

1 Answer

Your guess is correct. The relationship between the df and knots is df = length(knots) + as.integer(intercept) for periodic splines that implemented in splines2::mSpline(). For B-splines implemented in splines::bs() or splines2::bSpline(), the relationship is df = length(knots) + degree + as.integer(intercept).

See the following code for comparison:

df <- 8
degree <- 3
x <- seq(0, 1, length.out = 15)
x2 <- x %% 1       # get x in the cyclic period [0, 1)
intercept <- FALSE # default, the first basis function excluded

## set internal knots
knots <- quantile(x2, probs = seq(0, 1, length.out = df + 1 + (! intercept)))
(knots <- knots[- c(1, length(knots))])
#>  11.11111%  22.22222%  33.33333%  44.44444%  55.55556%  66.66667%  77.77778%
#> 0.03968254 0.15079365 0.26190476 0.37301587 0.48412698 0.59523810 0.70634921
#>  88.88889%
#> 0.81746032

spline_basis1 =  splines2::mSpline(x, degree = degree, df = df,
periodic = TRUE, intercept = intercept)
spline_basis2 =  splines2::mSpline(x, degree = degree, knots = knots,
periodic = TRUE, intercept = intercept)

knots(spline_basis1)
#> [1] 0.03968254 0.15079365 0.26190476 0.37301587 0.48412698 0.59523810 0.70634921
#> [8] 0.81746032

all.equal(spline_basis1, spline_basis2)
#> [1] TRUE