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
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ values 'full range', 'interquartile' or 'central 90%'. The previous boolean argu
- [#991](https://github.com/IAMconsortium/pyam/pull/991) Support the central 90% as limits in the statistics summary
- [#989](https://github.com/IAMconsortium/pyam/pull/989) Add an `IamDataFrame.series` attribute to get timeseries data
as **pd.Series**
- [#986](https://github.com/IAMconsortium/pyam/pull/986) Extend `set_meta_from_data()` to apply on different column

# Release v3.4.0

Expand Down
90 changes: 75 additions & 15 deletions pyam/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,31 +946,91 @@ def set_meta(self, meta, name=None, index=None): # noqa: C901
self._new_meta_column(name)
self.meta[name] = meta[name].combine_first(self.meta[name])

def set_meta_from_data(self, name, method=None, column="value", **kwargs):
def set_meta_from_data(self, name, method=None, column="value", on=None, **kwargs):
Comment thread
phackstock marked this conversation as resolved.
"""Add meta indicators from downselected timeseries data

Parameters
----------
name : str
Column name of the 'meta' table
method : function, optional
Resulting column name in the 'meta' table.
method : function or str, optional
Method for aggregation
(e.g., :func:`numpy.max <numpy.ndarray.max>`);
required if downselected data do not yield unique values
required if downselected data do not yield unique values.
column : str, optional
The column from `data` to be used to derive the indicator
The column from `data` to be used to derive the indicator.
on : str, optional
If given, apply the `method` on this column and use corresponding value from
`column` as meta indicator.
**kwargs
Passed to :meth:`slice` for downselected data
Passed to :meth:`slice` for downselection of data.

Raises
------
ValueError
If the resulting meta-indicators are not unique for each element
of the :attr:`index`.

Examples
--------
A simple use case for this method is to compute a meta-indicator for the peak
(maximum) temperature from annual temperature timeseries data for each scenario:

.. code:: python

df.set_meta_from_data(
name="Climate Assessment|Peak Warming [°C]",
method="max",
variable="Climate Assessment|Surface Temperature",
)

Alternatively, the *method* can be applied on a column *on* and the
corresponding value from the *column* is set as meta indicator. This can be
used to set the year when peak-temperature is reached as meta-indicator.

.. code:: python

df.set_meta_from_data(
name="Climate Assessment|Year of Peak Warming",
method="max",
column="value",
on="year",
variable="Climate Assessment|Surface Temperature",
)

"""
values = self._data[self.slice(**kwargs)]
if method is None and column != "value":
values = values.reset_index(column)[column]
elif method is not None:
if column == "value":
values = values.groupby(self.index.names)
else:
values = values.reset_index(column).groupby(self.index.names)[column]
values = values.apply(method)
if on is not None:

@staticmethod
def apply_method(x):
if callable(method):
value = x[x[on] == method(x[on])][column].unique()
else:
value = x[x[on] == x[on].apply(method)][column].unique()

if len(value) > 1:
logger.warning(f"Non-unique result from {method} on column {on}.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this just be a warning? I'd imagine that producing a non-unique value for a meta value can only be ill-conceived. We might want to raise an actual error here.

@danielhuppmann danielhuppmann Jul 17, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my original use case, it could be that the temperature stabilises at (for example) 1.7°,, in particular if we round the output from the climate assessment. So there could be multiple years where peak temperature is reached, without this being necessarily incorrect.

The truly correct solution would be to also provide an on_method argument that makes this unique?


return value[0]

values = (
self._data[self.slice(**kwargs)]
.reset_index([col for col in [column, on] if col != "value"])
.groupby(self.index.names)
.apply(apply_method)
)
else:
values = self._data[self.slice(**kwargs)]
if method is None and column != "value":
values = values.reset_index(column)[column]
elif method is not None:
if column == "value":
values = values.groupby(self.index.names)
else:
values = values.reset_index(column).groupby(self.index.names)[
column
]
values = values.apply(method)
self.set_meta(values, name)

def categorize(
Expand Down
12 changes: 11 additions & 1 deletion tests/test_feature_set_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def test_set_meta_from_data_mean(test_df):
pdt.assert_series_equal(test_df["pe_mean"], exp)


def test_set_meta_from_data_method_other_column(test_df):
def test_set_meta_from_data_method_non_default_column(test_df):
if "year" in test_df.data.columns:
col, value = "year", 2010
else:
Expand All @@ -158,3 +158,13 @@ def test_set_meta_from_data_nonunique(test_df):
pytest.raises(
ValueError, test_df.set_meta_from_data, "fail", variable="Primary Energy"
)


@pytest.mark.parametrize("method", ("min", np.min))
def test_set_meta_from_data_method_on_other_column(test_df_year, method):
# get the variable that has the lowest data value for each scenario
test_df_year.set_meta_from_data("foo", method=method, column="variable", on="value")
exp = pd.Series(
data=["Primary Energy|Coal", "Primary Energy"], index=EXP_IDX, name="foo"
)
pdt.assert_series_equal(test_df_year.meta["foo"], exp)
Loading