Plotting MCMCChains with Makie

It is currently trivially simple to plot the posterior likelihood functions from a MCMC chain with StatsPlots. However, it was quite easy to make a function that allows the same automatic plotting with Makie:

Setup
julia> using Turing

julia> @model function normalmodel(y)
           # Our prior belief about the probability

           μ ~ Uniform(extrema(y)...)
           σ ~ Uniform(0, 10^3)  # σ must be positive

           # The number of observations.
           N = length(y)

          # Each sample is distributed as Normal(μ, σ)
          y .~ filldist(Normal(μ, σ), N)
      end

julia> N_samples = 20;

julia> N_MCMC = 10^3;

julia> μ_true = 15;

julia> σ_true = 1;

julia> data_normal = rand(Normal(μ_true, σ_true), N_samples);

julia> normal_instance = normalmodel(data_normal);

julia> Threads.nthreads()
4

julia> chain_normal = sample(normal_instance, NUTS(), MCMCThreads(), N_MCMC, 4);
Ploting function definition
julia> using GLMakie

julia> import Makie.plot

julia> function Makie.plot(ch::Chains)
           fig = Figure()
           for (ind, param) in enumerate(ch.name_map.parameters)
               ax = Axis(fig[ind, 1], title=string(param))
               for (ind2, datavec) in enumerate(eachcol(getindex(ch, param).data))
                   # Get current default colorpalette
                   colors = Makie.current_default_theme().attributes[:palette][][:color][]
                   density!(ax, datavec, color=(:black, 0.0),
                       strokearound=true,
                       strokewidth=2,
                       strokecolor=colors[ind2%length(colors)]
                   )
               end
           end
           display(fig)
           return fig
       end

allows the following line

julia> plot(chain_normal)

to produce the following plot:

Would it be possible to make this work by default? E.g. defining a recipe for Chains within Makie?

3 Likes

I would say something like this is better off in a glue package. For base Makie I don’t think the convenience for the people who want this would be worth the inconvenience of extra dependencies, compile and using time for all.

This stuff actually works pretty well with AlgebraOfGraphics, one just needs to transform the chains into appropriate table objects. They are already tables but not quite in the right format

It indeed did until a few days ago. Then it started to complain about a colormap if compiled with GLMakie. I wish there was something like fillcolor=:none in Makie’s density().

ERROR: LoadError: KeyError: key :colormap not found
Stacktrace:
  [1] getindex(h::Dict{Symbol, Observables.Observable}, key::Symbol)
    @ Base ./dict.jl:484
  [2] getindex
    @ ~/.julia/packages/MakieCore/8YGMv/src/attributes.jl:96 [inlined]
  [3] getindex(x::MakieCore.Mesh{Tuple{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, GeometryBasics.Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, GeometryBasics.Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}}}, key::Symbol)
    @ MakieCore ~/.julia/packages/MakieCore/8YGMv/src/attributes.jl:192

FWIW, there’s more than one way to convert MCMC draws to a table. MCMCChains.Chains takes an approach that mirrors how Chains itself flattens multidimensional outputs. ArviZ.jl, which can consume Chains objects, takes a different approach. Both implement the Tables interface and can be used with StatsPlots.jl and AlgebraOfGraphics.jl, but which approach is best for you may vary from plot to plot.

Here are some examples of using ArviZ.jl with AlgebraOfGraphics: Creating custom plots · ArviZ.jl

1 Like

Could someone provide example code of how to transform the samples into a table in a format consumable by AlgebraOfGraphics? Ideally a MWE like the one I provided, but using AlgebraOfGraphics

This about does it.

using AlgebraOfGraphics, GLMakie
using AlgebraOfGraphics: density
fig = Figure()
for (i, var_name) in enumerate((:μ, :σ))
    draw!(
        fig[i, 1],
        data(chain_normal) *
        mapping(var_name; color=:chain => nonnumeric) *
        density() *
        visual(fillalpha=0)
    )
end

The main difference from the plot in the OP is that AlgebraOfGraphics uses the variable name as the x-axis label instead of the title, which is sensible since that’s what the ticks correspond to.

We need to loop over the variable names in this case because they’re found in different columns.

1 Like

You can also stack the DataFrame into long format, then you don’t have to loop. I always forget how to use AoG with wide format effectively