Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Next Release

- [#952](https://github.com/IAMconsortium/pyam/pull/952) Add an `aggregate_kyoto_gases()` method

# Release v3.3.0

## Highlights
Expand Down
4 changes: 2 additions & 2 deletions pyam/aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
logger = logging.getLogger(__name__)


def _aggregate(df, variable, components=None, method="sum"):
def aggregate_data(df, variable, components=None, method="sum"):
"""Internal implementation of the `aggregate` function"""

if components is not None:
Expand Down Expand Up @@ -74,7 +74,7 @@ def _aggregate_recursive(df, variable, recursive):
var_list = {reduce_hierarchy(v, -1) for v in components}

# a temporary dataframe allows to distinguish between full data and new data
_data_agg = _aggregate(_df, variable=var_list)
_data_agg = aggregate_data(_df, variable=var_list)

# check if data for intermediate variables already exists
with adjust_log_level("pyam.core"):
Expand Down
33 changes: 30 additions & 3 deletions pyam/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pandas as pd
from pandas.api.types import is_integer

from pyam.emissions import aggregate_kyoto_ghg
from pyam.netcdf import to_xarray

try:
Expand All @@ -23,11 +24,11 @@

from pyam._compare import _compare
from pyam.aggregation import (
_aggregate,
_aggregate_recursive,
_aggregate_region,
_aggregate_time,
_group_and_agg,
aggregate_data,
)
from pyam.compute import IamComputeAccessor
from pyam.exceptions import format_log_message, raise_data_error
Expand Down Expand Up @@ -246,6 +247,7 @@ def _finalize(self, data, append, **args):
else:
if data is None or data.empty:
return _empty_iamframe(self.dimensions + ["value"])

return IamDataFrame(data, meta=self.meta, **args)

def __getitem__(self, key):
Expand Down Expand Up @@ -1459,7 +1461,7 @@ def aggregate(
_aggregate_recursive(self, variable, recursive), meta=self.meta
)
else:
_df = _aggregate(self, variable, components=components, method=method)
_df = aggregate_data(self, variable, components=components, method=method)

# append to `self` or return as `IamDataFrame`
return self._finalize(_df, append=append)
Expand Down Expand Up @@ -1499,7 +1501,7 @@ def check_aggregate(

"""
# compute aggregate from components, return None if no components
df_components = _aggregate(self, variable, components, method)
df_components = aggregate_data(self, variable, components, method)
if df_components is None:
return

Expand Down Expand Up @@ -1864,6 +1866,31 @@ def check_internal_consistency(self, components=False, **kwargs):
]
]

def aggregate_kyoto_ghg(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

"""
return self._finalize(
aggregate_kyoto_ghg(
self,
metric,
f"Emissions|Kyoto Gases [{metric}]",
"Mt CO2-equiv/yr",
),
append=append,
)

def slice(self, *, keep=True, **kwargs):
"""Return a (filtered) slice object of the IamDataFrame timeseries data index

Expand Down
50 changes: 50 additions & 0 deletions pyam/emissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from pyam.aggregation import aggregate_data
from pyam.exceptions import raise_data_error

REQUIRED_KYOTO_SPECIES = ["Emissions|CO2", "Emissions|CH4", "Emissions|N2O"]


ALL_KYOTO_SPECIES = {
"Emissions|CO2",
"Emissions|CH4",
"Emissions|N2O",
"Emissions|HFC|HFC125",
"Emissions|HFC|HFC134a",
"Emissions|HFC|HFC143a",
"Emissions|HFC|HFC152a",
"Emissions|HFC|HFC227ea",
"Emissions|HFC|HFC23",
"Emissions|HFC|HFC236fa",
"Emissions|HFC|HFC245fa",
"Emissions|HFC|HFC32",
"Emissions|HFC|HFC365mfc",
"Emissions|HFC|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_ghg(df, metric: str, target_variable: str, target_unit: str):
"""Internal implementation of the `aggregate_kyoto_ghg` function"""

_df = df.filter(variable=ALL_KYOTO_SPECIES)

missing = _df.require_data(variable=REQUIRED_KYOTO_SPECIES)
if missing is not None:
raise_data_error(
"Missing emission species required for Kyoto GHG aggregation", missing
)

for unit in _df.unit:
_df.convert_unit(unit, target_unit, context=metric, inplace=True)

return aggregate_data(_df, target_variable, components=_df.variable)
60 changes: 60 additions & 0 deletions tests/test_emissions.py
Original file line number Diff line number Diff line change
@@ -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_ghg(metric="AR6GWP100", append=append)
exp = df.append(exp)
else:
obs = df.aggregate_kyoto_ghg(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_ghg(metric="AR6GWP100")
Loading