This set of functions allows you to do dlnm parameter sweep. Testing many different iterations of DLNM options quickly and efficiently. Github page is here
From Gasparrini 2014:
library(dlnm) ; library(splines) ; library(foreign) ; library(tsModel)
# LOAD THE DATA
lndn <- read.csv(paste0("https://raw.githubusercontent.com/gasparrini/",
"2014_gasparrini_BMCmrm_Rcodedata/e629be7809853f390d305d8e73ac4b152a54f092/london.csv"))
# KNOTS FOR EXPOSURE-RESPONSE FUNCTION
vk <- equalknots(lndn$tmean, fun="bs", degree = 2, df = 4)
# KNOTS FOR THE LAG-RESPONSE FUNCTION
maxlag <- 25
lk <- logknots(x = maxlag, nk = 3)
# COMPUTE THE CROSS-BASIS
cb <- crossbasis(x = lndn$tmean,
lag=maxlag,
argvar=list(fun="bs", degree = 2, knots = vk),
arglag=list(knots=lk))
# RUN THE MODEL
model <- glm(death~cb+ns(time,10*14)+dow,family=quasipoisson(),lndn)
And create outputs
# PREDICTION AT 99TH PERCENTILES
# COMPUTE 99TH PERCENTILES
perc <- quantile(lndn$tmean,c(0.99))
# CENTERING VALUE (AND PERCENTILE)
cen <- 20
sum(lndn$tmean<cen,na.rm=T)/sum(!is.na(lndn$tmean))
## [1] 0.9360454
pred <- crosspred(cb,model,at=perc,bylag=0.2,cen=cen)
# This is already exponentiated
o <- data.frame(est = pred$allRRfit, lb = pred$allRRlow, ub = pred$allRRhigh)
row.names(o) <- 1
o
## est lb ub
## 1 1.25294 1.188849 1.320487
But what if you wanted to test other spline options? Well now you can:
DLNM Sweep uses a custom function called make_cb_list
to expand crossbasis options:
cb2 <- make_cb_list(x = lndn,
var_list = c('tmean'),
argvar_list = list(set1 = list(fun="bs", degree = 2, knots = list(vk))) ,
lag_list = maxlag,
arglag_list = list(set1 = list(knots = list(lk))))
You can see that this creates the same cb
object as in
the basic DLNM (with some novel attributes removed):
attr(cb2[[1]], 'summary_str') <- NULL
attr(cb2[[1]], 'var_name') <- NULL
identical(cb, cb2[[1]])
## [1] TRUE
Ok so now how to use this to its fullest potential. One application might look like this:
var_list <- c('tmean', 'tmin')
cb_all <- list()
for(var in var_list) {
# make var knots that vary by var
vk2 = equalknots(lndn[, var], fun="bs", degree = 2, df = 4)
vk3 = equalknots(lndn[, var], fun="ns", df = 4)
k50 = quantile(lndn[, var], probs = .5, na.rm = T)
k10 = quantile(lndn[, var], probs = c(1:9)/10, na.rm = T)
cb_var = make_cb_list(x = lndn,
var_list = var,
argvar_list = list(set1 = list(fun="bs", degree = c(2, 3),
knots = list(vk2, k50, k10)),
set2 = list(fun="ns",
knots = list(vk3, k50, k10))) ,
lag_list = maxlag,
arglag_list = list(set1 = list(knots = list(lk))))
cb_all <- append(cb_all, cb_var)
}
So now you’ve created the crossbasis matrices, how many versions are there? Should be: 2 variables x (2 bs degrees x 3 bs argvar knots + 1 ns degrees x 3 ns argvar knots)
length(cb_all)
## [1] 18
Next step is to create the model objects (implemented below in parallel):
# setup for parallel processing
library(future) # parallel processing
library(future.apply) # parallel processing
library(progressr) # progress updates during parallel
plan(multisession)
handlers("progress")
get_model_obj <- function(cb_list) {
cb_seq <- seq(length(cb_list))
p <- progressr::progressor(along = cb_seq)
future_lapply(cb_seq, function(i) {
cb <- cb_list[[i]]
p(message = sprintf("%s", attr(cb, "summary_str")))
# **********
# model object, same as above
m <- glm(death ~ cb + ns(time,10*14) + dow, family = quasipoisson(), lndn)
# **********
attr(m, "var_name") <- attr(cb, "var_name")
attr(m, "summary_str") <- attr(cb, "summary_str")
m
})
}
# get objects
with_progress(models <- get_model_obj(cb_all))
Clearly this could be expanded to a two-cross basis example by creating an expanded grid of the length of each cb_list.
Final step is get model predictions, implemented again in parallel:
get_pred <- function(cb_list, model_list) {
stopifnot(length(cb_list) == length(model_list))
cb_seq <- seq(length(cb_list))
p <- progressr::progressor(along = cb_seq)
out <- future_lapply(cb_seq, function(i) {
cb <- cb_list[[i]]
m <- model_list[[i]]
p(message = sprintf("%s", attr(cb, "summary_str")))
# ********
# get prediction and centering values
perc <- quantile(lndn[, attr(cb, "var_name")],c(0.99), na.rm = T)
cen <- quantile(lndn[, attr(cb, "var_name")],c(0.94), na.rm = T) # ~ 20c for Tmax
pred <- crosspred(cb, m, at=perc, bylag=0.2, cen=cen)
# ********
data.frame(var_name = attr(cb, "var_name"),
summary_str = attr(cb, "summary_str"),
est = pred$allRRfit[1],
lb = pred$allRRlow[1],
ub = pred$allRRhigh[1])
})
o <- data.frame(do.call(rbind, out))
row.names(o) <- 1:nrow(o)
return(o)
}
# get betas
with_progress(RRs <- get_pred(cb_all, models))
And now plot
RRs %>%
ggplot(., aes(y = reorder(summary_str, est))) +
geom_point(aes(x=est), shape=15, size=3) +
geom_linerange(aes(xmin=lb, xmax=ub)) + ylab(NULL)