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)."""
from pathlib import Path

# ── Palette + theme (matching the Plotly Studio gallery these charts ship in) ──
VIOLET, TEAL, GREEN, PINK, ORANGE = "#845EEE", "#52B3D0", "#55B685", "#DA5597", "#E9A23B"
PRIMARY, SECONDARY = VIOLET, TEAL
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE]
BG, TEXT, GRID, MUTED = "#ffffff", "#1c2024", "#d9d9e0", "#60646c"
FONT = "Inter, -apple-system, BlinkMacSystemFont, sans-serif"
COLORSCALE = [[0, "rgba(132, 94, 238, 0.05)"], [1, "rgba(132, 94, 238, 0.9)"]]


def apply_theme(fig):
    """Light gallery theme: white background, Inter font, soft gridlines."""
    fig.update_layout(
        paper_bgcolor=BG, plot_bgcolor=BG, colorway=COLORWAY,
        font=dict(family=FONT, color=TEXT, size=12),
        legend=dict(font=dict(color=TEXT)),
        hoverlabel=dict(bgcolor="#f0f0f3", font=dict(color=TEXT, family=FONT), bordercolor=GRID),
    )
    fig.update_xaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
    fig.update_yaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)


def fetch_csv(url, **kwargs):
    import io
    import pandas as pd
    import requests
    r = requests.get(url, timeout=60)
    r.raise_for_status()
    return pd.read_csv(io.StringIO(r.text), **kwargs)


def fetch_json(url):
    import requests
    r = requests.get(url, timeout=60)
    r.raise_for_status()
    return r.json()

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),
    )

    apply_theme(fig)
    return fig


fig = generate()
fig.show()

Made with Plotly