4683cd860a3a4b2a80e1d78d24556c0d

2.1. Figure 1 - Emissions and temperature characteristics

79ad494ae2d54397af8768f2c9f31e1c

Licensed under the MIT License.

This notebook is part of a repository to generate figures and analysis for the manuscript

Keywan Riahi, Christoph Bertram, Daniel Huppmann, et al.  Cost and attainability of meeting stringent climate targets without overshoot Nature Climate Change, 2021 doi: 10.1038/s41558-021-01215-2

The scenario data used in this analysis should be cited as

ENGAGE Global Scenarios (Version 2.0) doi: 10.5281/zenodo.5553976

The data can be accessed and downloaded via the ENGAGE Scenario Explorer at https://data.ece.iiasa.ac.at/engage. Please refer to thelicenseof the scenario ensemble before redistributing this data or adapted material.

The source code of this notebook is available on GitHub at https://github.com/iiasa/ENGAGE-netzero-analysis. A rendered version can be seen at https://data.ece.iiasa.ac.at/engage-netzero-analysis.

[1]:
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import pyam

Import the scenario snapshot used for this analysis and the plotting configuration

[2]:
data_folder = Path("../data/")

output_folder = Path("output")
output_format = "png"
plot_args = dict(facecolor="white", dpi=300)
[3]:
rc = pyam.run_control()
rc.update("plotting_config.yaml")
[4]:
df = (
    pyam.IamDataFrame(data_folder / "ENGAGE_fig1.xlsx")
    .filter(year=range(2010, 2101, 5))
    .convert_unit("Mt CO2-equiv/yr", "Gt CO2e/yr")
    .convert_unit("Mt CO2/yr", "Gt CO2/yr")
)
pyam - INFO: Running in a notebook, setting up a basic logging at level INFO
pyam.core - INFO: Reading file ../data/ENGAGE_fig1.xlsx
pyam.core - INFO: Reading meta indicators

Emissions reductions in NDC vs. cost-effective emissions pathways

The following cells compute the statistics of GHG emissions across pathways used in the section “Implications for emissions pathways” of the manuscript.

[5]:
df_ghg = df.filter(variable="Emissions|Kyoto Gases")
[6]:
stats = pyam.Statistics(
    df=df_ghg,
    filters=[
        ("NDC", {"scenario_family": "INDCi", "budget_type": "reference"}),
        ("2°C", {"scenario_family": "NPi", "category": "2C"}),
        ("1.5°C", {"scenario_family": "NPi", "category": "1.5C (with low overshoot)"}),
    ]
)
[7]:
stats.add(df_ghg.filter(year=[2020, 2030, 2050, 2100]).timeseries(), "GHG emissions")
[8]:
stats.summarize()
[8]:
count GHG emissions
mean (max, min) 2020 2030 2050 2100
NDC 8 54.73 (57.27, 50.66) 53.02 (56.34, 46.84) 50.45 (58.12, 42.81) 48.55 (65.51, 23.43)
2°C 77 54.80 (57.12, 51.20) 36.32 (48.58, 24.95) 16.70 (27.19, 6.66) 0.40 (8.23, -14.56)
1.5°C 17 53.60 (57.12, 51.20) 25.65 (35.32, 19.35) 9.32 (15.26, 4.55) 0.75 (8.20, -10.34)

Panel a - GHG emissions developments in stringent mitigation scenarios

GHG emissions in NDC scenarios (grey) compared to stringent mitigation scenarios that reach peak temperatures below 2°C with limited overshoot (net-zero budget scenarios, blue) and mitigation scenarios with the same long-term carbon budget with temperature overshoot (end-of-century budget scenarios, red).

[9]:
fig, ax = plt.subplots(figsize=(4, 5))

ref_df = df.filter(
    variable='Emissions|Kyoto Gases',
    budget_type='reference', scenario_family='INDCi'
)
ref_df.plot(ax=ax, color='category', fill_between=True)

npi_df = df.filter(
    scenario_family='NPi',
    category_peak=['1.5C (with low overshoot)', '2C'],
    variable='Emissions|Kyoto Gases'
)
npi_df.plot(ax=ax, color='budget_type', fill_between=True)

plt.hlines(y=0, xmin=2010, xmax=2100, color="black", linewidths=0.5)

pyam.plotting.set_panel_label(f"n={len(ref_df.index) + len(npi_df.index)}", ax=ax, x=0.7, y=0.95)
ax.set_title(None)
ax.set_xlabel(None)
ax.legend([mpl.lines.Line2D([0, 1], [0, 1], color=c) for c in ['grey', 'blue', 'red']],
          ['NDC', 'Net-zero budget', 'End-of-century budget'], loc=3)

plt.tight_layout()
fig.savefig(output_folder / f"fig1a_ghg.{output_format}", **plot_args)
../_images/notebooks_2.1_ENGAGE_Figure_1_emissions_temperature_12_0.png

Panel b - Residual non-CO2 emissions

Residual non-CO2 emissions after the point of reaching net-zero CO2 emissions for specified temperature stabilization levels. The box shows the quartiles of the dataset while the whiskers extend to show the rest of the distribution.

[10]:
df_nonco2 = (
    df
    .filter(budget_type="reference", keep=False)
    .filter(scenario_family="NPi")
    .subtract("Emissions|Kyoto Gases", "Emissions|CO2", "Emissions|Non-CO2", ignore_units="Gt CO2e/yr")
)
[11]:
def get_from_meta_column(df, x, col):
    val = df.meta.loc[x.name[0:2], col]
    return val if val < np.inf else max(x.index)
[12]:
df_nonco2.set_meta(
    df_nonco2.timeseries().apply(
        lambda x: pyam.fill_series(x, get_from_meta_column(df, x, "netzero|CO2")),
        raw=False, axis=1),
    "non-CO2 in year of CO2 net-zero"
)
[13]:
def get_average(x, y):
    # downselect to years after netzero `y`
    _x = x[[i > y for i in x.index]]
    # concatenate value in year of netyero `y` with series after `y`, and compute average
    return pd.concat([pd.Series(pyam.fill_series(x, y), index=[y]), _x]).mean()
[14]:
df_nonco2.set_meta(
    df_nonco2.timeseries().apply(
        lambda x: get_average(x, get_from_meta_column(df, x, "netzero|CO2")),
        raw=False, axis=1),
    "average non-CO2 after year of CO2 net-zero"
)
[15]:
fig, ax = plt.subplots(figsize=(4, 3))

df_nonco2.plot.box(
    ax=ax,
    x="average non-CO2 after year of CO2 net-zero",
    y="category",
    order=[">2.5C", "2.5C", "2C", "1.5C (with low overshoot)"],
    palette=rc["color"]["category"],
    legend=False,
)

ax.set_title(None)
ax.set_xlabel("Mean non-CO2 emissions (Gt CO2e/yr)")
ax.set_xlim(2.1, 12.5)
ax.set_ylabel(None)
ax.set_yticklabels([">2.5C", "2.5C", "2C", "1.5C (with\nlow overshoot)"])
pyam.plotting.set_panel_label(f"n={len(df_nonco2.index)}", ax=ax, x=0.8, y=0.05)

plt.tight_layout()
fig.savefig(output_folder / f"fig1b_non_co2.{output_format}", **plot_args)
../_images/notebooks_2.1_ENGAGE_Figure_1_emissions_temperature_19_0.png

Panel c - Cumulative net-negative CO2 emissions

Relationship between cumulative net-negative CO2 emissions (NNCE) and resulting temperature drawdown after peak temperature (that is, overshoot); net-zero scenarios (red) and end-of-century scenarios (blue).

[16]:
df_co2 = df.filter(variable="Emissions|CO2")

Derive net-negative CO2 emissions and set as quantitative meta indicator

[17]:
co2_netneg = df_co2.timeseries().applymap(lambda x: - min(x, 0))

nn_label = "Cumulative net-negative CO2 emissions (GtCO2)"
df_co2.set_meta(
    meta=co2_netneg.apply(pyam.cumulative, axis=1, first_year=2020, last_year=2100),
    name=nn_label
)

Remove all scenarios that do not report GHG explicitly for comparibility to panel b

[18]:
df_ghg_nonref = df_ghg.filter(budget_type='reference', keep=False)

df_co2.set_meta(meta=True, name="has_ghg", index=df_ghg_nonref.index)
df_co2.filter(has_ghg=True, inplace=True)

Plot the data!

[19]:
overshoot_label = 'Temperature overshoot (°C)'
df_co2.meta[overshoot_label] = df_co2.meta['median warming peak-and-decline']

_df_co2 = df_co2.filter().filter(scenario_family="NPi")

fig, ax = plt.subplots(figsize=(4, 3))
_df_co2.plot.scatter(ax=ax, x=nn_label, y=overshoot_label, color='budget_type', legend=False)

ax.set_title(None)
pyam.plotting.set_panel_label(f"n={len(_df_co2.index)}", ax=ax, x=0.8, y=0.05)

plt.tight_layout()
fig.savefig(output_folder / f'fig1c_overshoot_netnegative_co2.{output_format}', **plot_args)
../_images/notebooks_2.1_ENGAGE_Figure_1_emissions_temperature_27_0.png

Panel d - Relationship between the budget and time of net-zero

Timing of when net-zero CO2 emissions are reached. Net-zero budget scenarios consistent with 1.5 °C (low overshoot) and 2 °C respectively (blue bars) are compared to scenarios with the same end-of-century carbon budget with net-negative emissions (red bars). The height of the bars indicates the number of scenarios that reach net zero at the specific year.

[20]:
netzero_bins = list(range(2050, 2101, 5)) + [">2100"]

def assign_nz_bin(x):
    for b in netzero_bins:
        try:
            if x < b:
                return b
        # this approach works as long as only the last item is a string
        except TypeError:
            return b
[21]:
cats_2c = ["1.5C (with low overshoot)", "2C"]
[22]:
x = df.filter(category_peak=cats_2c, scenario_family="NPi").meta
[23]:
x["netzero"] = x["netzero|CO2"].apply(assign_nz_bin)

The pyam package does not support histogram-type plots, so panel d is implemented directly in seaborn.

[24]:
fig, ax = plt.subplots(2, 1, figsize=(4, 3), sharex=True, sharey=True)

for i, label in enumerate(cats_2c):
    _x = x[x["category_peak"] == label]
    sns.countplot(
        ax=ax[i],
        data=_x,
        x="netzero",
        hue="budget_type",
        order=netzero_bins,
        palette=dict(peak_budget="blue", full_century_budget="red"),
    )
    ax[i].set_xlabel(None)

    ax[i].set_xticklabels([2050, "", 2060, "", 2070, "", 2080, "", 2090, "", 2100, ""])
    ax[i].set_ylabel(label)
    ax[i].get_legend().remove()

pyam.plotting.set_panel_label(f"n={len(x['netzero'])}", ax=ax[0], x=0.8, y=0.8)

ax[0].set_ylabel("1.5C (with\nlow overshoot)")
ax[1].set_xlabel("Year when net zero CO2 emissions are reached")

plt.tight_layout()
fig.savefig(output_folder / f"fig1d_netzero_year_hist_by_category.{output_format}", **plot_args)
../_images/notebooks_2.1_ENGAGE_Figure_1_emissions_temperature_34_0.png
[ ]: