Chris Parmer — home

Corner plot

Bayesian Inference — posterior samples

Example from the compendium of canonical charts

Bayesian Inference — Corner plot (posterior samples)

Python Code

"""Bayesian Inference — Corner plot (posterior samples)."""


import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots


def generate():
    print("generating posterior corner plot …")
    rng = np.random.default_rng(42)
    n_samples = 5000
    mean = [1.0, 2.0, 3.0]
    cov = [
        [1.0,  0.7, -0.5],
        [0.7,  1.0,  0.3],
        [-0.5, 0.3,  1.0],
    ]
    samples = rng.multivariate_normal(mean, cov, n_samples)
    param_names = ["θ₁", "θ₂", "θ₃"]
    n_params = 3

    # Compute axis ranges per parameter (±3.5 sigma)
    ranges = []
    for i in range(n_params):
        mu = samples[:, i].mean()
        sigma = samples[:, i].std()
        ranges.append((mu - 3.5 * sigma, mu + 3.5 * sigma))

    fig = make_subplots(
        rows=n_params,
        cols=n_params,
        shared_xaxes=False,
        shared_yaxes=False,
        horizontal_spacing=0.06,
        vertical_spacing=0.06,
    )

    for row in range(n_params):
        for col in range(n_params):
            if col > row:
                # Upper triangle — leave blank
                continue
            elif col == row:
                # Diagonal — 1D marginal histogram
                fig.add_trace(
                    go.Histogram(
                        x=samples[:, row],
                        nbinsx=40,
                        marker_color=VIOLET,
                        opacity=0.8,
                        showlegend=False,
                        hovertemplate=f"{param_names[row]}: %{{x:.2f}}<br>Count: %{{y}}<extra></extra>",
                    ),
                    row=row + 1,
                    col=col + 1,
                )
                # True-value vertical line
                fig.add_vline(
                    x=mean[row],
                    line=dict(color=TEAL, width=1.5, dash="dash"),
                    row=row + 1,
                    col=col + 1,
                )
            else:
                # Lower triangle — 2D joint histogram
                fig.add_trace(
                    go.Histogram2d(
                        x=samples[:, col],
                        y=samples[:, row],
                        nbinsx=30,
                        nbinsy=30,
                        colorscale=COLORSCALE,
                        showscale=False,
                        hovertemplate=(
                            f"{param_names[col]}: %{{x:.2f}}<br>"
                            f"{param_names[row]}: %{{y:.2f}}<br>"
                            "Count: %{z}<extra></extra>"
                        ),
                    ),
                    row=row + 1,
                    col=col + 1,
                )

    # Axis labels on outer edges only
    for i in range(n_params):
        # Bottom row: x-axis labels
        fig.update_xaxes(
            title_text=param_names[i],
            row=n_params,
            col=i + 1,
        )
        # Left column: y-axis labels for off-diagonal
        if i > 0:
            fig.update_yaxes(
                title_text=param_names[i],
                row=i + 1,
                col=1,
            )

    fig.update_layout(
        margin=dict(t=50, b=60, l=80, r=60),
    )

    return fig


if __name__ == "__main__":
    generate()

Made with Plotly