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
1119.82208true0.2188321.19989
22110.4105true-0.02819790.939742
3319.11711false0.7872-1.24085
44110.2051true-0.942249-1.53014
5128.93854true0.306157-1.2617
62210.1452false0.2136650.0171128
7328.91456false-1.211221.39277
84210.6057false-0.517808-0.466001

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
111x9.82208
221x10.4105
331x9.11711
441x10.2051
512x8.93854
622x10.1452
732x8.91456
842x10.6057
911y1.0
1021y1.0
1131y0.0
1241y1.0
1312y1.0
1422y0.0
1532y0.0
1642y0.0
1711z[1]0.218832
1821z[1]-0.0281979
1931z[1]0.7872
2041z[1]-0.942249
2112z[1]0.306157
2222z[1]0.213665
2332z[1]-1.21122
2442z[1]-0.517808
2511z[2]1.19989
2621z[2]0.939742
2731z[2]-1.24085
2841z[2]-1.53014
2912z[2]-1.2617
3022z[2]0.0171128
3132z[2]1.39277
3242z[2]-0.466001

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.769860.685658NaNNaNNaN1.005038.922959.9836310.5374
2y0.50.534522NaNNaNNaNNaN0.00.51.0
3z[1]-0.1467020.682285NaNNaNNaN0.926239-1.117080.09273350.618835
4z[2]-0.1186471.18565NaNNaNNaN0.939105-1.43619-0.2244441.32526

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.76986
2y0.5
3z[1]-0.146702
4z[2]-0.118647

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.76986
2y0.5
3z[-0.146702, -0.118647]

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
─────┼────────────────────────────────
   11      1  -1.38809   false
   22      1  -0.511805   true
   33      1  -1.37277   false

  188      2   2.31312   false
  199      2   1.58254    true
  2010      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
─────┼─────────────────────────────────
   11      1  x         -1.38809
   22      1  x         -0.511805
   33      1  x         -1.37277

  388      2  b          0.0
  399      2  b          1.0
  4010      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