Last active
September 8, 2025 08:10
-
-
Save cavedave/a11fa410a471b4fb50b656e76e3edbe0 to your computer and use it in GitHub Desktop.
staircase.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "nbformat": 4, | |
| "nbformat_minor": 0, | |
| "metadata": { | |
| "colab": { | |
| "provenance": [], | |
| "authorship_tag": "ABX9TyNv3w0Sagj1PRZ2d9ZxKnZa", | |
| "include_colab_link": true | |
| }, | |
| "kernelspec": { | |
| "name": "python3", | |
| "display_name": "Python 3" | |
| }, | |
| "language_info": { | |
| "name": "python" | |
| } | |
| }, | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "view-in-github", | |
| "colab_type": "text" | |
| }, | |
| "source": [ | |
| "<a href=\"https://colab.research.google.com/gist/cavedave/a11fa410a471b4fb50b656e76e3edbe0/staircase.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "lets make a staircase of denial gif and share the code\n", | |
| "\n", | |
| "the idea is that between every el nino hot year people go 'look it hasnt gotten warmer in X years global warming is disproven. Checkmate now, king me'\n", | |
| "\n", | |
| "And i want to make a way to easily show that warming continues inside the el nino cyscle and new record year is coming." | |
| ], | |
| "metadata": { | |
| "id": "QpovhOROUzWI" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "## staircase" | |
| ], | |
| "metadata": { | |
| "id": "a1l5rz8vKgQW" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "I heard about the escalator of denial here and wanted to update it and make the code public https://skepticalscience.com/graphics.php?g=465\n", | |
| "\n", | |
| "and this tweet also shows it https://x.com/RARohde/status/1614991656583041024\n", | |
| "\n" | |
| ], | |
| "metadata": { | |
| "id": "15WdK15XKh4T" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "hadcrut data https://www.metoffice.gov.uk/hadobs/hadcrut5/data/HadCRUT.5.0.2.0/download.html" | |
| ], | |
| "metadata": { | |
| "id": "QG8tIYGLKnfG" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "metadata": { | |
| "id": "d69c314c" | |
| }, | |
| "source": [ | |
| "import pandas as pd\n", | |
| "\n", | |
| "url = \"https://www.metoffice.gov.uk/hadobs/hadcrut5/data/HadCRUT.5.0.2.0/analysis/diagnostics/HadCRUT.5.0.2.0.analysis.summary_series.global.annual.csv\"\n", | |
| "df = pd.read_csv(url)\n", | |
| "display(df.head())" | |
| ], | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "import numpy as np\n", | |
| "import matplotlib.pyplot as plt\n", | |
| "import imageio\n", | |
| "from scipy.signal import argrelextrema\n", | |
| "\n", | |
| "\n", | |
| "# rename columns\n", | |
| "df = df.rename(columns={'Time':'Year', 'Anomaly (deg C)':'Anomaly'})\n", | |
| "df.set_index('Year', inplace=True)\n", | |
| "ts = df['Anomaly']" | |
| ], | |
| "metadata": { | |
| "id": "017GpJsNLoBY" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Make the years" | |
| ], | |
| "metadata": { | |
| "id": "srGGdU8cwj1Y" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "metadata": { | |
| "id": "d69c314c" | |
| }, | |
| "source": [ | |
| "# 1) find every record‐breaking year ≥ 1980", | |
| "post80 = ts.where(ts.index >= 1980)", | |
| "current_max = -np.inf", | |
| "record_years = []", | |
| "record_values = []", | |
| "for yr, val in post80.items():", | |
| "if np.isnan(val):", | |
| "continue", | |
| "if val >= current_max:", | |
| "current_max = val", | |
| "record_years.append(yr)", | |
| "record_values.append(val)", | |
| "# 2) build ALL steps", | |
| "all_steps = [(s, e, y) for s, e, y in zip(record_years, record_years[1:], record_values)]", | |
| "# 3) filter to only multi‑year “flat” periods (>=3 yr)", | |
| "steps = [(s, e, y) for (s, e, y) in all_steps if (e - s) >= 3]", | |
| "# 4) pre‑compute limits & frame list", | |
| "xmin, xmax = ts.index.min(), ts.index.max()", | |
| "ymin, ymax = ts.min() - 0.1, ts.max() + 0.1", | |
| ], | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "import numpy as np\n", | |
| "import matplotlib.pyplot as plt\n", | |
| "import imageio\n", | |
| "\n", | |
| "# …after loading df and computing ts, steps, xmin,xmax,ymin,ymax…\n", | |
| "\n", | |
| "frame_files = []\n", | |
| "\n", | |
| "for year in ts.index:\n", | |
| " if year < 1980:\n", | |
| " continue\n", | |
| " fig, ax = plt.subplots(figsize=(8,5))\n", | |
| " ax.set_position([0.12, 0.15, 0.80, 0.75])\n", | |
| "\n", | |
| " # 1) Zero line\n", | |
| " ax.axhline(0, color='black', linestyle='--', linewidth=1, alpha=0.1)\n", | |
| "\n", | |
| " # 2) full series\n", | |
| " ax.plot(ts.index, ts.values, color='lightgray', lw=1)\n", | |
| "\n", | |
| " # 3) staircase steps\n", | |
| " for s, e, y in steps:\n", | |
| " if e <= year:\n", | |
| " ax.hlines(y, s, e, lw=3, color='C0')\n", | |
| " elif s <= year < e:\n", | |
| " ax.hlines(y, s, year, lw=4, color='tomato')\n", | |
| " ax.text(\n", | |
| " year, y + 0.02,\n", | |
| " f\"No warming in {year - s} yr\",\n", | |
| " ha='right', va='bottom',\n", | |
| " color='tomato', fontsize=11, fontweight='bold'\n", | |
| " )\n", | |
| "\n", | |
| " # 4) lock axes\n", | |
| " ax.set_xlim(xmin, xmax)\n", | |
| " ax.set_ylim(ymin, ymax)\n", | |
| "\n", | |
| " # 5) titles & labels\n", | |
| " ax.set_title(\n", | |
| " \"Staircase of Denial — “No warming in years”\\n\",\n", | |
| " loc='center', fontsize=16, fontweight='bold',\n", | |
| " y=0.94, pad=5\n", | |
| " )\n", | |
| " ax.set_xlabel('Year', fontsize=12)\n", | |
| " ax.set_ylabel('Temperature anomaly (°C)', fontsize=12)\n", | |
| "\n", | |
| " # 6) source & notes\n", | |
| " ax.text(0.99, -0.1,\n", | |
| " \"Source: HadCRUT5 by @iamreddave\",\n", | |
| " ha='right', va='top',\n", | |
| " transform=ax.transAxes,\n", | |
| " fontsize=9, color='gray')\n", | |
| " ax.text(0.20, -0.1,\n", | |
| " \"Anomalies relative to 1961-1990\",\n", | |
| " ha='right', va='top',\n", | |
| " transform=ax.transAxes,\n", | |
| " fontsize=9, color='gray')\n", | |
| "\n", | |
| " # 7) save\n", | |
| " fname = f'frame_{year}.png'\n", | |
| " fig.savefig(fname, dpi=120)\n", | |
| " plt.close(fig)\n", | |
| " frame_files.append(fname)" | |
| ], | |
| "metadata": { | |
| "id": "l76R5v5GujNI" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "output_path = '/content/staircase_hadcrut5.gif'\n", | |
| "with imageio.get_writer(\n", | |
| " '/content/staircase_hadcrut5.gif',\n", | |
| " mode='I',\n", | |
| " fps=7\n", | |
| ") as writer:\n", | |
| " for fn in frame_files:\n", | |
| " writer.append_data(imageio.imread(fn))" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "Jaq21m9jungW", | |
| "outputId": "45d77e7d-be05-4be7-d5db-58f0f846418b" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stderr", | |
| "text": [ | |
| "/tmp/ipython-input-53-3463716466.py:8: DeprecationWarning: Starting with ImageIO v3 the behavior of this function will switch to that of iio.v3.imread. To keep the current behavior (and make this warning disappear) use `import imageio.v2 as imageio` or call `imageio.v2.imread` directly.\n", | |
| " writer.append_data(imageio.imread(fn))\n" | |
| ] | |
| } | |
| ] | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

