• Language: en

LEVEL_PARAMS

A required verbatim section that defines fixed effects and random effects for use in mixed effects models. The LEVEL_PARAMS defines a level structure, which dictates the number of instances of the f[X] and r[X] effect variables.

For example, f[X] variables representing fixed effects are usually declared at the GLOBAL level, so there is only one value of each f[X]. Whereas r[X] variables representing random effects are usually declared at the INDIV level, so each individual has a sample from each random effect. It’s also possible to define further sub levels below the INDIV level, for example within individual occasions which have there own r[X] variables, see Inter-Occasion Variation (IOV).

The LEVEL_PARAMS section is required by the following scripts:-

i.e. Any script that defines a mixed effect model.

LEVEL_PARAMS with two levels from a fit_script

The example below is used in Fitting a Two Compartment PopPK Model.

LEVEL_PARAMS:
    GLOBAL:
        params: |
            f[KA] ~ P1.0
            f[CL] ~ P1.0
            f[V1] ~ P20
            f[Q] ~ P0.5
            f[V2] ~ P100
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] ~ spd_matrix() [
                [0.05],
                [0.01, 0.05],
                [0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] ~ P0.1
        split_field: None
        split_dict: {}
    INDIV:
        params: |
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])
        split_field: ID
        split_dict: "*"

The example above defines two levels:-

  • GLOBAL - single value of each f[X] variable
  • INDIV - one value per individual for each r[X] variable

This example defines 5 mean fixed effect parameters i.e. f[KA], f[CL], f[V1], f[Q], f[V2], a 5x5 covariance matrix f[KA_isv, CL_isv, V1_isv, Q_isv, V2_isv], a proportional noise variable f[PNOISE] and a 5 element vector r[KA, CL, V1, Q, V2] of random effects defined for each individual.

Note each level has the following (required) sections:-

  • params - A verbatim section where f[X] and r[X] variables are declared
  • split_field - The name of the variable used to partition the data
  • split_dict - A Python dictionary specifying the partition of the data

Note the GLOBAL level has these null entries:-

GLOBAL:
    split_field: None
    split_dict: {}

Here the ‘split_field’ is null, so the GLOBAL level is not partitioned. Every variable declared at the GLOBAL level has one shared value over the whole population.

The INDIV level has the following entries:-

INDIV:
    split_field: ID
    split_dict: "*"

This means that the INDIV level is split using the ‘ID’ field. The “*” indicates that a partition is created for every distinct value of the ‘ID’ field present in the data file. This is similar to the factor concept in R. The structure of the mixed effects is a tree, see Fig. 48.

../../../_images/two_level_tree.svg

Fig. 48 LEVEL_PARAMS structure with two levels

Here the number of r[X] values is dependent on the number of individuals in the data file.

LEVEL_PARAMS with three levels from a fit_script

It is possible to add a further level as follows:-

LEVEL_PARAMS:
    GLOBAL:
        params: |
            f[KA] ~ P1.0
            f[CL] ~ P1.0
            f[V1] ~ P20
            f[Q] ~ P0.5
            f[V2] ~ P100
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] ~ spd_matrix() [
                [0.05],
                [0.01, 0.05],
                [0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.01, 0.05],
            ]
            f[KA_iov,CL_iov,V1_iov,Q_iov,V2_iov] ~ spd_matrix() [
                [0.05],
                [0.01, 0.05],
                [0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] ~ P0.1
        split_field: None
        split_dict: {}
    INDIV:
        params: |
            r[KA, CL, V1, Q, V2] ~ mnorm(
                [0,0,0,0,0], 
                f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv]
            )
        split_field: ID
        split_dict: "*"
    IOV:
        params: |
            r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] ~ mnorm( 
                [0,0,0,0,0], 
                f[KA_iov, CL_iov, V2_iov, Q_iov, V3_iov] 
            )
        split_field: OC
        split_dict: "*"

The example above defines three levels:-

  • GLOBAL - single value of each f[X] variable
  • INDIV - one value per individual for each r[X] variable
  • IOV - one value per iov per individual for each r[X] variable

The IOV level creates an extra level of r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] variables. If there are say 2 different values of c[OC] in the data file (i.e. two occasions) then each individual has a 5 element vector r[KA, CL, V1, Q, V2] at the INDIV level and additionally two 5 element vectors r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] (one for each occasion) at the IOV level.

The structure of the mixed effects is now as show in Fig. 49.

../../../_images/three_level_tree.svg

Fig. 49 LEVEL_PARAMS structure with three levels

For more information on this topic see Inter-Occasion Variation (IOV).

LEVEL_PARAMS with two levels from a gen_script

The example below is used in Generate a Two Compartment PopPK Data Set.

LEVEL_PARAMS:
    GLOBAL:
        params: |
            c[AMT] = 100.0
            f[KA] = 0.2
            f[CL] = 2.0
            f[V1] = 50
            f[Q] = 1.0
            f[V2] = 80
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] = [
                [0.1],
                [0.01, 0.03],
                [0.01, -0.01, 0.09],
                [0.01, 0.02, 0.01, 0.07],
                [0.01, 0.02, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] = 0.15
        split_field: None
        split_dict: {}
    INDIV:
        params: |
            c[ID] = sequential(50)
            t[DOSE] = 2.0
            t[OBS] ~ unif(1.0, 50.0; 5)
            # t[OBS] = range(1.0, 50.0; 5)
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])
        split_field: ID
        split_dict: "*"

The example above defines two levels and is similar to the LEVEL_PARAMS with two levels from a fit_script section. The Fit Script defines f[X] and r[X] variables. Additionally this Gen Script version defines c[X] variables and additionally t[DOSE] and t[OBS] variables that define the dosing and observation rows of the generated data file.

In the GLOBAL section:-

GLOBAL:
   params: |
        c[AMT] = 100.0

This syntax creates a c[AMT] field in the data file which is constant over all rows. In the INDIV section:-

INDIV:
    params: |
        c[ID] = sequential(50)

This syntax creates a c[ID] field in the data file which has values of [1,50]. i.e. 50 individuals. Also in the INDIV sections these t[X] variables are declared:-

INDIV:
    params: |
        t[DOSE] = 2.0
        t[OBS] ~ unif(1.0, 50.0; 5)

The t[DOSE] creates a dose row at time 2.0 for all individuals. The t[OBS] line creates 5 observation rows at random time points in the range [1,50.0].

LEVEL_PARAMS with three levels from a gen_script

It is possible to add a further level to the Gen Script LEVEL_PARAMS as follows:-

LEVEL_PARAMS:
    GLOBAL:
        params: |
            c[AMT] = 100.0
            f[KA] = 0.2
            f[CL] = 2.0
            f[V1] = 50
            f[Q] = 1.0
            f[V2] = 80
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] = [
                [0.1],
                [0.01, 0.03],
                [0.01, -0.01, 0.09],
                [0.01, 0.02, 0.01, 0.07],
                [0.01, 0.02, 0.01, 0.01, 0.05],
            ]
            f[KA_iov,CL_iov,V1_iov,Q_iov,V2_iov] = [
                [0.1],
                [0.01, 0.03],
                [0.01, -0.01, 0.09],
                [0.01, 0.02, 0.01, 0.07],
                [0.01, 0.02, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] = 0.15
        split_field: None
        split_dict: {}
    INDIV:
        params: |
            c[ID] = sequential(50)
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])
        split_field: ID
        split_dict: "*"
    IOV:
        params: |
            c[OC] = sequential(2)
            t[DOSE] = 2.0
            t[OBS] ~ unif(1.0, 50.0; 5)
            # t[OBS] = range(1.0, 50.0; 5)
            r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] ~ mnorm( 
                [0,0,0,0,0], 
                f[KA_iov, CL_iov, V2_iov, Q_iov, V3_iov] 
            )
        split_field: OC
        split_dict: "*"

The obvious difference between LEVEL_PARAMS with two levels from a gen_script and the three level version above is the addition of the third section:-

IOV:
    params: |
        c[OC] = sequential(2)
        t[DOSE] = 2.0
        t[OBS] ~ unif(1.0, 50.0; 5)
        # t[OBS] = range(1.0, 50.0; 5)
        r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] ~ mnorm(
            [0,0,0,0,0],
            f[KA_iov, CL_iov, V2_iov, Q_iov, V3_iov]
        )
    split_field: OC
    split_dict: "*"

This denotes an IOV level with two occasions for each individual. Note that this line creates two occasions:-

IOV:
    params: |
        c[OC] = sequential(2)

i.e. rows with c[OC] taking the values [1,2] are created. Within each occasion the t[DOSE] and t[OBS] create a dosing row and 5 observation rows. Note it is necessary to move the t[X] variables from the INDIV level to the IOV level. In a Gen Script it usually makes sense to place the t[X] variables at the lowest level.

LEVEL_PARAMS with two levels from a tut_script

The example below is used in Generate data and Fit using a Two Compartment Model.

LEVEL_PARAMS:
    GLOBAL:
        split_field: None
        split_dict: {}
        gen_params: |
            c[AMT] = 100.0
            f[KA] = 0.2
            f[CL] = 2.0
            f[V1] = 50
            f[Q] = 1.0
            f[V2] = 80
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] = [
                [0.1],
                [0.01, 0.03],
                [0.01, -0.01, 0.09],
                [0.01, 0.02, 0.01, 0.07],
                [0.01, 0.02, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] = 0.15
        fit_params: |
            f[KA] ~ P1.0
            f[CL] ~ P1.0
            f[V1] ~ P20
            f[Q] ~ P0.5
            f[V2] ~ P100
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] ~ spd_matrix() [
                [0.05],
                [0.01, 0.05],
                [0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] ~ P0.1
    INDIV:
        split_field: ID
        split_dict: "*"
        gen_params: |
            c[ID] = sequential(50)
            t[DOSE] = 2.0
            t[OBS] ~ unif(1.0, 50.0; 5)
            # t[OBS] = range(1.0, 50.0; 5)
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])
        fit_params: |
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])

The Tut Script combines the Gen Script and Fit Script levels. It does this by each level having the following (required) sections:-

  • gen_params - A verbatim section where f[X], r[X], t[X] and c[X] variables are declared for the child Gen Script
  • fit_params - A verbatim section where f[X] and r[X] variables are declared for the child Fit Script
  • split_field - The name of the variable used to partition the data
  • split_dict - A dictionary specifying the partition of the data

LEVEL_PARAMS with three levels from a tut_script

It is possible to add the third level to a Tut Script as follows:-

LEVEL_PARAMS:
    GLOBAL:
        split_field: None
        split_dict: {}
        gen_params: |
            c[AMT] = 100.0
            f[KA] = 0.2
            f[CL] = 2.0
            f[V1] = 50
            f[Q] = 1.0
            f[V2] = 80
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] = [
                [0.1],
                [0.01, 0.03],
                [0.01, -0.01, 0.09],
                [0.01, 0.02, 0.01, 0.07],
                [0.01, 0.02, 0.01, 0.01, 0.05],
            ]
            f[KA_iov,CL_iov,V1_iov,Q_iov,V2_iov] = [
                [0.1],
                [0.01, 0.03],
                [0.01, -0.01, 0.09],
                [0.01, 0.02, 0.01, 0.07],
                [0.01, 0.02, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] = 0.15
        fit_params: |
            f[KA] ~ P1.0
            f[CL] ~ P1.0
            f[V1] ~ P20
            f[Q] ~ P0.5
            f[V2] ~ P100
            f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv] ~ spd_matrix() [
                [0.05],
                [0.01, 0.05],
                [0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.01, 0.05],
            ]
            f[KA_iov,CL_iov,V1_iov,Q_iov,V2_iov] ~ spd_matrix() [
                [0.05],
                [0.01, 0.05],
                [0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.05],
                [0.01, 0.01, 0.01, 0.01, 0.05],
            ]
            f[PNOISE] ~ P0.1
    INDIV:
        split_field: ID
        split_dict: "*"
        gen_params: |
            c[ID] = sequential(50)
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])
        fit_params: |
            r[KA, CL, V1, Q, V2] ~ mnorm([0,0,0,0,0], f[KA_isv,CL_isv,V1_isv,Q_isv,V2_isv])
    IOV:
        gen_params: |
            c[OC] = sequential(2)
            t[DOSE] = 2.0
            t[OBS] ~ unif(1.0, 50.0; 5)
            # t[OBS] = range(1.0, 50.0; 5)
            r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] ~ mnorm( 
                [0,0,0,0,0], 
                f[KA_iov, CL_iov, V2_iov, Q_iov, V3_iov] 
            )
        fit_params: |
            r[KA_iov, CL_iov, V1_iov, Q_iov, V2_iov] ~ mnorm( 
                [0,0,0,0,0], 
                f[KA_iov, CL_iov, V2_iov, Q_iov, V3_iov] 
            )
        split_field: OC
        split_dict: "*"

This is similar to the LEVEL_PARAMS with three levels from a fit_script and LEVEL_PARAMS with three levels from a gen_script examples. The Tut Script incorporates the Gen Script ‘params’ into ‘gen_params’ and the Fit Script ‘params’ into ‘fit_params, over all three levels.

Rules for LEVEL_PARAMS->params section

Each individual level of the LEVEL_PARAMS includes a verbatim section called ‘params’, as follows:-

LEVEL_PARAMS:
    <level_name>:
        params: |

However the ‘params’ section is limited in what expressions it can accept. For example it is not pseudo Python code, unlike the PREPROCESS, MODEL_PARAMS or DERIVATIVES sections, which act more like Python functions.

Generally only declarative expressions of the type:-

x[VAR] = <some definition>

Or

x[VAR] ~ <some definition>

Are allowed. You cannot use if statements for example. And all ‘params’ sections are declarative and unordered. i.e. the order of the statements in a single ‘params’ section makes no difference to the mixed effect model.

The variables you are allowed to declare on the left hand side depends on which type of script you are running:-

  • In a Fit Script you are allowed to declare f[X] and r[X] only.
  • In a Gen Script you are allowed to declare f[X], r[X], c[X] and t[X]
  • All variables must have a unique name, i.e. no duplicate c[X] or f[X] or r[X]
  • If a variable is on the right hand side of an expression it must be defined in a level above the current level

You can not use m[X], d[X], p[X] etc variables in LEVEL_PARAMS.

Like all verbatim sections it is possible to introduce syntax errors by writing malformed code in LEVEL_PARAMS. Any errors will be brought to the users attention when PoPy attempts to interpret the ‘params’ sections and form a tree structure to manage the fixed effect and random effect variables.

Back to Top