How does one interpret the output of using a s() term in brms?

I am trying to get a handle on using s() terms in brms. I have read around the forum and various helpful places like Gavin SImpson’s blog, but I am still having trouble understanding the output from a model in brms where I use an s() term.
I understand that the sds() is a varying effect that controls the ‘wiggliness’ of the spline. The s term in the ‘Population-level’ effects is the completely smooth part. But what does this mean?? Is there any directly interpretable meaning from the numbers in the model output?
Below is a very simple example.

time<-seq(from=0, to=10, by=0.1)
a <- 10
b <- -1
mu = a + b*time^3
outcome <- rnorm(101, mu, 40)
data <- cbind(time, outcome)
data <- data.frame(data)

plot(outcome ~ time, data=data)

m.s <- brm(outcome ~ s(time, k=20), data=data, cores=4)

Here is the resulting output in case you don’t want to run the code for yourself, along with some plots of the data and conditional_smooths() from the model fit.
spline model output

‘stime_1’ estimate is -3300. Does this have any interpretable meaning just by looking at this output?
‘sds(stime_1)’ estimate is 302, which I take as the sd of the varying effects for the smooth term. Seems wiggly, but how do I interpret 302?

Also, I have been unable to find the interpretation of the y-axis for conditional_smooths(). If one uses conditional_effects() then the fit is on the scale of the data, but I am not sure how to interpret conditional_smooths().

I could estimate the slope (first derivative) of the spline along points in time using a finite differences approach using fitted() and some new data… but are plots, predictions, and derivatives the only meaningful way to interpret these? Or is the model output itself interpretable too?

Thanks so much! I realize these may be dumb, basic and obvious questions. Hopefully they are not redundant to other posts on the forum, but I think it would be helpful to others if these questions were answered in a single spot (maybe they are somewhere obvious and I haven’t found them!).


I found this helpful article, so I will try to answer this myself. Hopefully someone like @ucfagls will chime in if I am wrong.

sds(stime_1) is the sd of the smooth weights (spline coefficients). This determines the amount of ‘wiggliness’, in an analogous way to how the sd of group-level effects in a varying slopes and intercepts model determine the amount of variability among groups in slopes and intercepts. However, the actual numeric value of the sds() is not very practically interpretable, because thinking about the variance of smooth weights for any given data and model seems abstract to me. However, if the
value is around zero, then this is like ‘complete-pooling’ of the basis functions, which means that there isn’t much added value of more than a single basis function.

stime_1 is the unpenalized weight (ie coefficient) for one of the “natural” parameterized basis functions. The rest of the basis functions are like varying effects. Again, because the actual numeric value of stimes_1 is the value for the unpenalized coefficient for one of the basis functions, this wouldn’t seem to have a lot of practically interpretable meaning just from viewing this number.

Follow-up question - what if I have longitudinal data for 300 participants, with multiple data points per participant, where I would like to fit a spline. Does doing something like brm(outcome ~ s(time) + (time|id) make any sense? If so, how does the varying linear slope for time relate to the s(time)? I would like a varying slopes model, but if it isn’t linear, is there a varying splines model? I don’t think (s(time)|id) can be fit in brms, if I remember, and I am also not sure it makes much sense…

Again, any help is appreciated, and hopefully I have answered my original question correctly and don’t lead anyone else astray! Please correct if I am wrong.

Edit - also, still not sure about the y-axis on the conditional_smooths() plot…


In answer to the follow-up question, I found this helpful paper. On page 10 it mentions that for the varying slope case “the same curve is essentially rotated and stretched to match the actual trajectories”. I believe this is done by simply adding (time|id) to the model with the smooth, like brm(outcome ~ s(time) + (time|id). I played around with it on some data, and the quote above from the paper matches my intuition after predicting and plotting fit for several different subjects. You can also do ‘varying smooths’ as I describe in my question using bs=“fs”. However, this doesn’t seem very practical with several hundred participants as it is exceedingly slow to fit.

Hopefully all of this is on track!

Now if anyone can answer my question about the y-axis scale on conditional_smooths() plots in brms, then I think everything will be answered!


Originally, I did not think that this post helped answer my question about the y-axis scale on conditional_smooths(), until I realized the part where @paul.buerkner says “conditional_effects also adds the intercept”, implying that conditional_smooths() leaves it out. In my example above, the model is guassian, so I couldn’t figure out why conditional_smooths() differed from conditional_effects(), since the identity link is used (conditional_smooths() also shows things on the link scale, which differs from conditional_effects()). If I add the intercept from the model to the conditional_smooths() plot, then both plots look essentially the same (at least just eye-balling it).

I guess this makes me wonder why I would use conditional_smooths() when I can use conditional_effects(), which seems easier to interpret…? Maybe it is more useful in the case when there are multiple s() terms in the model.

1 Like