Annual global emissions and absolute annual rates of change (Table 2.4)¶
Notebook sr15_2.3.3_global_emissions_statistics¶
This notebook is based on the Release 1.1 of the IAMC 1.5C Scenario Explorer and Data and refers to the published version of the IPCC Special Report on Global Warming of 1.5C (SR15).
The notebook is run with pyam release 0.5.0.
The source code of this notebook is available on GitHub (release 2.0.2).
IPCC SR15 scenario assessment¶
Analysis of global CO2 and Kyoto emissions, (BE)CCS
and year of net-zero¶
This notebook computes indicators and diagnostics of emissions pathways, the use of carbon capture and sequestration, and the timing of net-zero of different emissions categories in the IPCC's "Special Report on Global Warming of 1.5°C". The notebook generates the data for Table 2.4 in the Special Report.
The scenario data used in this analysis can be accessed and downloaded at https://data.ene.iiasa.ac.at/iamc-1.5c-explorer.
Load pyam
package and other dependencies¶
import pandas as pd
import numpy as np
import io
import itertools
import yaml
import math
import pyam
Import scenario data, categorization and specifications files¶
The metadata file with scenario categorisation and quantitative indicators can be downloaded at https://data.ene.iiasa.ac.at/iamc-1.5c-explorer.
Alternatively, it can be re-created using the notebook sr15_2.0_categories_indicators
.
The last cell of this section loads and assigns a number of auxiliary lists as defined in the categorization notebook.
sr1p5 = pyam.IamDataFrame(data='../data/iamc15_scenario_data_world_r2.0.xlsx')
sr1p5.load_meta('sr15_metadata_indicators.xlsx')
with open("sr15_specs.yaml", 'r') as stream:
specs = yaml.load(stream, Loader=yaml.FullLoader)
rc = pyam.run_control()
for item in specs.pop('run_control').items():
rc.update({item[0]: item[1]})
cats = specs.pop('cats')
cats_15_no_lo = specs.pop('cats_15_no_lo')
Downselect scenario ensemble to categories of interest for this assessment¶
To reduce potential bias by many scenarios from the same modelling framework, 13 scenarios submitted by the 'AIM' model are excluded from the assessment underpinning this statement (cf. SPM Statement C1).
Also, note that we apply the filter by relevant years after computing the year of netzero.
cats.remove('Above 2C')
sr1p5.meta.rename(columns={'Kyoto-GHG|2010 (SAR)': 'kyoto_ghg_2010'}, inplace=True)
filter_args_aim = dict(model='AIM*',
scenario=['SFCM*_1p5Degree', 'EMF33_Med2C_nofuel', 'EMF33_Med2C_none'],
keep=False)
df = (
sr1p5
.filter(kyoto_ghg_2010='in range', category=cats)
.filter(**filter_args_aim)
)
Initialize a pyam.Statistics
instance¶
stats = pyam.Statistics(df=df,
filters=[
('below 1.5', {'category': 'Below 1.5C'}),
('lo os 1.5', {'category': '1.5C low overshoot'}),
('no & lo os 1.5', {'category': cats_15_no_lo}),
('hi os 1.5', {'category': ['1.5C high overshoot']}),
('lower 2.0', {'category': ['Lower 2C']}),
('higher 2.0', {'category': ['Higher 2C']})]
, rows=True)
years = [2030, 2050, 2100]
compare_years = [(2010, 2030), (2020, 2030), (2030, 2050)]
Function to compute the year of netzero and add growth statistics to the summary¶
def year_of_net_zero(data, years, threshold):
prev_val = 0
prev_yr = np.nan
for yr, val in zip(years, data):
if np.isnan(val):
continue
if val < threshold:
x = (val - prev_val) / (yr - prev_yr) # absolute change per year
return prev_yr + int((threshold - prev_val) / x) + 1 # add one because int() rounds down
prev_val = val
prev_yr = yr
return np.inf
header='Annual emissions/sequestration (GtCO2)'
header_change='Absolute annual change (GtCO2)'
header_zero='Timing of global zero'
statistics_settings = dict(
header=header,
header_change=header_change,
header_zero= header_zero,
years=years,
compare_years=[(2010, 2030), (2020, 2030), (2030, 2050)],
)
def add_statistics(data, row, years, compare_years,
header, header_change, header_zero, add_netzero=False):
stats.add(data[years], header=header, row=row)
for i, j in compare_years:
abs_ann_change = (data[j] - data[i]) / (j - i)
stats.add(abs_ann_change, header=header_change, row=row,
subheader='{}-{}'.format(i,j))
if add_netzero:
netzero = data.apply(year_of_net_zero, years=data.columns, threshold=0, axis=1)
stats.add(netzero, header=header_zero, row=row, subheader='year')
Get timeseries of total CO2 emissions¶
co2 = (
df.filter(variable='Emissions|CO2')
.convert_unit('Mt CO2/yr', 'Gt CO2/yr')
.timeseries()
)
add_statistics(co2, 'Total CO2 (net)', **statistics_settings, add_netzero=True)
co2_gross_seq_variables = [
'Carbon Sequestration|CCS|Biomass',
'Carbon Sequestration|Land Use',
'Carbon Sequestration|Direct Air Capture',
'Carbon Sequestration|Enhanced Weathering'
]
agg_sequestration = (
df.filter(variable=co2_gross_seq_variables)
.convert_unit('Mt CO2/yr', 'Gt CO2/yr')
.timeseries()
)
agg_sequestration = agg_sequestration.groupby(pyam.META_IDX).sum()
co2_ene_ind = (
df.filter(variable='Emissions|CO2|Energy and Industrial Processes')
.convert_unit('Mt CO2/yr', 'Gt CO2/yr')
.timeseries()
)
co2_ene_ind.index = co2_ene_ind.index.droplevel([2, 3, 4])
co2_ene_ind_gross = (co2_ene_ind + agg_sequestration).combine_first(co2_ene_ind)
add_statistics(co2_ene_ind_gross, 'CO2 from fossil fuels and industry (gross)', **statistics_settings)
add_statistics(co2_ene_ind, 'CO2 from fossil fuels and industry (net)', **statistics_settings)
co2_afolu = (
df.filter(variable='Emissions|CO2|AFOLU')
.convert_unit('Mt CO2/yr', 'Gt CO2/yr')
.timeseries()
)
add_statistics(co2_afolu, 'CO2 from AFOLU', **statistics_settings)
CCS from bioenergy¶
ccs_bio = (
df.filter(variable='Carbon Sequestration|CCS|Biomass')
.convert_unit('Mt CO2/yr', 'Gt CO2/yr')
.timeseries()
)
add_statistics(ccs_bio, 'Bioenergy combined with carbon capture and storage (BECCS)',**statistics_settings)
Total greenhouse gases according to the Kyoto protocol¶
ghg = (
df.filter(variable='Emissions|Kyoto Gases (AR4-GWP100)')
.rename(unit={'Mt CO2-equiv/yr': 'Mt CO2e/yr'})
.convert_unit('Mt CO2e/yr','Gt CO2e/yr')
.timeseries()
)
add_statistics(ghg, 'Kyoto GHG (AR4, GtCO2e)', **statistics_settings, add_netzero=True)
Display and export summary statistics to xlsx
¶
Note that in Table 2.4 as printed in the Special Report, the full range is shown for any cells with less than 7 scenarios, and interquartile ranges are shown otherwise.
This formatting was implemented manually ex-post, as it is currently not supported by the pyam.Statistics
module.
summary = stats.summarize(center='median', interquartile=True, custom_format='{:.1f}')
index = summary.index.get_level_values(1).unique()
summary = (
summary
.swaplevel(0, 1, axis=0)
.reindex(index=index, level=0)
)
summary
summary.to_excel('output/table_2.4_emission_statistics.xlsx')