Global price of carbon emissions and assessment of comparative carbon prices (Figure 2.26)

Notebook sr15_2.5_carbon_price_analysis

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).

sr15_2.5_carbon_price_analysis

IPCC SR15 scenario assessment

Assessment of carbon prices by warming category

This notebook contains the carbon price assessment of the scenario ensemble
in Section 2.5.2 and for Figure 2.26 of the IPCC's "Special Report on Global Warming of 1.5°C".

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

In [1]:
import pandas as pd
import numpy as np
import io
import yaml
import math
import matplotlib.pyplot as plt
plt.style.use('style_sr15.mplstyle')
%matplotlib inline

import pyam
from utils import boxplot_by_cat
pyam - INFO: Running in a notebook, setting `pyam` logging level to `logging.INFO` and adding stderr handler

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.

In [2]:
sr1p5 = pyam.IamDataFrame(data='../data/iamc15_scenario_data_world_r2.0.xlsx')
pyam.utils - INFO: Reading `../data/iamc15_scenario_data_world_r2.0.xlsx`
In [3]:
sr1p5.load_meta('sr15_metadata_indicators.xlsx')
pyam.core - INFO: Importing metadata for 416 scenarios (for total of 416)
In [4]:
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')
marker= specs.pop('marker')

Downselect scenario ensemble to categories of interest for this assessment

Only scenarios that limit warming to at most 2°C at the end of the century are included in this assessment.

In [5]:
cats.remove('Above 2C')

Set specifications for figures and statistics

First, set the list of years included in the plots. Then, define an auxiliary dictionary and function for easier display.

In [6]:
years = [2030, 2050, 2070, 2100]
In [7]:
filter_args = dict(df=sr1p5, category=cats, marker=None, join_meta=True)
In [8]:
def plotting_args():
    return {'categories': cats, 'column': 'category',
            'add_marker': marker, 'ar5_format': True}

Rename aggregate indicator from metadata

This simplifies including the aggregate indicator 'Annual compounded Net Present Value' in the plots and assessment below.

In [9]:
sr1p5.meta.rename(columns={'carbon price|AC NPV (2030-2100)': 'ac_npv'}, inplace=True)

Check for misreported carbon prices and exclude these from analysis and figues

In some cases, models were not able to report carbon prices throughout the century due to the scenario protocol and policy implementation. Carbon prices below 5 USD/tCO2 in 2030 or reported as nan were, after consultation with the modeling teams, assessed to be misreported entries. For consistency, these scenarios are excluded throughout this notebook.

In [10]:
df = sr1p5.filter(category=cats, variable='Price|Carbon', year=range(2030, 2101, 10))
In [11]:
misc = df.validate({'Price|Carbon': {'lo': 5, 'year': 2030}},
                   exclude_on_fail=True).set_index(pyam.META_IDX)
pyam.core - INFO: 9 of 1725 data points do not satisfy the criteria
pyam.core - INFO: 9 non-valid scenarios will be excluded

Check for nan entries in the carbon price timeseries and remove scenarios.

In [12]:
carbon_price = df.timeseries()
carbon_price.index = carbon_price.index.droplevel([2, 3, 4])
In [13]:
has_na = carbon_price.apply(lambda x: np.isnan(max(x)), axis=1)
na = carbon_price[has_na]
In [14]:
df.set_meta(meta=True, name='exclude', index=na.index)

Show all carbon-price trajectories excluded from the analysis and export to xlsx for archiving

In [15]:
all_excluded = carbon_price.loc[misc.index].append(na)
all_excluded
Out[15]:
2030 2040 2050 2060 2070 2080 2090 2100
model scenario
GCAM 4.2 SSP2-26 0.0 109.134954 177.769116 289.567332 471.674340 768.309480 1251.493020 2038.548420
SSP5-26 0.0 132.742062 216.222678 352.203876 573.704640 934.501500 1522.207620 2479.512240
IMAGE 3.0.1 ADVANCE_2020_1.5C-2100 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
ADVANCE_2020_Med2C 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
ADVANCE_2020_WB2C 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
ADVANCE_2030_Med2C 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
ADVANCE_2030_WB2C 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
SSP2-26 0.0 210.312818 409.131885 802.318800 761.865601 815.986817 816.114625 1017.040211
IMAGE 3.0.2 EMF33_tax_hi_full 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
WITCH-GLOBIOM 4.2 ADVANCE_2030_Med2C NaN 199.226508 273.332316 376.582161 524.264031 731.470600 1020.100250 1417.531582
ADVANCE_2030_Price1.5C NaN 641.420551 853.912667 1150.707355 1580.505945 2188.425124 3046.300489 4245.559906
ADVANCE_2030_WB2C NaN 576.080713 770.106400 1044.557946 1434.888489 1981.998906 2760.917823 3840.221318
In [16]:
sr1p5.set_meta(True, 'exclude', all_excluded.index)
In [17]:
df.filter(exclude=False, inplace=True)

Compute net-present value carbon price timeseries

Reload carbon-price timeseries after excluding non-valid timeseries data.

In [18]:
carbon_price = df.timeseries()
carbon_price.index = carbon_price.index.droplevel([2, 3, 4])
In [19]:
carbon_price_npv = carbon_price.copy()

r = 0.05
baseyear = 2020

for y in carbon_price_npv.columns: 
    carbon_price_npv[y] = carbon_price_npv[y] / math.pow(1 + r, y - baseyear)

Statistical assessment of the carbon price development

The following assessment is the basis of Section 2.5.2.1.

In [20]:
stats = pyam.Statistics(df=sr1p5, groupby={'category': cats})
In [21]:
stats.add(carbon_price, header='Carbon price')
In [22]:
summary = stats.summarize()
summary
Out[22]:
count Carbon price
mean (max, min) 2030 2040 2050 2060 2070 2080 2090 2100
category Below 1.5C 7 1472.17 (6050.00, 135.69) 2989.06 (12100.00, 182.35) 3977.87 (14300.00, 245.07) 4722.21 (16595.73, 329.35) 5612.80 (19260.03, 419.85) 6675.23 (22352.05, 510.35) 7948.20 (25940.46, 600.85) 9472.66 (30104.97, 691.35)
1.5C low overshoot 43 333.60 (1275.24, 56.87) 670.49 (2454.96, 92.63) 1026.42 (4303.97, 125.55) 1403.58 (7085.71, 168.73) 1896.64 (11124.41, 215.10) 2589.95 (16810.87, 261.46) 3566.98 (24605.34, 307.83) 4995.24 (35037.73, 262.70)
1.5C high overshoot 34 132.62 (672.86, 14.25) 365.59 (1699.58, 69.24) 603.46 (3725.30, 112.78) 907.90 (5680.24, 183.72) 1274.16 (7635.18, 263.25) 1734.32 (9590.12, 342.79) 2350.70 (11545.06, 422.33) 3214.41 (13500.00, 501.87)
Lower 2C 69 171.51 (1423.87, 14.25) 345.11 (2319.33, 46.20) 525.78 (3777.95, 75.25) 798.52 (6153.87, 93.02) 1205.96 (10024.01, 146.87) 1571.03 (16328.06, 202.91) 2085.92 (26596.69, 194.88) 2854.07 (43323.21, 153.72)
Higher 2C 51 61.94 (218.41, 14.25) 141.88 (1047.37, 27.64) 176.00 (1057.00, 45.02) 242.03 (1057.00, 73.34) 366.60 (1098.29, 119.46) 511.99 (1100.00, 153.93) 703.59 (1436.31, 149.04) 993.04 (2339.60, 173.85)
In [23]:
summary.to_excel('output/sec_2.5_carbon_price_summary_statistics.xlsx')

Descriptive statistics of the 'Higher 2C' pathways

In [24]:
stats.summarize().loc[('category', 'Higher 2C')]
Out[24]:
              mean (max, min)
count                                                  51
Carbon price  2030                  61.94 (218.41, 14.25)
              2040                141.88 (1047.37, 27.64)
              2050                176.00 (1057.00, 45.02)
              2060                242.03 (1057.00, 73.34)
              2070               366.60 (1098.29, 119.46)
              2080               511.99 (1100.00, 153.93)
              2090               703.59 (1436.31, 149.04)
              2100               993.04 (2339.60, 173.85)
Name: (category, Higher 2C), dtype: object

Descriptive statistics of the 'Below 1.5C' pathways

In [25]:
stats.summarize().loc[('category', 'Below 1.5C')]
Out[25]:
              mean (max, min)
count                                                     7
Carbon price  2030                1472.17 (6050.00, 135.69)
              2040               2989.06 (12100.00, 182.35)
              2050               3977.87 (14300.00, 245.07)
              2060               4722.21 (16595.73, 329.35)
              2070               5612.80 (19260.03, 419.85)
              2080               6675.23 (22352.05, 510.35)
              2090               7948.20 (25940.46, 600.85)
              2100               9472.66 (30104.97, 691.35)
Name: (category, Below 1.5C), dtype: object

Plot the average carbon price by category

This section produces panel b of Figure 2.26.

In [26]:
filtered_npv = pyam.filter_by_meta(carbon_price_npv, df, category=None, marker=None, ac_npv=None, join_meta=True)
In [27]:
price_threshold = 690
cols = ['ac_npv']
In [28]:
boxplot_by_cat(filtered_npv, **plotting_args(), years=cols,
               xlabel='Annual compounded net-present-value carbon price from 2030 until 2100',
               ylabel='Price of carbon (USD/tCO2)',
               ymax=price_threshold, ymin=0, save='output/fig2.26b_carbon_price_npv.png')
/Users/dh/anaconda3/lib/python3.7/site-packages/matplotlib/axes/_base.py:3604: MatplotlibDeprecationWarning: 
The `ymin` argument was deprecated in Matplotlib 3.0 and will be removed in 3.2. Use `bottom` instead.
  alternative='`bottom`', obj_type='argument')
/Users/dh/anaconda3/lib/python3.7/site-packages/matplotlib/axes/_base.py:3610: MatplotlibDeprecationWarning: 
The `ymax` argument was deprecated in Matplotlib 3.0 and will be removed in 3.2. Use `top` instead.
  alternative='`top`', obj_type='argument')

Summary of outliers in the figure

Display the timeseries data of scenarios exceeding the upper threshold of the figure (the number of data points exceeding the threshold per category is marked above the panel).

In [29]:
above_threshold = filtered_npv[cols].apply(lambda x: max(x) > price_threshold, axis=1)
filtered_npv[above_threshold]
Out[29]:
2030 2040 2050 2060 2070 2080 2090 2100 marker category ac_npv
model scenario
MESSAGE V.3 GEA_Mix_1p5C_AdvNCO2_PartialDelay2020 874.131359 874.131363 874.131361 874.131361 874.131362 874.131362 874.131362 874.131362 NaN Lower 2C 891.189442
POLES EMF33 EMF33_1.5C_limbio 3714.175184 4560.362743 3308.697516 2357.352413 1679.546115 1196.628442 852.563645 607.427204 NaN Below 1.5C 2265.811769
EMF33_1.5C_nofuel 830.624632 1189.840199 1147.863523 842.315956 618.101550 453.570307 332.835348 244.238553 NaN Below 1.5C 728.264073
WITCH-GLOBIOM 3.1 SSP1-19 782.883830 925.249321 995.842737 1006.494383 970.090273 899.978937 808.683354 706.955435 NaN 1.5C low overshoot 920.800514

Plot price trajectory by category over time using a log scale

This section produces panel a of Figure 2.26.

In [30]:
filtered_data = pyam.filter_by_meta(carbon_price, df, category=None, marker=None, join_meta=True)
In [31]:
price_threshold = 15000
In [32]:
hlines = (
    [i for i in range(10, 91, 10)] 
    + [i for i in range(100, 901, 100)]
    + [i for i in range(1000, 10001, 1000)]
)
In [33]:
boxplot_by_cat(filtered_data, **plotting_args(), ylabel='Price of carbon (USD/tCO2)',
               log_scale=True, years=years, ymax=price_threshold,
               hlines=hlines, legend=False, save='output/fig2.26a_carbon_price_over_time.png')

Summary of outliers in the figure

Display the timeseries data of scenarios exceeding the upper threshold of the figure (the number of data points exceeding the threshold per category is marked above the panel).

In [34]:
above_threshold = filtered_data[years].apply(lambda x: max(x) > price_threshold, axis=1)
filtered_data[above_threshold]
Out[34]:
2030 2040 2050 2060 2070 2080 2090 2100 marker category
model scenario
MESSAGE V.3 GEA_Mix_1p5C_AdvNCO2_PartialDelay2020 1423.867874 2319.330739 3777.945369 6153.874914 10024.013790 16328.06220 26596.69278 43323.20996 NaN Lower 2C
POLES EMF33 EMF33_1.5C_limbio 6050.000000 12100.000000 14300.000000 16595.734380 19260.026760 22352.04512 25940.46426 30104.96758 NaN Below 1.5C
WITCH-GLOBIOM 3.1 SSP1-19 1275.235264 2454.961901 4303.974925 7085.709092 11124.412990 16810.87387 24605.34384 35037.73011 NaN 1.5C low overshoot
SSP4-19 874.829674 1684.139060 2952.588503 4860.898022 7631.506795 11532.50048 16879.61863 24036.38517 NaN 1.5C low overshoot

Export timeseries data to xlsx

In [35]:
writer = pd.ExcelWriter('output/sec2.5_carbon_price_timeseries.xlsx')
pyam.utils.write_sheet(writer, 'real', pyam.filter_by_meta(carbon_price, **filter_args),
                       index=True)
pyam.utils.write_sheet(writer, 'npv', pyam.filter_by_meta(carbon_price_npv, **filter_args),
                       index=True)
writer.save()

Assessment of relative difference between 1.5°C and 2°C scenarios

The following assessment is the basis of Section 2.5.2.1.

We first define a mapping between corresponding pairs of scenarios, then assign this mapping to a number of dictionaries.

In [36]:
mapping = [
    ('SSP1-19', 'SSP1-26', 'SSP1'),
    ('SSP2-19', 'SSP2-26', 'SSP2'),
    ('SSP5-19', 'SSP5-26', 'SSP5'),
    ('ADVANCE_2020_1.5C-2100', 'ADVANCE_2020_WB2C', 'ADVANCE_2020'),
    ('CD-LINKS_NPi2020_400', 'CD-LINKS_NPi2020_1000', 'CD-LINKS_NPi2020'),
    ('DAC15_50', 'DAC2_66', 'DAC'),
    ('EMF33_1.5C_full', 'EMF33_WB2C_full', 'EMF33_full'),
    ('EMF33_1.5C_limbio', 'EMF33_WB2C_limbio', 'EMF33_limbio'),
    ('EMF33_1.5C_nofuel', 'EMF33_WB2C_nofuel', 'EMF33_nofuel'),
    ('PEP_1p5C_full_eff', 'PEP_2C_full_eff', 'PEP_full_eff'),
    ('PEP_1p5C_full_goodpractice', 'PEP_2C_full_goodpractice', 'PEP_full_goodpractice'),
    ('PEP_1p5C_full_NDC', 'PEP_2C_full_NDC', 'PEP_full_NDC'),
    ('PEP_1p5C_full_netzero', 'PEP_2C_full_netzero', 'PEP_full_netzero'),
    ('PEP_1p5C_red_eff', 'PEP_2C_red_eff', 'PEP_red_eff'),
    ('SMP_1p5C_Def', 'SMP_2C_Def', 'SMP_Def'),
    ('SMP_1p5C_early', 'SMP_2C_early', 'SMP_early'),
    ('SMP_1p5C_lifesty', 'SMP_2C_lifesty', 'SMP_lifesty'),
    ('SMP_1p5C_regul', 'SMP_2C_regul', 'SMP_regul'),
    ('SMP_1p5C_Sust', 'SMP_2C_Sust', 'SMP_Sust'),
    ('SFCM_SSP2_Bio_1p5Degree', 'SFCM_SSP2_Bio_2Degree', 'SFCM_SSP2_Bio'),
    ('SFCM_SSP2_combined_1p5Degree', 'SFCM_SSP2_combined_2Degree', 'SFCM_SSP2_combined'),
    ('SFCM_SSP2_EEEI_1p5Degree', 'SFCM_SSP2_EEEI_2Degree', 'SFCM_SSP2_EEEI'),
    ('SFCM_SSP2_LifeStyle_1p5Degree', 'SFCM_SSP2_LifeStyle_2Degree', 'SFCM_SSP2_LifeStyle'),
    ('SFCM_SSP2_Ref_1p5Degree', 'SFCM_SSP2_Ref_2Degree', 'SFCM_SSP2_Ref'),
    ('SFCM_SSP2_ST_bio_1p5Degree', 'SFCM_SSP2_ST_bio_2Degree', 'SFCM_SSP2_ST_bio'),
    ('SFCM_SSP2_ST_CCS_1p5Degree', 'SFCM_SSP2_ST_CCS_2Degree', 'SFCM_SSP2_ST_CCS'),
    ('SFCM_SSP2_ST_nuclear_1p5Degree', 'SFCM_SSP2_ST_nuclear_2Degree', 'SFCM_SSP2_ST_nuclear'),
    ('SFCM_SSP2_ST_solar_1p5Degree', 'SFCM_SSP2_ST_solar_2Degree', 'SFCM_SSP2_ST_solar'),
    ('SFCM_SSP2_ST_wind_1p5Degree', 'SFCM_SSP2_ST_wind_2Degree', 'SFCM_SSP2_ST_wind'),
    ('SFCM_SSP2_SupTech_1p5Degree', 'SFCM_SSP2_SupTech_2Degree', 'SFCM_SSP2_SupTech'),
    ('TERL_15D_LowCarbonTransportPolicy', 'TERL_2D_LowCarbonTransportPolicy', 'TERL_LowCarbonTransportPolicy'),
    ('TERL_15D_NoTransportPolicy', 'TERL_2D_NoTransportPolicy', 'TERL_NoTransportPolicy')
]
In [37]:
rename_1p5 = {}
rename_1p5_reverse = {}
rename_2 = {}
rename_2_reverse = {}

for (scen_1p5, scen_2, scen) in mapping:
    rename_1p5[scen_1p5] = scen
    rename_1p5_reverse[scen] = scen_1p5
    rename_2[scen_2] = scen
    rename_2_reverse[scen] = scen_2
In [38]:
def apply_rename_mapping(data, rename):
    return data.loc[(slice(None), rename), :].copy().rename(rename, level=1)
In [39]:
carbon_price_all = df.filter(variable='Price|Carbon', exclude=False,
                             year=range(2030, 2101, 10)).timeseries()
carbon_price_all = pyam.filter_by_meta(carbon_price_all, sr1p5, ac_npv=None, join_meta=True)
In [40]:
carbon_price_1p5 = apply_rename_mapping(carbon_price_all, rename_1p5)
carbon_price_2 = apply_rename_mapping(carbon_price_all, rename_2)

Show pairs where only one of the corresponding scenario is available

The reason for the corresponding scenario not being available could be one of the following:

  • not reported by modeling team
  • excluded due misreported carbon prices (see section above)
  • warming outcome not in line with comparison of 1.5°C and 2°C scenarios
In [41]:
pd.DataFrame(index=carbon_price_1p5.index.difference(carbon_price_2.index))
Out[41]:
model scenario region variable unit
AIM/CGE 2.0 SFCM_SSP2_Bio World Price|Carbon US$2010/t CO2
SFCM_SSP2_Ref World Price|Carbon US$2010/t CO2
SFCM_SSP2_ST_CCS World Price|Carbon US$2010/t CO2
SFCM_SSP2_ST_bio World Price|Carbon US$2010/t CO2
SFCM_SSP2_SupTech World Price|Carbon US$2010/t CO2
GCAM 4.2 SSP2 World Price|Carbon US$2010/t CO2
SSP5 World Price|Carbon US$2010/t CO2
REMIND-MAgPIE 1.5 SSP5 World Price|Carbon US$2010/t CO2
In [42]:
pd.DataFrame(index=carbon_price_2.index.difference(carbon_price_1p5.index))
Out[42]:
model scenario region variable unit
AIM/CGE 2.0 SSP5 World Price|Carbon US$2010/t CO2
AIM/CGE 2.1 EMF33_full World Price|Carbon US$2010/t CO2
IMAGE 3.0.2 EMF33_full World Price|Carbon US$2010/t CO2
EMF33_limbio World Price|Carbon US$2010/t CO2
EMF33_nofuel World Price|Carbon US$2010/t CO2
MESSAGE-GLOBIOM 1.0 EMF33_limbio World Price|Carbon US$2010/t CO2
EMF33_nofuel World Price|Carbon US$2010/t CO2
REMIND-MAgPIE 1.7-3.0 EMF33_limbio World Price|Carbon US$2010/t CO2
WITCH-GLOBIOM 3.1 SSP2 World Price|Carbon US$2010/t CO2

Compute the relative difference per scenario pair over time

In [43]:
carbon_price_rel = carbon_price_1p5 / carbon_price_2

Remove scenarios where the relative difference is not defined

In [44]:
carbon_price_rel = carbon_price_rel[~np.isnan(carbon_price_rel[2030])]

Define an auxiliary function to generate descriptive statistics

In [45]:
def describe_by_cat(data, category=None):
    return (
        apply_rename_mapping(
            pyam.filter_by_meta(apply_rename_mapping(data, rename_1p5_reverse),
                                sr1p5, category=category),
            rename_1p5)
        .describe()
        .reindex(index=['count', 'mean', '25%', '75%'])
    )

Show the descriptive statistics across corresponding scenario paris

Across all scenario pairs where the lower scenario of the pair is in a 1.5°C category

In [46]:
describe_by_cat(carbon_price_rel, ['Below 1.5C', '1.5C low overshoot', '1.5C high overshoot'])
Out[46]:
2030 2040 2050 2060 2070 2080 2090 2100 ac_npv
count 39.000000 39.000000 39.000000 39.000000 39.000000 39.000000 39.000000 39.000000 39.000000
mean 3.398137 3.359933 3.450369 3.371025 3.157821 3.161912 3.190651 3.226003 3.280339
25% 1.735533 1.926173 2.071409 1.926178 1.984034 2.034221 1.984022 1.926156 2.005698
75% 4.080526 4.560990 4.686391 4.770813 4.318693 4.197154 4.147879 4.114844 4.357414

Show the descriptive statistics across scenario pairs excluding high overshoot scebaruis

In [47]:
describe_by_cat(carbon_price_rel, ['Below 1.5C', '1.5C low overshoot'])
Out[47]:
2030 2040 2050 2060 2070 2080 2090 2100 ac_npv
count 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000
mean 3.858620 3.760778 3.844842 3.761358 3.458540 3.474266 3.516143 3.575495 3.630415
25% 1.778158 2.130304 2.468064 2.022891 2.135736 2.263657 2.289493 2.022829 2.475405
75% 4.422301 4.658698 5.233685 5.234406 4.381184 4.422301 4.271343 4.422301 4.498053

Descriptive statistics only for scenarios submitted from model intercomparison projects

In [48]:
projects = ['ADVANCE*', 'SSP*', 'CD-LINKS*', 'EMF33*']
In [49]:
carbon_price_rel_mip = (
    carbon_price_rel[pyam.pattern_match(carbon_price_rel.reset_index().scenario, projects).values]
)

Descriptive statistics across all scenario pairs

In [50]:
describe_by_cat(carbon_price_rel_mip, ['Below 1.5C', '1.5C low overshoot', '1.5C high overshoot'])
Out[50]:
2030 2040 2050 2060 2070 2080 2090 2100 ac_npv
count 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000
mean 4.146089 3.745379 4.015951 3.976128 3.628382 3.602181 3.569069 3.534463 3.749630
25% 2.283465 2.242089 2.762377 2.497634 2.314019 2.312845 2.309578 2.234431 2.475405
75% 4.757213 4.839825 5.233685 5.234406 4.808397 4.851501 4.827645 4.826477 4.935952

Only scenarios that remain below 1.5°C or exhibit low overshoot

In [51]:
describe_by_cat(carbon_price_rel_mip, ['Below 1.5C', '1.5C low overshoot'])
Out[51]:
2030 2040 2050 2060 2070 2080 2090 2100 ac_npv
count 17.000000 17.000000 17.000000 17.000000 17.000000 17.000000 17.000000 17.000000 17.000000
mean 4.725467 4.223374 4.577471 4.570903 4.065030 4.039622 3.987097 3.943845 4.224948
25% 2.556071 2.829308 3.877778 3.093308 3.093308 2.720915 3.084309 2.708139 3.093308
75% 5.353280 5.145268 5.428333 5.486838 4.915467 5.145268 5.145268 5.084016 5.239983

Export data for relative carbon price assessment to xlsx

In [52]:
def add_cats(data, df, col_suffix, mapping, mapping_reverse):
    ret = apply_rename_mapping(data, mapping_reverse)
    ret['scenario_{}'.format(col_suffix)] = ret.reset_index().scenario.values
    ret = pyam.filter_by_meta(ret, df, category=None, join_meta=True)
    ret.rename(columns={'category': 'subcategory_{}'.format(col_suffix)}, inplace=True)
    return apply_rename_mapping(ret, mapping)
In [53]:
carbon_price_rel = add_cats(carbon_price_rel, df, '1.5', rename_1p5, rename_1p5_reverse)
In [54]:
carbon_price_rel = add_cats(carbon_price_rel, df, '2', rename_2, rename_2_reverse)
In [55]:
carbon_price_rel.to_excel('output/sec2.5_relative_carbon_prices.xlsx')
In [ ]: