Working with NamedTuples
Julia's NamedTuple type is used throughout Pumas for specifying model parameters. Understanding how to manipulate NamedTuples is useful for common workflows such as:
- Sequential model building where you add or remove parameters
- Covariate selection workflows
- Simulation studies with varying parameter sets
- Sensitivity analyses across model variants
This page covers the Julia language features for working with NamedTuples that are most relevant to Pumas users. For comprehensive documentation, see the Julia manual section on NamedTuples.
Creating and Modifying NamedTuples
A NamedTuple is an immutable collection of named values. In Pumas, model parameters are passed as NamedTuples to functions like fit and simobs. Julia provides several built-in operations for creating new NamedTuples based on existing ones.
Merging and Overriding with Splatting
The most straightforward approach uses Julia's splatting (...) and merging capabilities. When you splat a NamedTuple inside a new one, later values override earlier ones with the same name:
initial_params = (tvka = 1.0, tvcl = 2.5, tvv = 10.0, Ω₁₁ = 0.04, Ω₂₂ = 0.09)(tvka = 1.0, tvcl = 2.5, tvv = 10.0, Ω₁₁ = 0.04, Ω₂₂ = 0.09)updated_params = (; initial_params..., tvka = 1.5, wtoncl = 0.5)(tvka = 1.5, tvcl = 2.5, tvv = 10.0, Ω₁₁ = 0.04, Ω₂₂ = 0.09, wtoncl = 0.5)Here tvka was updated from 1.0 to 1.5 and a new parameter wtoncl was added, while all other parameters remained unchanged.
Using merge for Parameter Updates
The merge function provides equivalent functionality with a slightly different syntax:
merge(initial_params, (tvka = 1.5, wtoncl = 0.5))(tvka = 1.5, tvcl = 2.5, tvv = 10.0, Ω₁₁ = 0.04, Ω₂₂ = 0.09, wtoncl = 0.5)Both approaches are equivalent; use whichever reads more clearly in your code.
Removing Parameters with Base.structdiff
When you need to remove parameters from your initial estimates, Base.structdiff returns a new NamedTuple with specified keys removed:
Base.structdiff(updated_params, NamedTuple{(:Ω₁₁,)})(tvka = 1.5, tvcl = 2.5, tvv = 10.0, Ω₂₂ = 0.09, wtoncl = 0.5)Multiple parameters can be removed at once:
Base.structdiff(updated_params, NamedTuple{(:Ω₁₁, :Ω₂₂)})(tvka = 1.5, tvcl = 2.5, tvv = 10.0, wtoncl = 0.5)The second argument to structdiff is a NamedTuple type specifying which keys to remove. Note the syntax NamedTuple{(key1, key2)} with a tuple of symbols.
Sequential Model Fitting Workflow
A common use case is building models incrementally, using estimates from simpler models as starting values for more complex ones.
Using Estimates from Previous Fits
After fitting a model, you can extract the parameter estimates using coef and use them as initial values for the next fit:
using Pumas
base_inits =
(tvka = 1.0, tvcl = 2.0, tvv = 10.0, Ω = Diagonal([0.04, 0.04, 0.04]), σ_prop = 0.1)(tvka = 1.0, tvcl = 2.0, tvv = 10.0, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04], σ_prop = 0.1)After fitting the base model (not shown), you would typically get estimates back via coef(fit_result). For demonstration, assume we obtained these estimates:
base_estimates =
(tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = Diagonal([0.05, 0.06, 0.07]), σ_prop = 0.08)(tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08)Adding Parameters for a Covariate Model
When extending the model to include covariates, add the new parameters to the previous estimates:
covariate_inits = (; base_estimates..., wtoncl = 0.75)(tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08, wtoncl = 0.75)Removing Parameters When Simplifying Models
If backward elimination suggests removing a covariate, use Base.structdiff:
simplified_inits = Base.structdiff(covariate_inits, NamedTuple{(:wtoncl,)})(tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08)Simulation Applications
Parameter manipulation is especially useful in simulation studies where you need to vary parameters systematically:
sim_params = (
tvka = 1.0,
tvcl = 2.0,
tvv = 10.0,
wtoncl = 0.75,
Ω = Diagonal([0.04, 0.04, 0.04]),
σ_prop = 0.1,
)
scenarios = [
"Low CL" => (; sim_params..., tvcl = 1.5),
"High CL" => (; sim_params..., tvcl = 3.0),
"No weight effect" => (; sim_params..., wtoncl = 0.0),
]3-element Vector{Pair{String, @NamedTuple{tvka::Float64, tvcl::Float64, tvv::Float64, wtoncl::Float64, Ω::Diagonal{Float64, Vector{Float64}}, σ_prop::Float64}}}:
"Low CL" => (tvka = 1.0, tvcl = 1.5, tvv = 10.0, wtoncl = 0.75, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04], σ_prop = 0.1)
"High CL" => (tvka = 1.0, tvcl = 3.0, tvv = 10.0, wtoncl = 0.75, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04], σ_prop = 0.1)
"No weight effect" => (tvka = 1.0, tvcl = 2.0, tvv = 10.0, wtoncl = 0.0, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04], σ_prop = 0.1)Covariate Selection Workflows
These techniques support systematic covariate testing by building different parameter sets for each candidate model:
structural_params =
(tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = Diagonal([0.05, 0.06, 0.07]), σ_prop = 0.08)
covariate_tests = [
"WT on CL" => (; structural_params..., wtoncl = 0.75),
"AGE on V" => (; structural_params..., ageonv = 0.5),
"WT on CL + AGE on V" => (; structural_params..., wtoncl = 0.75, ageonv = 0.5),
"WT on CL+V" => (; structural_params..., wtoncl = 0.75, wtonv = 0.3),
]4-element Vector{Pair{String, NamedTuple}}:
"WT on CL" => (tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08, wtoncl = 0.75)
"AGE on V" => (tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08, ageonv = 0.5)
"WT on CL + AGE on V" => (tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08, wtoncl = 0.75, ageonv = 0.5)
"WT on CL+V" => (tvka = 1.2, tvcl = 2.3, tvv = 12.0, Ω = [0.05 0.0 0.0; 0.0 0.06 0.0; 0.0 0.0 0.07], σ_prop = 0.08, wtoncl = 0.75, wtonv = 0.3)Building Model Hierarchies
For systematic model development, you can build a hierarchy of parameter sets:
base = (tvka = 1.0, tvcl = 2.0, tvv = 10.0, σ = 0.1)(tvka = 1.0, tvcl = 2.0, tvv = 10.0, σ = 0.1)with_eta = (; base..., Ω = Diagonal([0.04, 0.04, 0.04]))(tvka = 1.0, tvcl = 2.0, tvv = 10.0, σ = 0.1, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04])with_wt = (; with_eta..., wtoncl = 0.75)(tvka = 1.0, tvcl = 2.0, tvv = 10.0, σ = 0.1, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04], wtoncl = 0.75)full_model = (; with_wt..., ageonv = 0.5)(tvka = 1.0, tvcl = 2.0, tvv = 10.0, σ = 0.1, Ω = [0.04 0.0 0.0; 0.0 0.04 0.0; 0.0 0.0 0.04], wtoncl = 0.75, ageonv = 0.5)This hierarchical approach ensures consistency across related models and makes it easy to track which parameters were added at each stage.
Summary
Julia's built-in NamedTuple operations provide everything needed for managing parameters across model fits:
| Operation | Syntax |
|---|---|
| Add/override parameters | (; old_params..., new_param = value) |
| Merge parameter sets | merge(params1, params2) |
| Remove parameters | Base.structdiff(params, NamedTuple{tuple_of_keys}) |
| Extract from fit | coef(fit_result) |
These operations support efficient parameter management in pharmacometric workflows, from sequential model building to simulation studies and covariate selection.