Accessing samples
FlexiChains stores data in a dict-of-array format (a column-oriented form, similar to dataframes). This means that it is very easy to access all the data for a given variable, but accessing all the data for a given sample (i.e., iteration / chain number) is a bit more involved.
Consider our now-familiar example chain:
using FlexiChainschn = FlexiChains.EXAMPLE_CHAIN╭─FlexiChain (10 iterations, 2 chains) ────────────────────────────────────────╮
│ ↓ iter = 101:110 │
│ → chain = 1:2 │
│ │
│ Parameters (3) ── VarName │
│ Float64 mu, tau │
│ Vector{Float64} theta (8,) │
│ │
│ Extras (2) │
│ Float64 logjoint │
│ Bool is_divergent │
╰──────────────────────────────────────────────────────────────────────────────╯Here, we have three parameters mu, tau, and theta. FlexiChains makes it very easy to access the value of a single parameter across all iterations and chains:
chn[@vn(mu)]┌ 10×2 DimArray{Float64, 2} Parameter(mu) ┐
├─────────────────────────────────────────┴─────────────── dims ┐
↓ iter Sampled{Int64} 101:110 ForwardOrdered Regular Points,
→ chain Sampled{Int64} 1:2 ForwardOrdered Regular Points
└───────────────────────────────────────────────────────────────┘
↓ → 1 2
101 11.3517 4.78008
102 11.3517 4.59214
103 11.3517 5.68752
104 11.3517 7.10065
⋮
107 1.30113 5.48149
108 0.915619 6.2456
109 0.284842 6.25491
110 0.516548 1.47832However, suppose you want to obtain the values of all parameters at a given iteration and chain number. For example, you might want to know what happened at the fifth iteration.
One way is to subset a chain. The issue is that that gives you just another chain back:
subsetted = chn[iter=5, chain=1]╭─FlexiChain (1 iteration, 1 chain) ───────────────────────────────────────────╮
│ ↓ iter = 105:105 │
│ → chain = 1:1 │
│ │
│ Parameters (3) ── VarName │
│ Float64 mu, tau │
│ Vector{Float64} theta (8,) │
│ │
│ Extras (2) │
│ Float64 logjoint │
│ Bool is_divergent │
╰──────────────────────────────────────────────────────────────────────────────╯and to access the data you will still need to index into it, which is rather awkward:
(mu=only(subsetted[@vn(mu)]), tau=only(subsetted[@vn(tau)]))(mu = 11.351650969356333, tau = 0.846032171561763)We should definitely like to have a better way of accessing this collection of values at a given iteration/chain.
Generally speaking, the operation above can be viewed as a transformation from column-oriented data to row-oriented data, or from a dict-of-arrays to an array-of-dicts. FlexiChains provides a few convenient ways for you to do this transformation.
values_at
The function FlexiChains.values_at is at the core of this transformation. Given iteration and chain indices (as keyword arguments), it returns some container that holds all the values for a given iteration.
draw = FlexiChains.values_at(chn; iter=5, chain=1)VarNames.Draw
├─ parameters
│ VarNamedTuple
│ ├─ mu => 11.351650969356333
│ ├─ tau => 0.846032171561763
│ └─ theta => [10.492901852254972, 11.46635882152557, 11.570533934319785, 12.130958608708998, 10.967643507906876, 12.011427174304124, 10.793983473375265, 11.581970127184468]
└─ extras
├─ logjoint = -45.082557361617546
└─ is_divergent = falseThere is a subtlety here in that the exact type of the container returned depends on what is stored in the chain.
For the interested reader, or if you are developing FlexiChains, this is determined by the structures stored in the chain, which are meant to be a 'template' for the container. If you are just a user who has obtained a FlexiChain from some library, should not need to worry about this, as the structures should be set up correctly for you by that library.
For chains that have parameter type VarName, i.e. a VNChain, the returned container should usually be a VarNames.Draw. This is a struct that separately stores the parameters and the stats as a VarNames.VarNamedTuple and a NamedTuple respectively.
If you instead get an OrderedDict, you should probably report this as a bug to the library that produced the chain.
For more information about these types, the reader is referred to the VarNames.jl docs. The key point here is that this is a high-fidelity representation of the data. You can access the parameters and extras separately:
ps = FlexiChains.parameters(draw)VarNamedTuple
├─ mu => 11.351650969356333
├─ tau => 0.846032171561763
└─ theta => [10.492901852254972, 11.46635882152557, 11.570533934319785, 12.130958608708998, 10.967643507906876, 12.011427174304124, 10.793983473375265, 11.581970127184468]FlexiChains.extras(draw)(logjoint = -45.082557361617546, is_divergent = false)The parameter VarNamedTuple can be further indexed into with VarNames to obtain the values of individual parameters. Much like a FlexiChain itself, you have the ability to index into a VarNamedTuple with sub-variables, such as theta[1:3]:
ps[@vn(mu)], ps[@vn(theta[1:3])](11.351650969356333, [10.492901852254972, 11.46635882152557, 11.570533934319785])Choosing a different container type
Sometimes, though, one might want different output formats. There is some support for converting to NamedTuple or AbstractDict, by passing an optional type argument.
Note that conversion to NamedTuple is lossy if you have VarNames that contain indexing or field access syntax (e.g., x[1] or x.a).
FlexiChains.values_at(chn, NamedTuple; iter=5, chain=1)(mu = 11.351650969356333, tau = 0.846032171561763, theta = [10.492901852254972, 11.46635882152557, 11.570533934319785, 12.130958608708998, 10.967643507906876, 12.011427174304124, 10.793983473375265, 11.581970127184468], logjoint = -45.082557361617546, is_divergent = false)FlexiChains.values_at(chn, Dict; iter=5, chain=1)Dict{Union{FlexiChains.Extra, FlexiChains.Parameter{var"#s91"}} where var"#s91"<:VarName, Any} with 5 entries:
Parameter(tau) => 0.846032
Parameter(mu) => 11.3517
Extra(:is_divergent) => false
Extra(:logjoint) => -45.0826
Parameter(theta) => [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114,…Obtaining only parameters
Often the stats are not of very much interest, and you just want the parameters. In that case, you can use parameters_at:
FlexiChains.parameters_at(chn; iter=5, chain=1)VarNamedTuple
├─ mu => 11.351650969356333
├─ tau => 0.846032171561763
└─ theta => [10.492901852254972, 11.46635882152557, 11.570533934319785, 12.130958608708998, 10.967643507906876, 12.011427174304124, 10.793983473375265, 11.581970127184468]This is exactly the same as values_at, except that it only returns the parameters.
Just as above, if you instead get an OrderedDict, you should probably report this as a bug to the library that produced the chain.
Arrays of samples
The functions above also accept multiple iteration or chain indices, and will return the corresponding array of containers.
FlexiChains.parameters_at(chn; iter=[5, 6], chain=1)┌ 2-element DimArray{VarNames.VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, 1} ┐
├──────────────────────────────────────────────────────────────────────── dims ┤
↓ iter Sampled{Int64} [105, 106] ForwardOrdered Irregular Points
└──────────────────────────────────────────────────────────────────────────────┘
105 … VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
106 VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])All of DimensionalData's selectors also work:
FlexiChains.parameters_at(chn; iter=Not(1..8), chain=1)┌ 10-element DimArray{VarNames.VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, 1} ┐
├──────────────────────────────────────────────────────────────────────── dims ┤
↓ iter Sampled{Int64} [101, …, 110] ForwardOrdered Irregular Points
└──────────────────────────────────────────────────────────────────────────────┘
101 … VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
102 VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
103 VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
104 VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
105 VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
106 … VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582])
107 VarNamedTuple(mu = 1.30113, tau = 1.25758, theta = [-0.278603, 2.20435, 1.4987, 1.25086, 1.20394, 2.61129, 1.21561, 1.20296])
108 VarNamedTuple(mu = 0.915619, tau = 0.973233, theta = [-0.312388, 1.68678, -0.409465, -0.343761, 0.0934012, 0.613312, 0.966749, 0.965976])
109 VarNamedTuple(mu = 0.284842, tau = 1.82906, theta = [1.86213, -1.40004, 3.83221, -1.47719, 1.469, 1.02763, 0.180669, -0.839965])
110 VarNamedTuple(mu = 0.516548, tau = 1.84247, theta = [2.57074, -2.26718, 3.4244, -0.980078, 0.672412, 0.74598, 1.65239, -1.42801])This also means that you can convert a VNChain back into an Array of Draw objects by passing : for both the iteration and chain indices.
FlexiChains.values_at(chn; iter=:, chain=:)┌ 10×2 DimArray{VarNames.Draw{VarNames.VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}, 2} ┐
├──────────────────────────────────────────────────────────────────────── dims ┤
↓ iter Sampled{Int64} 101:110 ForwardOrdered Regular Points,
→ chain Sampled{Int64} 1:2 ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
↓ → … 2
101 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 4.78008, tau = 2.78233, theta = [5.06589, 7.0214, 5.71944, 6.71881, 2.90095, -2.12359, 3.62712, 5.59417]), (logjoint = -54.7921, is_divergent = false))
102 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 4.59214, tau = 3.14813, theta = [5.82169, 6.69599, 5.35547, 6.51507, 2.97696, -1.63556, 3.65501, 6.79247]), (logjoint = -54.5201, is_divergent = false))
103 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 5.68752, tau = 3.19854, theta = [9.76186, 8.79631, 6.02066, 8.1514, 2.35677, 4.94412, 1.58788, 6.26805]), (logjoint = -54.9841, is_divergent = false))
104 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 7.10065, tau = 2.62221, theta = [12.5662, 6.24494, 7.5854, 5.73067, 4.84375, 7.32268, 5.19098, 7.05275]), (logjoint = -53.2478, is_divergent = false))
⋮ ⋱
107 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 5.48149, tau = 2.44535, theta = [2.74071, 5.78394, 8.56174, 8.99474, 5.77947, 7.40668, 5.41739, 5.32562]), (logjoint = -53.045, is_divergent = false))
108 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 6.2456, tau = 4.63625, theta = [15.5577, 8.86566, 2.77102, 4.54069, 8.30045, 4.91825, 11.8373, 8.66637]), (logjoint = -57.7038, is_divergent = false))
109 Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 6.25491, tau = 7.48281, theta = [8.97417, 8.43466, 2.55279, -8.15037, 8.45557, 0.756357, 18.4144, 3.61016]), (logjoint = -63.576, is_divergent = false))
110 … Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 1.47832, tau = 4.93959, theta = [6.06009, 4.07403, -0.727583, 6.03801, 1.85736, -4.66044, 2.52889, 12.7658]), (logjoint = -59.8085, is_divergent = false))Drawing random samples
To get a random sample from the chain, you can use Base.rand. The container type returned is the same as for values_at:
rand(chn)VarNames.Draw
├─ parameters
│ VarNamedTuple
│ ├─ mu => 11.351650969356333
│ ├─ tau => 0.846032171561763
│ └─ theta => [10.492901852254972, 11.46635882152557, 11.570533934319785, 12.130958608708998, 10.967643507906876, 12.011427174304124, 10.793983473375265, 11.581970127184468]
└─ extras
├─ logjoint = -45.082557361617546
└─ is_divergent = trueThis method follows Julia's conventions closely, so you can have an optional rng first argument, and you can also specify the number of samples to draw:
using Random: Xoshirorand(Xoshiro(468), chn, 2, 2)2×2 Matrix{VarNames.Draw{VarNames.VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}}:
Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582]), (logjoint = -45.0826, is_divergent = false)) … Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 4.78008, tau = 2.78233, theta = [5.06589, 7.0214, 5.71944, 6.71881, 2.90095, -2.12359, 3.62712, 5.59417]), (logjoint = -54.7921, is_divergent = false))
Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 11.3517, tau = 0.846032, theta = [10.4929, 11.4664, 11.5705, 12.131, 10.9676, 12.0114, 10.794, 11.582]), (logjoint = -45.0826, is_divergent = false)) Draw{VarNamedTuple{(:mu, :tau, :theta), Tuple{Float64, Float64, Vector{Float64}}}, @NamedTuple{logjoint::Float64, is_divergent::Bool}}(VarNamedTuple(mu = 5.48149, tau = 2.44535, theta = [2.74071, 5.78394, 8.56174, 8.99474, 5.77947, 7.40668, 5.41739, 5.32562]), (logjoint = -53.045, is_divergent = false))Weighted sampling is also supported (see the docstring below for more info).
If you only want parameters, pass the parameters_only=true keyword argument:
rand(chn, parameters_only=true)VarNamedTuple
├─ mu => 11.351650969356333
├─ tau => 0.846032171561763
└─ theta => [10.492901852254972, 11.46635882152557, 11.570533934319785, 12.130958608708998, 10.967643507906876, 12.011427174304124, 10.793983473375265, 11.581970127184468]Docstrings
FlexiChains.values_at — Function
FlexiChains.values_at(chn::FlexiChain; iter=:, chain=:)
FlexiChains.values_at(chn::FlexiChain, ::Type{Tout}; iter=:, chain=:)Extract all values from the chain corresponding to a particular set of MCMC iterations(s).
The iter and chain keyword arguments can be anything used to index into the respective dimensions of a FlexiChain, such as an integer, a vector of integers, a range, or a DimensionalData.jl selector. Both default to : (i.e. all iterations / all chains).
In particular, you can convert the entire chain into a DimMatrix of the desired output type by calling this function with no keyword arguments.
To get only the parameter keys, use FlexiChains.parameters_at.
The output type can be specified with an (optional) positional argument. Possible options are:
unspecified: uses the
structurestored in the chain to determine the output type. Specifically, for chains sampled with Turing.jl, the storedstructurewill beVarNamedTuple, and consequently the output ofparameters_atwill be aDynamicPPL.ParamsWithStatsper iteration (which stores the parameters as aVarNamedTuple, and the stats/extras as aNamedTuple).For chains that do not have a stored
structure, the default output will be anOrderedDict, orNamedTupleforTKey = Symbol.Tout <: AbstractDict: returns a dictionary mappingParameterOrExtra{TKey}to their values. This is the most faithful representation of the data in the chain.Tout = NamedTuple, orTout = ComponentArray: attempts to convert every key name to a Symbol, which is used as the field name in the outputNamedTupleorComponentArray.
This will throw an error if any key cannot be converted to a Symbol, or if there are duplicate key names after conversion. If you have parameter names that convert to the same Symbol, you can either use OrderedDict, subset the chain before calling this function, or rename your parameters. Furthermore, please be aware that this is a lossy conversion as it does not retain information about whether a key is a parameter or an extra.
For order-sensitive output types, such as OrderedDict, the keys are returned in the same order as they are stored in the FlexiChain. This also corresponds to the order returned by keys(chn).
FlexiChains.parameters_at — Function
FlexiChains.parameters_at(chn::FlexiChain; iter=:, chain=:)
FlexiChains.parameters_at(chn::FlexiChain, ::Type{Tout}; iter=:, chain=:)Extract all parameter values from the chain corresponding to a particular set of MCMC iteration(s), discarding non-parameter (i.e. Extra) keys.
The iter and chain keyword arguments can be anything used to index into the respective dimensions of a FlexiChain, such as an integer, a vector of integers, a range, or a DimensionalData.jl selector. Both default to : (i.e. all iterations / all chains).
In particular, you can convert the entire chain into a DimMatrix of the desired output type by calling this function with no keyword arguments.
To get all keys (not just parameters), use FlexiChains.values_at.
The output type for each iteration can be specified with the first (optional) positional argument. Possible options are:
unspecified: uses the
structurestored in the chain to determine the output type. Specifically, for chains sampled with Turing.jl, the storedstructurewill beVarNamedTuple, and consequently the output ofparameters_atwill be aVarNamedTupleper iteration.For chains that do not have a stored
structure, the default output will be aNamedTupleif the key type isSymbol, and anOrderedDictotherwise.Tout <: AbstractDict: returns a dictionary mappingTKeyto their values.Tout = NamedTupleorTout <: ComponentArray: attempts to convert every parameter name to a Symbol, which is used as the field name in the outputNamedTupleorComponentArray.
This will throw an error if any key cannot be converted to a Symbol, or if there are duplicate key names after conversion. If you have parameter names that convert to the same Symbol, you can either use OrderedDict, subset the chain before calling this function, or rename your parameters. Furthermore, please be aware that this is a lossy conversion as it does not retain information about whether a key is a parameter or an extra.
For order-sensitive output types, such as OrderedDict, the parameters are returned in the same order as they are stored in the FlexiChain. This also corresponds to the order returned by FlexiChains.parameters(chn).
Base.rand — Function
Base.rand(rng::Random.AbstractRNG, chn::FlexiChain, dims::Int...; parameters_only=false, weights=nothing)Sample uniformly from a FlexiChain with replacement.
Pass weights::StatsBase.AbstractWeights to sample with probabilities proportional to the weights instead. Supply one weight per sample, with all iterations of the first chain followed by all iterations of the second chain, and so on.
rand([rng,] chn) returns a single sample, whereas rand([rng,] chn, dims...) returns an Array of samples with dimensions dims.... The parameters_only keyword specifies whether the returned samples include only parameters, or both parameters and extras.
In general, the return type of rand is the same as the return type of FlexiChains.parameters_at or FlexiChains.values_at (but wrapped in an Array if dims is not empty). This means that the return type can depend on the 'structure' stored in the FlexiChain.
For example, if chn::FlexiChain{VarName} was constructed using Turing.jl, rand(chn) will return a DynamicPPL.ParamsWithStats, and rand(chn, parameters_only=true) will return a DynamicPPL.VarNamedTuple.