Chris Parmer — home

Marimekko chart

Business — UCB Admissions by department

Example from the compendium of canonical charts

Business — Marimekko chart (UCB Admissions by department)

Python Code

"""Business — Marimekko chart (UCB Admissions by department)."""


import pandas as pd
import plotly.graph_objects as go

URL = "https://vincentarelbundock.github.io/Rdatasets/csv/datasets/UCBAdmissions.csv"


def generate():
    print("fetching UCBAdmissions from Rdatasets …")
    df = fetch_csv(URL)
    print("columns:", df.columns.tolist())
    print(df.head(6))

    df.columns = [c.strip().lower() for c in df.columns]
    # Expected cols: admit, gender, dept, freq

    # Aggregate over gender to get dept totals
    dept_total = df.groupby("dept")["freq"].sum().reset_index()
    dept_total.columns = ["dept", "total"]
    dept_total = dept_total.sort_values("dept")

    admitted = df[df["admit"].str.lower() == "admitted"].groupby("dept")["freq"].sum()
    rejected = df[df["admit"].str.lower() == "rejected"].groupby("dept")["freq"].sum()

    dept_total = dept_total.set_index("dept")
    dept_total["admitted"] = admitted
    dept_total["rejected"] = rejected
    dept_total["admit_pct"] = dept_total["admitted"] / dept_total["total"]
    dept_total["reject_pct"] = dept_total["rejected"] / dept_total["total"]
    dept_total = dept_total.reset_index()

    grand_total = dept_total["total"].sum()
    n_dept = len(dept_total)

    # Compute proportional widths (sum to 1) and bar centers
    dept_total["width"] = dept_total["total"] / grand_total
    dept_total["center"] = dept_total["width"].cumsum() - dept_total["width"] / 2

    fig = go.Figure()

    for _, row in dept_total.iterrows():
        dept = row["dept"]
        cx   = row["center"]
        w    = row["width"]

        # Admitted bar (bottom portion, from 0 to admit_pct)
        fig.add_trace(go.Bar(
            x=[cx],
            y=[row["admit_pct"]],
            width=[w * 0.97],  # small gap between departments
            marker_color=GREEN,
            marker_line_width=0,
            name="Admitted" if dept == dept_total["dept"].iloc[0] else None,
            showlegend=(dept == dept_total["dept"].iloc[0]),
            legendgroup="Admitted",
            hovertemplate=f"<b>Dept {dept}</b><br>Admitted: {row['admit_pct']:.1%}<br>n={row['admitted']:.0f}<extra></extra>",
            text=[f"<b>Dept {dept}</b><br>{row['admit_pct']:.0%}"],
            textposition="inside",
            textfont=dict(size=10, color="white"),
            base=0,
        ))

        # Rejected bar (top portion, from admit_pct to 1)
        fig.add_trace(go.Bar(
            x=[cx],
            y=[row["reject_pct"]],
            width=[w * 0.97],
            marker_color=PINK,
            marker_line_width=0,
            name="Rejected" if dept == dept_total["dept"].iloc[0] else None,
            showlegend=(dept == dept_total["dept"].iloc[0]),
            legendgroup="Rejected",
            hovertemplate=f"<b>Dept {dept}</b><br>Rejected: {row['reject_pct']:.1%}<br>n={row['rejected']:.0f}<extra></extra>",
            base=row["admit_pct"],
        ))

    # Column-width legend via annotations
    for _, row in dept_total.iterrows():
        fig.add_annotation(
            x=row["center"], y=-0.06,
            xref="x", yref="paper",
            text=f"{row['total']:.0f}",
            showarrow=False,
            font=dict(size=9, color="#60646c"),
        )

    fig.update_layout(
        barmode="stack",
        xaxis=dict(
            title="Department (bar width ∝ total applicants)",
            tickvals=dept_total["center"].tolist(),
            ticktext=[f"Dept {d}" for d in dept_total["dept"]],
            showgrid=False,
            zeroline=False,
            range=[0, 1],
        ),
        yaxis=dict(
            title="Share of applicants",
            tickformat=".0%",
            range=[0, 1.02],
            showgrid=False,
            zeroline=False,
        ),
        legend=dict(orientation="h", y=-0.18),
        margin=dict(t=50, b=80, l=55, r=40),
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly