MakeoverMonday: Global Oil Production - Who Still Moves the Map

MakeoverMonday
Python
Data Viz
Country-level oil output in terawatt-hours from Our World in Data: top producers, trajectories since 1990, and shifting shares of the world total
Author

chokotto

Published

April 27, 2026

Overview

Oil output remains a core lens on geopolitics and the energy transition. This makeover uses Our World in Data’s country-level series (Energy Institute and Shift Data Portal, harmonised by OWID) in terawatt-hours (energy-equivalent) to compare how the largest producers evolved since 1990.

Dataset

Code
import sys
from pathlib import Path

import pandas as pd
import numpy as np
import plotly.graph_objects as go

_p = Path.cwd()
if (_p / "_mm_layout.py").exists():
    _posts = _p
elif (_p.parent / "_mm_layout.py").exists():
    _posts = _p.parent
elif (_p / "posts" / "_mm_layout.py").exists():
    _posts = _p / "posts"
else:
    _posts = _p
sys.path.insert(0, str(_posts))
from _mm_layout import apply_mm_layout
Code
url = (
    "https://ourworldindata.org/grapher/oil-production-by-country.csv"
    "?v=1&csvType=full&useColumnShortNames=false"
)
raw = pd.read_csv(url, storage_options={"User-Agent": "chokotto-makeover/1.0"})
raw.columns = raw.columns.str.replace(" ", "_")

oil = raw.rename(columns={"Oil": "oil_twh"}).copy()
oil = oil[oil["Year"] >= 1990]
oil = oil[oil["Entity"].notna()]
# Drop aggregates that would double-count
exclude = {"World", "Africa", "Asia", "Europe", "North America", "South America", "Oceania"}
oil = oil[~oil["Entity"].isin(exclude)]

latest_y = int(oil["Year"].max())
top_entities = (
    oil[oil["Year"] == latest_y]
    .nlargest(8, "oil_twh")["Entity"]
    .tolist()
)

by_year_total = oil.groupby("Year", as_index=False)["oil_twh"].sum()
by_year_total = by_year_total.rename(columns={"oil_twh": "world_total"})

top8 = oil[oil["Entity"].isin(top_entities)].merge(by_year_total, on="Year")
top8["share_pct"] = np.where(
    top8["world_total"] > 0, top8["oil_twh"] / top8["world_total"] * 100, 0
)

My Makeover

The same eight producers still dominate volume, but ranks drift

Code
color_map = {e: PAL[i % len(PAL)] for i, e in enumerate(top_entities)}

fig = go.Figure()
for entity in top_entities:
    sub = oil[oil["Entity"] == entity].sort_values("Year")
    fig.add_trace(go.Scatter(
        x=sub["Year"],
        y=sub["oil_twh"],
        mode="lines",
        name=entity,
        line=dict(color=color_map[entity], width=3 if entity == top_entities[0] else 2),
        hovertemplate=f"{entity}<br>%{{x}}: %{{y:,.0f}} TWh<extra></extra>",
    ))

lead = top_entities[0]
lead_last = oil[(oil["Entity"] == lead) & (oil["Year"] == latest_y)]["oil_twh"].sum()

fig.update_layout(
    **THEME,
    xaxis=dict(title=""),
    yaxis=dict(title="Oil production (terawatt-hours)"),
)
apply_mm_layout(
    fig,
    f"Oil output (TWh): {lead} led with {lead_last:,.0f} TWh in {latest_y} among top 8",
    subtitle="Top 8 countries by output in the latest year; lines from 1990",
    legend_position="top",
    n_legend_items=len(top_entities),
)
add_source(fig)
assert_no_title_overlap(fig)
fig.show()

try:
    fig.write_image("chart-1.png", width=1200, height=520, scale=2)
except Exception:
    pass

Share of world production: concentration vs diversification

Code
years = sorted(top8["Year"].unique())
wide = top8.pivot(index="Year", columns="Entity", values="share_pct").reindex(years)
wide = wide.fillna(0)

fills = [
    "rgba(180,83,9,0.35)", "rgba(15,118,110,0.32)", "rgba(29,78,216,0.28)",
    "rgba(124,58,237,0.26)", "rgba(190,18,60,0.24)", "rgba(100,116,139,0.22)",
    "rgba(202,138,4,0.2)", "rgba(3,105,161,0.2)",
]
fig2 = go.Figure()
for i, entity in enumerate(top_entities):
    if entity not in wide.columns:
        continue
    fig2.add_trace(go.Scatter(
        x=wide.index,
        y=wide[entity],
        name=entity,
        stackgroup="one",
        mode="lines",
        line=dict(width=0.5),
        fillcolor=fills[i % len(fills)],
        hovertemplate=f"{entity}: %{{y:.1f}}%<extra></extra>",
    ))

fig2.update_layout(
    **THEME,
    xaxis=dict(title=""),
    yaxis=dict(title="Percent of summed country totals", range=[0, 100]),
)
apply_mm_layout(
    fig2,
    "Share of estimated world oil output: eight largest producers (100% stacked)",
    subtitle=(
        "World total = sum of country-level series in dataset "
        "(excludes regional aggregates)"
    ),
    legend_position="top",
    n_legend_items=len(top_entities),
)
add_source(fig2)
assert_no_title_overlap(fig2)
fig2.show()

This post is part of Makeover Monday.