smooth.spline与bSpline差异及smooth.spline三类技术问题咨询
Hi there, let's break down your questions about smooth.spline() and bSpline() step by step:
smooth.spline() and bSpline() First, let's clarify their fundamental purposes:
smooth.spline()is a nonparametric smoothing tool: it automatically adjusts the curve's smoothness (or lets you control it via thesparparameter) to balance fitting the data and avoiding overfitting. It handles knot selection and curve fitting in one integrated step.bSpline()is a B-spline basis constructor: it doesn't fit data on its own—instead, it generates a matrix of B-spline basis functions. You need to pair this matrix with a linear model (likelm()) to perform a parametric spline fit.
smooth.spline() (1) Why aren't the knots generated by smooth.spline() evenly distributed?
smooth.spline() uses adaptive knot placement by default. Instead of spacing knots uniformly, it places them more densely in regions where the data changes rapidly (e.g., steep slopes, high variance) and more sparsely in flat, stable regions. This adaptive approach lets it capture subtle data trends far better than rigid uniform knots would.
If you force uniform knots, you could manually specify the knots parameter, but that defeats the purpose of smooth.spline()'s adaptive design. In your example, using nknots=9 still triggers adaptive selection based on your data's distribution, not uniform spacing.
You can verify this with your code:
s2$fit$knot # Expected: 1:7/8 (uniform), but actual is adaptive diff(s2$fit$knot) # Shows uneven spacing
(2) What type of spline does smooth.spline() use? And why do curves match closely (when spar=0) but parameters differ from a linear model with bSpline()?
- Spline type: When
spar=0,smooth.spline()performs cubic spline interpolation (the curve passes through every data point). For non-zerospar, it's a penalized cubic spline (smoothing spline) that balances fit and smoothness by penalizing high curvature. - Parameter discrepancy: Even though the curves overlap almost perfectly, the coefficients differ because the two methods use different basis function representations.
smooth.spline()uses its own internal basis (e.g., for interpolating or penalized splines), whilebSpline()generates standard B-spline basis functions. These bases are linearly equivalent—meaning they can represent the exact same curve—but their coefficient values will differ because they're just different "coordinate systems" for the same spline curve.
You can visualize this overlap and compare coefficients with your code:
# Plot to compare curves ggplot() + geom_point(data=data, aes(x=x, y=y18)) + geom_line(aes(x=p$x, y=p$y)) + geom_line(aes(x=bs100$x, y=bs100$y), color=2) + scale_x_continuous(labels = scales::percent) + geom_vline(xintercept=s2$fit$knot, lty=2, color="grey") # Compare coefficients and their differences cbind(s2$fit$coef, lm_y18$coefficients, s2$fit$coef - lm_y18$coefficients)
(3) How to get a spline matrix from smooth.spline() that matches the output format of bSpline()?
smooth.spline() doesn't directly output a B-spline basis matrix, but you can replicate the format by following these steps:
- Extract knots from
smooth.spline(): From your fit object, pull the internal knots (note:s2$fit$knotincludes repeated boundary knots, so you'll want to extract the unique inner knots—likes2$fit$knot[5:11]in your example). - Generate B-spline basis matrix: Use
bSpline()with the samexvalues, extracted knots,degree=3(sincesmooth.spline()uses cubic splines), and matching intercept settings.
Here's how that looks with your example code:
# Extract knots from smooth.spline fit extracted_knots <- s2$fit$knot[5:11] # Generate B-spline matrix for original data bsx <- data.frame(bSpline(x=data$x, knots=extracted_knots, degree=3, intercept=FALSE)) # Generate B-spline matrix for prediction grid bs100 <- data.frame(bSpline(x=0:100/100, knots=extracted_knots, degree=3, intercept=FALSE)) bs100 <- data.frame(x=0:100/100, bs100)
Keep in mind: If your smooth.spline() fit uses penalized splines (non-zero spar), you may need to adjust the B-spline model to include the same penalty (e.g., using glmnet() instead of lm()) to get fully matching results.
Full Example Code
Example from bSpline Documentation (Modified for Your Questions)
y18 <- c(1:3, 5, 4, 7:3, 2*(2:5), rep(10, 4)) data <- data.frame(y18, x=seq(0,1,length=18)) xx <- seq(0, 1, len = 201) s2 <- smooth.spline(y=data$y18, x=data$x, nknots = 9, spar=0 ) p <- predict(s2, xx) bsx <- data.frame(bSpline(x=data$x , knots=s2$fit$knot[5:11], degree=3, intercept = FALSE)) data <- data.frame(data, bsx) bs100 <- data.frame(bSpline(x=0:100/100, knots=s2$fit$knot[5:11], degree=3, intercept = FALSE)) bs100 <- data.frame(x=0:100/100, bs100) lm_y18 <- lm(data=data[,-2], y18 ~ .) bs100$y <- predict(lm_y18, bs100)
Code for Question 1 (Knot Spacing)
s2$fit$knot # Expected result: 1:7/8 diff(s2$fit$knot)
Code for Question 2 (Curve & Coefficient Comparison)
ggplot() + geom_point(data=data, aes(x=x, y=y18)) + geom_line(aes(x=p$x, y=p$y)) + geom_line(aes(x=bs100$x, y=bs100$y), color=2) + scale_x_continuous(labels = scales::percent) + geom_vline(xintercept=s2$fit$knot, lty=2, color="grey") # Compare coefficient differences cbind(s2$fit$coef, lm_y18$coefficients, s2$fit$coef - lm_y18$coefficients)
内容的提问来源于stack exchange,提问作者ThomasC

