nmrec is an R package for reading records from a NONMEM control stream, parsing and modifying records of interest, and writing the result.

Reading a control stream

read_ctl() reads the NONMEM control stream from the specified path. Or, if you’ve already read in the lines as a character vector, you can use parse_ctl().

lines <- nmrec::nmrec_examples[["bayes1"]]
head(lines, 4)
#> [1] ";Model Desc: Two compartment Model, Using ADVAN3, TRANS4"
#> [2] ";Project Name: nm7examples"                              
#> [3] ""                                                        
#> [4] "$PROB RUN# Example 1 (from samp5l)"

ctl <- nmrec::parse_ctl(lines)

The result is an ?nmrec_ctl_records list of two items, “frontmatter” (any lines before the first record) and “records”.

ctl$frontmatter
#> [1] ";Model Desc: Two compartment Model, Using ADVAN3, TRANS4"
#> [2] ";Project Name: nm7examples"                              
#> [3] ""

length(ctl$records)
#> [1] 25
head(ctl$records, 3)
#> [[1]]
#> $PROB RUN# Example 1 (from samp5l)
#> 
#> 
#> [[2]]
#> $INPUT C SET ID JID TIME  DV=CONC AMT=DOSE RATE EVID MDV CMT CLX
#>        V1X QX V2X SDIX SDSX
#> 
#> 
#> [[3]]
#> $DATA example1.csv IGNORE=C

Record objects

The list of records contains ?nmrec_record objects.

purrr::map_chr(ctl$records, "name")
#>  [1] "problem"     "input"       "data"        "subroutines" "pk"         
#>  [6] "error"       "theta"       "omega"       "sigma"       "prior"      
#> [11] "thetap"      "thetapv"     "omegap"      "omegapd"     "sigmap"     
#> [16] "sigmapd"     "estimation"  "estimation"  "estimation"  "estimation" 
#> [21] "estimation"  "covariance"  "table"       "table"       "table"
unique(purrr::map_chr(ctl$records, is))
#>  [1] "nmrec_record_problem"    "nmrec_record_input"     
#>  [3] "nmrec_record_data"       "nmrec_record_raw"       
#>  [5] "nmrec_record_theta"      "nmrec_record_omega"     
#>  [7] "nmrec_record_sigma"      "nmrec_record_prior"     
#>  [9] "nmrec_record_estimation" "nmrec_record_table"

class(ctl$records[[17]])
#> [1] "nmrec_record_estimation" "nmrec_record"           
#> [3] "R6"

If nmrec doesn’t support parsing within a given record type, the record type will be nmrec_record_raw. Otherwise, the record type will be nmrec_record_{name}, where name is the standardized record name in lower case.

You can use select_records() to extract records of a given type. This is a simple wrapper around purrr::keep() that standardizes the input name (e.g., “est” to “estimation”).

ests <- nmrec::select_records(ctl, "est")
head(ests, 2)
#> [[1]]
#> $EST METHOD=ITS MAPITER=0 INTERACTION FILE=example1.ext NITER=500
#>      PRINT=5 NOABORT SIGL=4 CTYPE=3 CITER=10
#>      CALPHA=0.05 NOPRIOR=1 NSIG=2
#> 
#> 
#> 
#> [[2]]
#> $EST METHOD=SAEM INTERACTION NBURN=3000 NITER=500 PRINT=100
#>      SEED=1556678 ISAMPLE=2

The ?record object is an R6 object.

est1 <- ests[[1]]
ls(est1)
#> [1] "clone"       "format"      "get_lines"   "get_options" "initialize" 
#> [6] "name"        "parse"       "values"

The primary method of interest is $parse(), which parses the record lines and populates the $values field. This field is a list of elements (things like spaces and new lines) and ?nmrec_option objects. Once a record is parsed, the $values field fully controls how it will be rendered.

est1$values
#> NULL

est1$parse()

length(est1$values)
#> [1] 31
head(est1$values, 3)
#> [[1]]
#> $EST
#> 
#> [[2]]
#> [1] " "
#> attr(,"class")
#> [1] "nmrec_whitespace" "nmrec_element"    "character"       
#> 
#> [[3]]
#> METHOD=ITS

Option objects

You can grab an option of interest from a record with get_record_option().

est1_meth <- nmrec::get_record_option(est1, "meth")
ls(est1_meth)
#> [1] "clone"      "format"     "initialize" "name"       "name_raw"  
#> [6] "sep"        "value"

?nmrec_option objects, like ?nmrec_record objects, are R6 objects. The fields of interest depend on the option type. Here are the important fields for the selected method option of the first estimation record:

est1_meth$name
#> [1] "method"
est1_meth$name_raw
#> [1] "METHOD"
est1_meth$value
#> [1] "ITS"
est1_meth$sep
#> [1] "="

Modifying options and records

A key feature of option and records objects is that, as R6 objects, they have reference semantics (see vignette("Introduction", "R6")). For example, you could mutate NSIG=2 to NSIG=3 in the est1 record like so:

est1_nsig <- nmrec::get_record_option(est1, "sig")
est1_nsig$name
#> [1] "sigdigits"
est1_nsig$value
#> [1] "2"
est1_nsig$value <- 3

est1_nsig
#> NSIG=3
est1
#> $EST METHOD=ITS MAPITER=0 INTERACTION FILE=example1.ext NITER=500
#>      PRINT=5 NOABORT SIGL=4 CTYPE=3 CITER=10
#>      CALPHA=0.05 NOPRIOR=1 NSIG=3

If you print ctl (the original nmrec_ctl_records object), you’ll notice that the NSIG change is reflected there.

Alternatively, you can also modify or append new options with set_record_option(), which covers the more common cases of setting flag/value options.

est1
#> $EST METHOD=ITS MAPITER=0 INTERACTION FILE=example1.ext NITER=500
#>      PRINT=5 NOABORT SIGL=4 CTYPE=3 CITER=10
#>      CALPHA=0.05 NOPRIOR=1 NSIG=3
nmrec::set_record_option(est1, "CITER", value = 15) # Update CITER to 15
est1
#> $EST METHOD=ITS MAPITER=0 INTERACTION FILE=example1.ext NITER=500
#>      PRINT=5 NOABORT SIGL=4 CTYPE=3 CITER=15
#>      CALPHA=0.05 NOPRIOR=1 NSIG=3
nmrec::set_record_option(est1, "NOABORT", NULL) # Remove NOABORT
est1
#> $EST METHOD=ITS MAPITER=0 INTERACTION FILE=example1.ext NITER=500
#>      PRINT=5  SIGL=4 CTYPE=3 CITER=15
#>      CALPHA=0.05 NOPRIOR=1 NSIG=3
nmrec::set_record_option(est1, "MAXEVAL", 10) # Add option MAXEVAL=10
est1
#> $EST METHOD=ITS MAPITER=0 INTERACTION FILE=example1.ext NITER=500
#>      PRINT=5  SIGL=4 CTYPE=3 CITER=15
#>      CALPHA=0.05 NOPRIOR=1 NSIG=3 MAXEVAL=10

You can write the modified result to a file with write_ctl().

write_ctl(ctl, "modified.ctl")