Introduction

This vignette demonstrates extracting parameter estimates and labels into a table that can be used for diagnostics, or to generate reports. Note, this vignette exclusively deals with NONMEM models.

If you are new to bbr, the “Getting Started with bbr” vignette will take you through some basic scenarios for modeling with NONMEM using bbr, introducing you to its standard workflow and functionality.

Setup

There is some initial set up necessary for using bbr. Please refer to the “Getting Started” vignette, mentioned above, if you have not done this yet. Once this is done, load the library.

Parameter labels

Create Model Object

We have a control stream and output directory, because the model has already been run.

MODEL_DIR <- system.file("test-refs", "param-labels", "example-model", package = "bbr")

mod1 <- read_model(file.path(MODEL_DIR, "510"))
class(mod1)
#> [1] "bbi_nonmem_model" "bbi_base_model"   "bbi_model"        "list"

The model object you have just created can now be passed to the post-processing functions to create your tables.

Extract Parameter labels

Currently, the param_labels() function parses labels from the comments in the control stream. Here is the relevant section of our example control stream.

mod1 %>% 
  get_model_path() %>%                          # get control stream file path
  read_lines(skip = 15, n_max = 18) %>%  # read in only parameter section
  paste(collapse = "\n") %>%
  cat()
#> $THETA
#>  (0, 13) ;[L/day] CL
#>  (0, 75) ;[L] V
#>  (0.001 );[L/day] CL_{CLCR}
#> $THETA
#>  (0.001) ;[L/day] CL_{AGE}
#>  (0.001) ;[L] V_{WT}
#>  (0.001) ;[L] V_{AGE}
#> 
#> $OMEGA BLOCK(2)
#> ; a comment
#> (0.04) ;[P] CL
#> (0.02) ;[R] CL-V
#> (0.04) ;[P] V
#> 
#> $SIGMA
#> 0.04 ;[P] Residual

This control stream is parsed into the tibble below, following the syntax defined in the “Details” section of the ?param_labels documentation.

label_df <- mod1 %>% 
  param_labels() %>% 
  apply_indices(.omega = block(2))
label_df
#> # A tibble: 10 × 5
#>    parameter_names label     unit    type  param_type
#>    <chr>           <chr>     <chr>   <chr> <chr>     
#>  1 THETA1          CL        "L/day" ""    THETA     
#>  2 THETA2          V         "L"     ""    THETA     
#>  3 THETA3          CL_{CLCR} "L/day" ""    THETA     
#>  4 THETA4          CL_{AGE}  "L/day" ""    THETA     
#>  5 THETA5          V_{WT}    "L"     ""    THETA     
#>  6 THETA6          V_{AGE}   "L"     ""    THETA     
#>  7 OMEGA(1,1)      CL        ""      "[P]" OMEGA     
#>  8 OMEGA(2,1)      CL-V      ""      "[R]" OMEGA     
#>  9 OMEGA(2,2)      V         ""      "[P]" OMEGA     
#> 10 SIGMA(1,1)      Residual  ""      "[P]" SIGMA

Note, there are some subtleties to the apply_indices() function that will be addressed in the next section.

Add Parameter Estimates

The user can also extract parameter estimates using the model_summary() and param_estimates() functions.

param_df <- mod1 %>% 
  model_summary() %>% 
  param_estimates()
param_df
#> # A tibble: 10 × 8
#>    parameter_names estimate  stderr random_effect_sd random_effect_sdse fixed
#>    <chr>              <dbl>   <dbl>            <dbl>              <dbl> <lgl>
#>  1 THETA1          12.3     0.961            NA                NA       FALSE
#>  2 THETA2          90.7     4.94             NA                NA       FALSE
#>  3 THETA3           0.163   0.335            NA                NA       FALSE
#>  4 THETA4           0.567   1.57             NA                NA       FALSE
#>  5 THETA5           0.403   0.342            NA                NA       FALSE
#>  6 THETA6          -0.395   2.32             NA                NA       FALSE
#>  7 OMEGA(1,1)       0.0517  0.0122            0.227             0.0269  FALSE
#>  8 OMEGA(2,1)       0.00345 0.00946           0.0654            0.178   FALSE
#>  9 OMEGA(2,2)       0.0538  0.0120            0.232             0.0259  FALSE
#> 10 SIGMA(1,1)       0.0450  0.00388           0.212             0.00914 FALSE
#> # ℹ 2 more variables: diag <lgl>, shrinkage <dbl>

These two tibbles can be joined together to create a table for including in reports.

report_df <- inner_join(
  label_df %>% select(-param_type),
  param_df %>% select(parameter_names, estimate, stderr),
  by = "parameter_names"
)
report_df
#> # A tibble: 10 × 6
#>    parameter_names label     unit    type  estimate  stderr
#>    <chr>           <chr>     <chr>   <chr>    <dbl>   <dbl>
#>  1 THETA1          CL        "L/day" ""    12.3     0.961  
#>  2 THETA2          V         "L"     ""    90.7     4.94   
#>  3 THETA3          CL_{CLCR} "L/day" ""     0.163   0.335  
#>  4 THETA4          CL_{AGE}  "L/day" ""     0.567   1.57   
#>  5 THETA5          V_{WT}    "L"     ""     0.403   0.342  
#>  6 THETA6          V_{AGE}   "L"     ""    -0.395   2.32   
#>  7 OMEGA(1,1)      CL        ""      "[P]"  0.0517  0.0122 
#>  8 OMEGA(2,1)      CL-V      ""      "[R]"  0.00345 0.00946
#>  9 OMEGA(2,2)      V         ""      "[P]"  0.0538  0.0120 
#> 10 SIGMA(1,1)      Residual  ""      "[P]"  0.0450  0.00388

Applying indices

Because there are numerous ways of specifying the diagonal and off-diagonal elements of an $OMEGA or $SIGMA block in a control stream, automatically parsing the structure of these blocks can be brittle and error prone. For this reason, indices are not automatically added to the output of the param_labels() function and are instead added with the apply_indices() function.

Default behavior: All Diagonal

By default apply_indices() assumes that all $OMEGA and $SIGMA elements are diagonal. If this is the case, you do not need to pass anything to .omega or .sigma arguments described below. However, be careful that you do not accidentally overlook this because your indices will be incorrectly returned as simply (1,1), (2,2), (3,3), etc.

Specifying Block Structure

Block structure is specified with two arguments, .omega and .sigma which each take a logical vector. Each element in this vector corresponds to a parameter specified in the control stream and denotes whether that element is a diagonal in the relevant matrix.

For example, an $OMEGA BLOCK(3) would be represented with the vector .omega = c(TRUE, FALSE, TRUE, FALSE, FALSE, TRUE) because the first, third, and sixth elements are the diagonals and the others represent the off-diagonals. However, the user doesn’t need to type this explicitly because bbr has a helper function block() that generates these vectors.

cat(paste("block(1): ", paste(block(1), collapse = ", "), "\n"))
#> block(1):  TRUE
cat(paste("block(2): ", paste(block(2), collapse = ", "), "\n"))
#> block(2):  TRUE, FALSE, TRUE
cat(paste("block(3): ", paste(block(3), collapse = ", "), "\n"))
#> block(3):  TRUE, FALSE, TRUE, FALSE, FALSE, TRUE
cat(paste("block(4): ", paste(block(4), collapse = ", "), "\n"))
#> block(4):  TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE

Notice the use of .omega = block(2) in the example from the previous section.

Mixed structure

More complicated block structures can be represented by concatenating these logical vectors together. For example, the following omega block:

cat(REF_OMEGA)
#> 
#>       $OMEGA BLOCK (3)
#>       .1          ;[P] 5 P1NPF
#>       .01 .1      ;[P] 6 CTFX
#>       .01 .01 .1  ;[P] 7 LSF
#>       $OMEGA BLOCK(2)
#>       .1          ;[P] 8 FAKE1
#>       .01 .1      ;[P] 9 FAKE2
#>       $OMEGA BLOCK (1) 0.04 ; [P] IOV_{KA}
#>       $OMEGA BLOCK(1) SAME
REF_OMEGA %>%
  param_labels() %>%
  apply_indices(
      .omega = c(block(3), block(2), block(1), block(1))
    )
#> # A tibble: 11 × 4
#>    parameter_names label      type  param_type
#>    <chr>           <chr>      <chr> <chr>     
#>  1 OMEGA(1,1)      "5 P1NPF"  [P]   OMEGA     
#>  2 OMEGA(2,1)      ""         [A]   OMEGA     
#>  3 OMEGA(2,2)      "6 CTFX"   [P]   OMEGA     
#>  4 OMEGA(3,1)      ""         [A]   OMEGA     
#>  5 OMEGA(3,2)      ""         [A]   OMEGA     
#>  6 OMEGA(3,3)      "7 LSF"    [P]   OMEGA     
#>  7 OMEGA(4,4)      "8 FAKE1"  [P]   OMEGA     
#>  8 OMEGA(5,4)      ""         [A]   OMEGA     
#>  9 OMEGA(5,5)      "9 FAKE2"  [P]   OMEGA     
#> 10 OMEGA(6,6)      "IOV_{KA}" [P]   OMEGA     
#> 11 OMEGA(7,7)      "IOV_{KA}" [P]   OMEGA

It is equally valid to replace any of these with logical vectors as well. For instance, instead of c(..., block(1), block(1)) it may be easier to write c(..., rep(TRUE, 2)), especially if there are a number of BLOCK(1) lines in a row in the control stream.

SAME blocks

It is also worth noting here that blocks which use SAME (for example, $OMEGA BLOCK(1) SAME above) will inherit the label from the block to which SAME refers. This is true even if there are multiple SAME blocks in a row.