When a dataset is updated (e.g. a new data cut with more subjects, additional observations, corrections, etc), every Monolix/PKanalix project usually has to be re-run on the new data. Doing this by hand (opening each project, replacing the dataset, checking the tagging, launching the run, saving) quickly becomes cumbersome and error-prone, especially with many models.
If the structure of the dataset does not change (same columns, only more rows), this can be fully automated with the R package lixoftConnectors. A single script then loads every project, swaps in the new dataset, re-runs it, and collects the results.
The example below is based on downloadable example projects. Only the dataset needs to be updated; running the script re-estimates all projects automatically.
rm(list = ls())
path.prog <- dirname(rstudioapi::getSourceEditorContext()$path)
library(lixoftConnectors)
library(dplyr)
# Initialize Monolix connectors
initializeLixoftConnectors(software = "monolix", force = TRUE)
#-------------------------------------------------------------------------------
# helper function to retrieve information criterion of project
get_info_row <- function(project_name) {
ll_res <- getEstimatedLogLikelihood()
if ("importanceSampling" %in% names(ll_res)) {
ll <- ll_res$importanceSampling
method_used <- "importanceSampling"
} else if ("linearization" %in% names(ll_res)) {
ll <- ll_res$linearization
method_used <- "linearization"
} else {
stop(paste("No likelihood results found for", project_name))
}
data.frame(
project = project_name,
method = method_used,
OFV = as.numeric(ll["OFV"]),
BICc = as.numeric(ll["BICc"]),
data = data_file)
}
#-------------------------------------------------------------------------------
# data tagging and obsid mapping must be the same for all projects
data_settings <- list(
dataFile = data,
headerTypes = c(
"id", # id
"time", # time
"amount", # amt
"observation", # dv
"obsid", # dvid
"contcov", # wt
"catcov", # sex
"contcov"), # age
observationTypes = list(
"1" = "continuous",
"2" = "continuous"))
#-------------------------------------------------------------------------------
data_file <- "warfarin_dataset_V01.csv" # <-- data file can be updated
data <- paste0(path.prog,"/Data/",data_file)
# Each entry is one work dir + the projects it contains
work_dirs <- list(
list(dir = paste0(path.prog,"/PKmodeling/1_1_structuralmodel/"), # Structural PK model
projects = c("r01_1cmt",
"r02_Tlag_1cmt",
"r03_Tlag_2cmt")),
list(dir = paste0(path.prog,"/1_PKmodeling/1_2_statisticalmodel/"), # Statistical PK model
projects = c("r02_1_Tlag_1cmt_comb2err",
"r02_2_Tlag_1cmt_comb2err_logwtV",
"r02_3_Tlag_1cmt_comb2err_logwtVCl",
"r02_4_Tlag_1cmt_comb2err_logwtVCl_logageCl")))
# Outer loop: work dirs / inner loop: projects in that work dir
res <- data.frame(NULL)
for (wd in work_dirs) {
info_criteria <- data.frame()
for (prj in wd$projects) {
loadProject(paste0(wd$dir, prj, ".mlxtran"))
setData(data_settings) # setData only changes data file, other settings stay the same in monolix project
runScenario()
saveProject(paste0(wd$dir, prj))
info_temp <- get_info_row(prj)
info_criteria <- rbind(info_criteria, info_temp)
}
res <- rbind(res, info_criteria) %>% arrange(BICc) # collect all projects into one data frame
}
print(res)
Notes on the script
-
data_fileis the only line to touch for an update: point it at the new dataset in theData/folder, then run the whole script. -
get_info_row()reads the estimated log-likelihood of the currently loaded project and returns one row (OFV, BICc, method, data file) to build the summary table. -
data_settingsdefines the column tagging and observation mapping once. Since these do not change from project to project, the same settings are applied to every project.setData()only replaces the data file; the model, initial values, and task configuration stored in each.mlxtranstay untouched. -
work_dirslists the working directories and the projects in each. -
The two loops iterate over the working directories (outer) and their projects (inner). For each project the same calls run (
loadProject,setData,runScenario,saveProject) and the
information criterion is appended tores. -
resis the final, BICc-sorted table with one row per re-estimated project on the new dataset.