Skip to content

Tables.jl ​

Documentation for Tables.jl ↗

FlexiChains implements a Tables.jl interface which allows you to easily convert a FlexiChain or FlexiSummary into any type that consumes tabular data, e.g., a DataFrame.

Chains ​

In fact, FlexiChains implements two different Tables.jl interfaces for chains which produce wide-format and long-format tables respectively.

This is best demonstrated with an example. First let's sample a chain as usual:

julia
using FlexiChains, DynamicPPL, LinearAlgebra, Distributions

@model function f()
    x ~ Normal(10.0)
    y ~ Bernoulli()
    z ~ MvNormal(zeros(2), I)
end

chn = FlexiChains._make_prior_chain(f(), 4, 2)
╭─FlexiChain (4 iterations, 2 chains) ─────────────────────────────────────────╮
│ ↓ iter  = 1:4                                                                │
│ → chain = 1:2                                                                │
│                                                                              │
│ Parameters (3) ── VarName                                                    │
│  Float64          x                                                          │
│  Bool             y                                                          │
│  Vector{Float64}  z (2,)                                                     │
│                                                                              │
│ Extras (3)                                                                   │
│  Float64  logprior, loglikelihood, logjoint                                  │
╰──────────────────────────────────────────────────────────────────────────────╯

Wide format ​

Now we can convert this into a wide-format DataFrame by wrapping the chain in Wide:

julia
using DataFrames

DataFrame(Wide(chn))
8×6 DataFrame
Rowiterchainxyz[1]z[2]
Int64Int64Float64BoolFloat64Float64
11111.3708true-0.5495710.539258
22110.0114true-0.141802-0.963475
3319.08378false-1.802220.389987
44110.5098true-0.133026-0.781501
5127.75106true0.272464-0.571211
6228.46946true-0.199529-0.237951
73210.6786false0.9366640.107682
8429.72404false0.8192560.438132

Wide format is the default layout for FlexiChains, so if you aren't specifying any additional keyword arguments to Wide, you technically don't have to wrap it at all:

julia
DataFrame(chn) == DataFrame(Wide(chn))
true

Long format ​

To get a long-format DataFrame, you can wrap the chain in Long:

julia
DataFrame(Long(chn))
32×4 DataFrame
Rowiterchainparamvalue
Int64Int64VarNameFloat64
111x11.3708
221x10.0114
331x9.08378
441x10.5098
512x7.75106
622x8.46946
732x10.6786
842x9.72404
911y1.0
1021y1.0
1131y0.0
1241y1.0
1312y1.0
1422y1.0
1532y0.0
1642y0.0
1711z[1]-0.549571
1821z[1]-0.141802
1931z[1]-1.80222
2041z[1]-0.133026
2112z[1]0.272464
2222z[1]-0.199529
2332z[1]0.936664
2442z[1]0.819256
2511z[2]0.539258
2621z[2]-0.963475
2731z[2]0.389987
2841z[2]-0.781501
2912z[2]-0.571211
3022z[2]-0.237951
3132z[2]0.107682
3242z[2]0.438132

Notice, though, that this promotes y to Float64, because all parameter values are stored in a single column.

Both the Wide and Long wrapper structs accept keyword arguments which determine whether array-valued parameters (like z) are split up, and whether or not to include the Extra keys in the chain as well.

Summaries ​

For FlexiSummary, the long format is not supported: only the wide format is implemented. In contrast to Wide(::FlexiChain), where each parameter is given a different column, the wide format for FlexiSummary splits each statistic into a separate column.

julia
fs = summarystats(chn)
DataFrame(Wide(fs))
4×10 DataFrame
Rowparammeanstdmcseess_bulkess_tailrhatq5q50q95
VarNameFloat64Float64Float64Float64Float64Float64Float64Float64Float64
1x9.699871.20682NaNNaNNaN1.383228.00259.8677411.1285
2y0.6250.517549NaNNaNNaN1.527530.01.01.0
3z[1]-0.099720.859471NaNNaNNaN1.3188-1.36379-0.1374140.895571
4z[2]-0.1348850.588318NaNNaNNaN1.11983-0.899784-0.06513450.503864

Like for FlexiChain, the Wide wrapper is the default Tables.jl implementation for FlexiSummary, so you can also just do DataFrame(fs).

Wide(::FlexiSummary) takes the same keyword arguments as Wide(::FlexiChain).

Splitting VarNames

Please note that the act of summarising will typically already cause array-valued variables to be split up. If this has already been done, then using Wide(...; split_varnames=false) cannot reverse this!

julia
w = Wide(mean(chn), split_varnames=false)
DataFrame(w)
4×2 DataFrame
Rowparamstat
VarNameFloat64
1x9.69987
2y0.625
3z[1]-0.09972
4z[2]-0.134885

Notice how z[1] and z[2] are already split up despite the split_varnames=false argument. If you want to prevent this, you need to specify split_varnames=false at the summary step as well:

julia
w = Wide(mean(chn; split_varnames=false), split_varnames=false)
DataFrame(w)
3×2 DataFrame
Rowparamstat
VarName…Any
1x9.69987
2y0.625
3z[-0.09972, -0.134885]

Docstrings ​

FlexiChains.Wide Type
julia
FlexiChains.Wide(
    chn::Union{<:FlexiChain,<:FlexiSummary};
    split_varnames::Bool=true,
    parameters_only::Bool=true
)

A wrapper struct indicating a 'wide' table format. The exact meaning depends on whether the input is a FlexiChain or a FlexiSummary. A FlexiChain will have each parameter in a separate column; conversely, a FlexiSummary will have each summary statistic in a separate column.

Example (FlexiChain)

julia
using Turing, FlexiChains, DataFrames

@model function f()
    x ~ Normal()
    b ~ Bernoulli()
end
chn = sample(f(), Prior(), MCMCThreads(), 10, 2; chain_type=VNChain)

df = DataFrame(Wide(chn))

returns a DataFrame that looks like the following. Each parameter is a different column, and the iter and chain dimensions are represented as separate columns as well; iter varies faster than chain.

Note

Because all parameter names must be converted to Symbol for column names, this may lead to clashes between e.g. parameters and extras which convert to the same Symbol. FlexiChains will error in such a situation.

julia
20×4 DataFrame
 Row │ iter   chain  x          b
     │ Int64  Int64  Float64    Bool
─────┼────────────────────────────────
   1 │     1      1  -1.38809   false
   2 │     2      1  -0.511805   true
   3 │     3      1  -1.37277   false
  ⋮  │   ⋮      ⋮        ⋮        ⋮
  18 │     8      2   2.31312   false
  19 │     9      2   1.58254    true
  20 │    10      2  -1.14516   false

Keyword arguments

  • split_varnames: whether to split array-valued parameters into scalar leaves. If true (the default), then array-valued parameters are split into scalar leaves, e.g. a vector-valued parameter x would be split into x[1], x[2], etc.

  • parameters_only: whether to include only parameters (and skip extras) in the resulting table. Defaults to true.

Example (FlexiSummary)

julia
julia> df = DataFrame(Wide(summarystats(chn)))
2×10 DataFrame
 Row │ param     mean       std       mcse      ess_bulk  ess_tail  rh ⋯
     │ VarName…  Float64    Float64   Float64   Float64   Float64   Fl ⋯
─────┼──────────────────────────────────────────────────────────────────
   1 │ x         -0.253812  0.987327  0.193554   26.0206    25.641     ⋯
   2 │ b          0.5       0.512989  0.113426   20.4545   NaN      Na
                                                       4 columns omitted

julia> df = DataFrame(Wide(mean(chn)))
2×2 DataFrame
 Row │ param     stat
     │ VarName…  Float64
─────┼─────────────────────
   1 │ x         -0.253812
   2 │ b          0.5
source
FlexiChains.Long Type
julia
FlexiChains.Long(
    chn::FlexiChain;
    split_varnames::Bool=true,
    parameters_only::Bool=true
)

A wrapper struct indicating that a FlexiChain should be converted to a 'long' table format, where all values are stacked into a single column, and there is an additional column indicating the parameter name.

Note

Because all parameter values are stacked into a single column, note that the resulting element type of the value column will be the common supertype of all parameter values. This can cause data to be promoted to a type that is not the same as its original type. For example, the b parameter below is converted to Float64.

Example

julia
using Turing, FlexiChains, DataFrames

@model function f()
    x ~ Normal()
    b ~ Bernoulli()
end
chn = sample(f(), Prior(), MCMCThreads(), 10, 2; chain_type=VNChain)

df = DataFrame(Long(chn))

returns a DataFrame that looks like the following. The iter and chain dimensions are represented as separate columns as before, but now the parameter names are stacked into a single param column, and the values are stacked into a single value column.

The iter column varies faster than the chain column, which in turn varies faster than the param column.

julia
40×4 DataFrame
 Row │ iter   chain  param     value
     │ Int64  Int64  VarName…  Float64
─────┼─────────────────────────────────
   1 │     1      1  x         -1.38809
   2 │     2      1  x         -0.511805
   3 │     3      1  x         -1.37277
  ⋮  │   ⋮      ⋮      ⋮         ⋮
  38 │     8      2  b          0.0
  39 │     9      2  b          1.0
  40 │    10      2  b          0.0

Keyword arguments

  • split_varnames: whether to split array-valued parameters into scalar leaves. If true (the default), then array-valued parameters are split into scalar leaves, e.g. a vector-valued parameter x would be split into x[1], x[2], etc.

  • parameters_only: whether to include only parameters (and skip extras) in the resulting table. Defaults to true.

Additionally, when parameters_only=true (the default), the Parameter wrapper is stripped from keys. Otherwise, the Parameter/Extra wrappers are retained. If you want to unwrap them, you can use FlexiChains.get_name on the param column of the resulting table.

source