diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 94293de47..2abc2a359 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,6 @@ # Next Release +- [#952](https://github.com/IAMconsortium/pyam/pull/952) Add an `aggregate_kyoto_gases()` method - [#946](https://github.com/IAMconsortium/pyam/pull/946) Handle plot-styling by meta indicators with `np.nan` - [#944](https://github.com/IAMconsortium/pyam/pull/944) Refactor to a `format_n()` function for nice log messages - [#943](https://github.com/IAMconsortium/pyam/pull/943) Improved handling for division by zero diff --git a/pyam/core.py b/pyam/core.py index cbfd3e497..28f1bdb45 100755 --- a/pyam/core.py +++ b/pyam/core.py @@ -11,6 +11,7 @@ import pandas as pd from pandas.api.types import is_integer +from pyam.emissions import SPECIES_UNIT_MAPPING, aggregate_kyoto_gases from pyam.netcdf import to_xarray try: @@ -1864,6 +1865,26 @@ def check_internal_consistency(self, components=False, **kwargs): ] ] + def aggregate_kyoto_gases(self, *, metric: str, append: bool = False): + """Compute the aggregate Kyoto gases from a set of species using a GWP metric + + metric: str + A global warming potential (GWP) metric supported by :mod:`iam_units`, + e.g. 'AR6GWP100'. + append : bool, optional + Append the aggregate emissions timeseries to `self` and return None, + else return aggregated emissions timeseries as new :class:`IamDataFrame`. + + See Also + -------- + pyam.IamDataFrame.convert_unit + + """ + data = concat(aggregate_kyoto_gases(self, metric)).aggregate( + f"Emissions|Kyoto Gases [{metric}]", components=SPECIES_UNIT_MAPPING.keys() + ) + return self._finalize(data, append=append) + def slice(self, *, keep=True, **kwargs): """Return a (filtered) slice object of the IamDataFrame timeseries data index diff --git a/pyam/emissions.py b/pyam/emissions.py new file mode 100644 index 000000000..0eabfc6c9 --- /dev/null +++ b/pyam/emissions.py @@ -0,0 +1,50 @@ +from pyam.exceptions import raise_data_error + +REQUIRED_SPECIES = ["Emissions|CO2", "Emissions|CH4", "Emissions|N2O"] + +ALL_KYOTO_SPECIES = { + "Emissions|CO2", + "Emissions|CH4", + "Emissions|N2O", + "Emissions|HFC125", + "Emissions|HFC134a", + "Emissions|HFC143a", + "Emissions|HFC152a", + "Emissions|HFC227ea", + "Emissions|HFC23", + "Emissions|HFC236fa", + "Emissions|HFC245fa", + "Emissions|HFC32", + "Emissions|HFC365mfc", + "Emissions|HFC4310mee", + "Emissions|NF3", + "Emissions|SF6", + "Emissions|C2F6", + "Emissions|C3F8", + "Emissions|C4F10", + "Emissions|C5F12", + "Emissions|C6F14", + "Emissions|C7F16", + "Emissions|C8F18", + "Emissions|CF4", + "Emissions|cC4F8", +} + + +def aggregate_kyoto_gases(df, metric: str): + """Internal implementation of the `aggregate_kyoto_gases` function""" + + missing = df.require_data(variable=REQUIRED_SPECIES) + if missing is not None: + raise_data_error("Missing species for aggregation", missing) + + df_list = list() + for species, unit in SPECIES_UNIT_MAPPING.items(): + if species in df.variable: + df_list.append( + df.filter(variable=species).convert_unit( + unit, "Mt CO2-equiv/yr", context=metric + ) + ) + + return df_list diff --git a/tests/test_emissions.py b/tests/test_emissions.py new file mode 100644 index 000000000..a1366af01 --- /dev/null +++ b/tests/test_emissions.py @@ -0,0 +1,60 @@ +import pandas as pd +import pytest + +from pyam import IamDataFrame +from pyam.testing import assert_iamframe_equal + +EMISSIONS_SPECIES_DATA = pd.DataFrame( + [ + ["Emissions|CO2", "Mt CO2/yr", 42885.41, 33011.87, 24642.81], + ["Emissions|CH4", "Mt CH4/yr", 413.63, 287.42, 233.97], + ["Emissions|N2O", "kt N2O/yr", 11623.95, 9005.23, 8177.40], + ["Emissions|SF6", "kt SF6/yr", 8.01, 5.26, 2.60], + ["Emissions|HFC|HFC125", "kt HFC125/yr", 98.76, 57.44, 16.71], + ["Emissions|HFC|HFC134a", "kt HFC134a/yr", 248.84, 144.53, 42.41], + ["Emissions|HFC|HFC143a", "kt HFC143a/yr", 40.59, 23.61, 6.87], + ["Emissions|HFC|HFC23", "kt HFC23/yr", 7.13, 4.24, 1.55], + ["Emissions|HFC|HFC32", "kt HFC32/yr", 61.18, 35.55, 10.29], + ], + columns=["variable", "unit", 2020, 2025, 2030], +) + + +EXP_GHG_DATA = pd.DataFrame( + [ + [ + "Emissions|Kyoto Gases [AR6GWP100]", + "Mt CO2-equiv/yr", + 58938.34, + 44284.49, + 33666.64, + ] + ], + columns=["variable", "unit", 2020, 2025, 2030], +) + + +@pytest.mark.parametrize("append", ((False, True))) +def test_kyoto_ghg(append): + df_args = dict(model="model_a", scenario="scenario_a", region="World") + df = IamDataFrame(EMISSIONS_SPECIES_DATA, **df_args) + exp = IamDataFrame(EXP_GHG_DATA, **df_args) + + if append: + obs = df.copy() + obs.aggregate_kyoto_gases(metric="AR6GWP100", append=append) + exp = df.append(exp) + else: + obs = df.aggregate_kyoto_gases(metric="AR6GWP100") + + assert_iamframe_equal(exp, obs) + + +def test_kyoto_ghg_raises(): + df_args = dict(model="model_a", scenario="scenario_a", region="World") + df = IamDataFrame(EMISSIONS_SPECIES_DATA, **df_args) + df.filter(variable="Emissions|CH4", keep=False, inplace=True) + + match = "Missing species for aggregation:.* scenario_a Emissions|CH4" + with pytest.raises(ValueError, match=match): + df.aggregate_kyoto_gases(metric="AR6GWP100")