In this article, I examined the process of modifying the disease transmission model (for instance, the \(SEIR\) model) to include vaccination and the waning of vaccine-induced immunity as it might happen in a clinical trial. A straightforward method to represent this involves assuming vaccinated individuals (i.e., those in the vaccinated, \(V\), compartment) possess partial immunity and subsequently transition back to the susceptible, \(S\), compartment. This approach effectively simulates the diminishing effectiveness of vaccines using two parameters. I focus on determining these parameters through the analysis of existing vaccine efficacy data, utilizing the typhoid vaccine clinical trial as a case study. The adaptation of the model to account for the progressive reduction in vaccine-derived immunity across various compartments was conducted prior post.
Vaccine efficacy data
Vaccine efficacy data come from a clinical trial conducted in Malawi from 2018 through 2023, for which the detailed information is available in the study.
# df <- data.frame(year=c(1,2), efficacy=c(83.4,73.0)) # Shakya (2021) Lancet Glob Health# vaccine efficacy cumulative over the four years# Patel (2024) Lancettab2_vitt <-data.frame(group ="Vi-TT",age =c("<2yo","2-4yo",">=5yo","all_ITT","all_PP"),person_years =c(6586,15007,38907,60500,59942), typhoid_culture =c(4,5,15,24,22),IR_obs =c(60.7,33.3,38.6,39.7,36.7),VE_obs =c(70.6,79.6,79.3,78.3,80.0))tab2_mena <-data.frame(group ="MenA",age =c("<2yo","2-4yo",">=5yo","all_ITT","all_PP"),person_years =c(6773,15297,38151,60220,59662), typhoid_culture =c(14,25,71,110,109),IR_obs =c(206.7,163.4,186.1,182.7,182.7),VE_obs =NA)tab2 <-rbind(tab2_vitt, tab2_mena)# Check the sanity of the datatab2$IR_cal <-round(tab2$typhoid_culture/tab2$person_years*1e5, digits=1)tab2$IR_cal == tab2$IR_obs
Model of typhoid transmission for a clinical trial
seirv_nstg_trial <-function(u, p, t){ S <- u[1]; E <- u[2]; I <- u[3]; R <- u[4]; C <- u[5]# # vaccinated cohort nstg <- p[1]for (i in1:nstg) {eval(str2lang(paste0("V",i," <- ", "u[5+",i,"]"))); }# vaccinated and partially protected VS <- u[5+ nstg +1] # vaccinated but immunity waned and fully susceptible VE <- u[5+ nstg +2] VI <- u[5+ nstg +3] VR <- u[5+ nstg +4] VC <- u[5+ nstg +5] pop <- S + E + I + R v_sum <-paste(sapply(1:nstg, function(x) paste0("V",x)), collapse="+")eval(str2lang(paste0("popV <- VS+VE+VI+VR+", v_sum))) epsilon <- p[2] # 1/latent period gamma <- p[3] # 1/duration of infectiousness beta <- p[4] # transmission rate mu <- p[5] # death rate is applied and population size decreases over time omega <- p[6] # 1 / duration of natural immunity omega_v <- p[7] # 1 / duration of partial vaccine-derived immunity ve <- p[8] # vaccine efficacy# vaccinated and unvaccinated population mix randomly foi <- beta*(I+VI)/(pop+popV) # force of infection muEI <- epsilon muIR <- gamma muRS <- omega muVS <- nstg*omega_v# differential equations dS <-- foi*S + muRS*R - mu*S + mu*(pop+popV) dE <- foi*S - muEI*E - mu*E dI <- muEI*E - muIR*I - mu*I dR <- muIR*I - muRS*R - mu*R dC <- muEI*E dV1 <-- foi*(1-ve)*V1 - muVS*V1 - mu*V1if (nstg >=2) {for (i in2:nstg) {eval(str2lang(paste0("dV",i," <- - foi*(1-ve)*V", i, " + muVS*V", i-1 , " - muVS*V", i," - mu*V", i))) } }eval(str2lang(paste0("dVS <- - foi*VS + muVS*V", nstg, " + muRS*VR - mu*VS"))) eval(str2lang(paste0("dVE <- foi*((1-ve)*(", v_sum, ")+VS) - muEI*VE - mu*VE"))) dVI <- muEI*VE - muIR*VI - mu*VI dVR <- muIR*VI - muRS*VR - mu*VR dVC <- muEI*VE dvstr <-paste(sapply(1:nstg, function(x) paste0("dV", x)), collapse=",")return(eval(str2lang(paste0("c(dS,dE,dI,dR,dC,", dvstr, ",dVS,dVE,dVI,dVR,dVC)"))))}
Simulation of the variable-compartment \(SEIRV\) model
Solutions for the steady states of the \(SEIR\) model. These are used to set the initial conditions as we assume that the disease transmission reached a steady state.
Define sum of squared difference (ssq) between the model and the data to evaluate the performance of the model.
# the initial VE is not measured from the modeltimes <-round(tab3[tab3$group =="Vi-TT",]$year_since_vacc*365) # times to measure VEve_obs <- tab3[tab3$group =="Vi-TT",]$VE_obs/100ssq <-function(p){ ve_sim <-measure_vacc_eff(p, params=params, times=times)sum((ve_obs - ve_sim)^2) }# check for some predetermined ve and omega# ssq(p=c(0.8,1/3650)) must be smaller than ssq(p=c(0.4,1/365))ssq(p=c(0.8,1/3650))
[1] 0.05994089
ssq(p=c(0.4,1/3650))
[1] 1.005789
Estimate parameters by minimizing the ssq using the nlminb function.