Skip to content

Commit

Permalink
Implement filtering by variable depth
Browse files Browse the repository at this point in the history
  • Loading branch information
danielhuppmann committed Nov 3, 2024
1 parent 735c243 commit cc99b32
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 15 deletions.
5 changes: 3 additions & 2 deletions docs/api/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ Timeseries data coordinates
- Any *column* of the :attr:`IamDataFrame.coordinates <pyam.IamDataFrame.coordinates>`
('**region**', '**variable**', '**unit**'): string or list of strings
- '**measurand**': a tuple (or list of tuples) of '*variable*' and '*unit*'
- '**level**': the "depth" of entries in the '*variable*' column (number of '|')
(excluding the strings in the '*variable*' argument, if given)
- '**depth**': the "depth" of entries in the '*variable*' column (number of '|')
- '**level**': the "depth" of entries in the '*variable*' column (number of '|'),
excluding the strings in the '*variable*' argument (if given)
- '**year**': takes an integer (int/:class:`numpy.int64`), a list of integers or
a range. Note that the last year of a range is not included,
so ``range(2010, 2015)`` is interpreted as ``[2010, ..., 2014]``
Expand Down
12 changes: 9 additions & 3 deletions pyam/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,7 @@ def filter(self, *, keep=True, inplace=False, **kwargs):
if not inplace:
return ret

def _apply_filters(self, level=None, **filters): # noqa: C901
def _apply_filters(self, level=None, depth=None, **filters): # noqa: C901
"""Determine rows to keep in data for given set of filters
Parameters
Expand All @@ -1918,6 +1918,9 @@ def _apply_filters(self, level=None, **filters): # noqa: C901
regexp = filters.pop("regexp", False)
keep = np.ones(len(self), dtype=bool)

if level is not None and depth is not None:
raise ValueError("Filter by `level` and `depth` not supported")

if "variable" in filters and "measurand" in filters:
raise ValueError("Filter by `variable` and `measurand` not supported")

Expand Down Expand Up @@ -2003,10 +2006,13 @@ def _apply_filters(self, level=None, **filters): # noqa: C901
keep = np.logical_and(keep, keep_col)

if level is not None and not ("variable" in filters or "measurand" in filters):
# if level and variable/measurand is given, level-filter is applied there
# if level is given without variable/measurand, it is equivalent to depth
depth = level

if depth is not None:
col = "variable"
lvl_index, lvl_codes = get_index_levels_codes(self._data, col)
matches = find_depth(lvl_index, level=level)
matches = find_depth(lvl_index, level=depth)
keep_col = get_keep_col(lvl_codes, matches)

keep = np.logical_and(keep, keep_col)
Expand Down
56 changes: 46 additions & 10 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,22 +431,46 @@ def test_filter_empty_df():
assert len(obs) == 0


def test_variable_and_measurand_raises(test_df):
pytest.raises(ValueError, test_df.filter, variable="foo", measurand=("foo", "bar"))
def test_filter_variable_and_measurand_raises(test_df):
with pytest.raises(ValueError, match="Filter by `variable` and `measurand` not"):
test_df.filter(variable="foo", measurand=("foo", "bar"))


def test_filter_level_and_depth_raises(test_df):
with pytest.raises(ValueError, match="Filter by `level` and `depth` not"):
test_df.filter(level=1, depth=2)


@pytest.mark.parametrize(
"filter_args",
(dict(variable="*rimary*C*"), dict(measurand=("*rimary*C*", "EJ/*"))),
)
def test_filter_variable_and_depth(test_df, filter_args):
def test_filter_variable_and_level(test_df, filter_args):
obs = test_df.filter(**filter_args, level=0).variable
assert obs == ["Primary Energy|Coal"]

obs = test_df.filter(**filter_args, level="0+").variable
assert obs == ["Primary Energy|Coal"]

obs = test_df.filter(**filter_args, level=1).variable
assert obs == []


@pytest.mark.parametrize(
"filter_args",
(dict(variable="*rimary*C*"), dict(measurand=("*rimary*C*", "EJ/*"))),
)
def test_filter_variable_and_depth(test_df, filter_args):
obs = test_df.filter(**filter_args, depth=1).variable
assert obs == ["Primary Energy|Coal"]

obs = test_df.filter(**filter_args, depth="0+").variable
assert obs == ["Primary Energy|Coal"]

obs = test_df.filter(**filter_args, depth=0).variable
assert obs == []


def test_filter_measurand_list(test_df):
data = test_df.data
data.loc[4, "variable"] = "foo"
Expand All @@ -460,18 +484,30 @@ def test_filter_measurand_list(test_df):
assert obs.scenario == ["scen_b"]


def test_variable_depth_0_keep_false(test_df):
obs = test_df.filter(level=0, keep=False).variable
@pytest.mark.parametrize(
"filter_name",
("level", "depth"),
)
def test_variable_depth_0_keep_false(test_df, filter_name):
obs = test_df.filter(**{filter_name: 0}, keep=False).variable
assert obs == ["Primary Energy|Coal"]


def test_variable_depth_raises(test_df):
pytest.raises(ValueError, test_df.filter, level="1/")
@pytest.mark.parametrize(
"filter_name",
("level", "depth"),
)
def test_variable_depth_raises(test_df, filter_name):
pytest.raises(ValueError, test_df.filter, **{filter_name: "1/"})


def test_variable_depth_with_list_raises(test_df):
pytest.raises(ValueError, test_df.filter, level=["1", "2"])
pytest.raises(ValueError, test_df.filter, level=[1, 2])
@pytest.mark.parametrize(
"filter_name",
("level", "depth"),
)
def test_variable_depth_with_list_raises(test_df, filter_name):
pytest.raises(ValueError, test_df.filter, **{filter_name: ["1", "2"]})
pytest.raises(ValueError, test_df.filter, **{filter_name: [1, 2]})


def test_timeseries(test_df):
Expand Down

0 comments on commit cc99b32

Please sign in to comment.