Skip to content

Commit 8f67e34

Browse files
committed
set single value
1 parent bf65fb5 commit 8f67e34

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

src/pandas_openscm/index_manipulation.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from __future__ import annotations
66

7-
from collections.abc import Mapping
7+
from collections.abc import Collection, Mapping
88
from typing import TYPE_CHECKING, Any, Callable, TypeVar
99

1010
import numpy as np
@@ -714,3 +714,56 @@ def update_levels_from_other(
714714
res = pd.MultiIndex(levels=levels, codes=codes, names=names)
715715

716716
return res
717+
718+
719+
def set_levels(
720+
ini: pd.MultiIndex, levels_to_set: dict[str, Any | Collection[Any]]
721+
) -> pd.MultiIndex:
722+
"""
723+
Set the levels of a MultiIndex to the provided values
724+
725+
Parameters
726+
----------
727+
ini
728+
Input MultiIndex
729+
730+
levels_to_set
731+
Mapping of level names to values to set
732+
733+
Returns
734+
-------
735+
New MultiIndex with the levels set to the provided values
736+
737+
Raises
738+
------
739+
TypeError
740+
If `ini` is not a MultiIndex
741+
ValueError
742+
If the length of the values is not equal to the length of the index
743+
"""
744+
# set the levels specified in levels_to_set to the provided values
745+
#
746+
# We should support both single values e.g. levels_to_set={"variable": "Emissions"}
747+
# and values with the same length as the index itself e.g.
748+
# levels_to_set={"variable": ["a", "b", "c"]}.
749+
# Values of any other length should raise an error (because we don't know what to do
750+
# if the index is say 4 elements long but the user only gives us 3 values)
751+
#
752+
# This should work whether the level to be set exists or not
753+
# TODO: move to pandas-openscm
754+
# TODO: split out method that just works on MultiIndex
755+
756+
new_names = levels_to_set.keys()
757+
new_values = levels_to_set.values()
758+
759+
if not isinstance(ini, pd.MultiIndex):
760+
raise TypeError(ini)
761+
762+
return pd.MultiIndex(
763+
codes=[
764+
*ini.codes, # type: ignore # not sure why check above isn't working
765+
*([[0] * ini.shape[0]] * len(new_values)), # type: ignore # fix when moving to pandas-openscm
766+
],
767+
levels=[*ini.levels, *[pd.Index([value]) for value in new_values]], # type: ignore # fix when moving to pandas-openscm
768+
names=[*ini.names, *new_names], # type: ignore # fix when moving to pandas-openscm
769+
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Test `pandas_openscm.index_manipulation.set_levels`
3+
"""
4+
5+
from __future__ import annotations
6+
7+
import pandas as pd
8+
import pytest
9+
10+
from pandas_openscm.index_manipulation import set_levels
11+
12+
13+
@pytest.mark.parametrize(
14+
"start, levels_to_set, exp",
15+
(
16+
pytest.param(
17+
pd.MultiIndex.from_tuples(
18+
[
19+
("sa", "va", "kg", 0),
20+
("sb", "vb", "m", 1),
21+
("sa", "va", "kg", 2),
22+
],
23+
names=["scenario", "variable", "unit", "run_id"],
24+
),
25+
{"new_variable": "test"},
26+
pd.MultiIndex.from_tuples(
27+
[
28+
("sa", "va", "kg", 0, "test"),
29+
("sb", "vb", "m", 1, "test"),
30+
("sa", "va", "kg", 2, "test"),
31+
],
32+
names=["scenario", "variable", "unit", "run_id", "new_variable"],
33+
),
34+
id="set-single-level",
35+
),
36+
),
37+
)
38+
def test_update_levels_from_other(start, levels_to_set, exp):
39+
res = set_levels(start, levels_to_set=levels_to_set)
40+
41+
pd.testing.assert_index_equal(res, exp)

0 commit comments

Comments
 (0)