Chris Parmer — home

Dumbbell chart

the life-expectancy gap between women and men, 2022

Example from Plotly for highly customizable print-ready data visualization · shared helpers

Dumbbell chart — the life-expectancy gap between women and men, 2022

Python Code

"""Dumbbell chart — the life-expectancy gap between women and men, 2022.

Two dots joined by a rule for each country, sorted by the size of the gap,
with the gap itself written at the right edge and the two sexes identified
once in the header rather than in a legend.
"""
from pathlib import Path

from _shared import fetch_json, save, base_layout, titles, INK, MUTED, FAINT, GRID, VIOLET, TEAL

import numpy as np
import plotly.graph_objects as go

CHART_NUM = 21
YEAR = 2022
CODES = ["JPN", "KOR", "FRA", "ESP", "ITA", "DEU", "GBR", "USA", "CAN", "AUS", "CHN", "IND", "BRA", "MEX", "RUS", "UKR",
         "TUR", "IRN", "EGY", "NGA", "ZAF", "ETH", "IDN", "VNM", "BGD", "PAK"]


def wb(indicator):
    url = f"https://api.worldbank.org/v2/country/all/indicator/{indicator}?date={YEAR}&format=json&per_page=400"
    return {r["countryiso3code"]: (r["country"]["value"], r["value"]) for r in fetch_json(url)[1] if r["value"] is not None}


def generate():
    women, men = wb("SP.DYN.LE00.FE.IN"), wb("SP.DYN.LE00.MA.IN")
    rows = [(women[c][0].replace("Russian Federation", "Russia").replace("Korea, Rep.", "South Korea").replace("Iran, Islamic Rep.", "Iran")
             .replace("Egypt, Arab Rep.", "Egypt").replace("Viet Nam", "Vietnam").replace("Turkiye", "Turkey"), women[c][1], men[c][1])
            for c in CODES if c in women and c in men]
    rows.sort(key=lambda r: r[1] - r[2])
    names = [r[0] for r in rows]
    w = np.array([r[1] for r in rows]); m = np.array([r[2] for r in rows])

    fig = go.Figure()
    for i, (name, wv, mv) in enumerate(rows):
        fig.add_trace(go.Scatter(x=[mv, wv], y=[i, i], mode="lines", line=dict(color=GRID, width=3), hoverinfo="skip"))
    fig.add_trace(go.Scatter(x=m, y=list(range(len(rows))), mode="markers", name="Men", marker=dict(size=11, color=TEAL),
                             hovertemplate="%{text}: men %{x:.1f}<extra></extra>", text=names))
    fig.add_trace(go.Scatter(x=w, y=list(range(len(rows))), mode="markers", name="Women", marker=dict(size=11, color=VIOLET),
                             hovertemplate="%{text}: women %{x:.1f}<extra></extra>", text=names))
    for i, (name, wv, mv) in enumerate(rows):
        fig.add_annotation(x=1.0, y=i, xref="paper", text=f"+{wv - mv:.1f}", showarrow=False, xanchor="left", xshift=8,
                           font=dict(size=12, color=VIOLET if wv - mv >= 8 else MUTED, family="Inter"))
    fig.add_annotation(x=1.0, y=len(rows) - 0.4, xref="paper", text="<b>Gap</b>", showarrow=False, xanchor="left", xshift=8, font=dict(size=12, color=INK))
    fig.add_annotation(x=m[-1], y=len(rows) - 1, text="<b>Men</b>", showarrow=False, xanchor="right", xshift=-12, font=dict(size=13, color=TEAL))
    fig.add_annotation(x=w[-1], y=len(rows) - 1, text="<b>Women</b>", showarrow=False, xanchor="left", xshift=12, font=dict(size=13, color=VIOLET))
    fig.update_layout(**base_layout(margin=dict(l=110, r=80, t=110, b=70)))
    fig.update_xaxes(range=[52, 90], dtick=5, ticksuffix=" yrs", showgrid=True)
    fig.update_yaxes(tickvals=list(range(len(rows))), ticktext=names, showgrid=False, tickfont=dict(size=12, color=INK), range=[-0.8, len(rows) - 0.2])
    g0, g1 = w[0] - m[0], w[-1] - m[-1]
    titles(fig, f"Women outlive men everywhere, by {'under a year' if g0 < 1 else f'{g0:.0f} years'} in {names[0]} and {g1:.0f} in {names[-1]}",
           f"Life expectancy at birth by sex, {YEAR}, sorted by the gap between women and men.",
           "Source: World Bank World Development Indicators (SP.DYN.LE00.FE.IN, SP.DYN.LE00.MA.IN), from UN World Population Prospects.")
    save(CHART_NUM, fig, width=1280, height=900)

Made with Plotly