Skip to content

Commit 055ace2

Browse files
committed
Test new helper functions
1 parent 55d8966 commit 055ace2

File tree

3 files changed

+159
-52
lines changed

3 files changed

+159
-52
lines changed

src/pandas_openscm/accessors.py

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from pandas_openscm.index_manipulation import (
3232
convert_index_to_category_index,
33+
ensure_index_is_multiindex,
3334
set_index_levels_func,
3435
update_index_levels_from_other_func,
3536
update_index_levels_func,
@@ -74,6 +75,46 @@ def __init__(self, pandas_obj: pd.DataFrame):
7475
# However, it's probably better to do validation closer to the data use.
7576
self._df = pandas_obj
7677

78+
def ensure_index_is_multiindex(self, copy: bool = True) -> pd.DataFrame:
79+
"""
80+
Ensure that the index is a [pd.MultiIndex][pandas.MultiIndex]
81+
82+
Parameters
83+
----------
84+
copy
85+
Whether to copy `df` before manipulating the index name
86+
87+
Returns
88+
-------
89+
:
90+
`df` with a [pd.MultiIndex][pandas.MultiIndex]
91+
92+
If the index was already a [pd.MultiIndex][pandas.MultiIndex],
93+
this is a no-op (although the value of copy is respected).
94+
"""
95+
return ensure_index_is_multiindex(self._df, copy=copy)
96+
97+
def eiim(self, copy: bool = True) -> pd.DataFrame:
98+
"""
99+
Ensure that the index is a [pd.MultiIndex][pandas.MultiIndex]
100+
101+
Alias for [ensure_index_is_multiindex][(c).]
102+
103+
Parameters
104+
----------
105+
copy
106+
Whether to copy `df` before manipulating the index name
107+
108+
Returns
109+
-------
110+
:
111+
`df` with a [pd.MultiIndex][pandas.MultiIndex]
112+
113+
If the index was already a [pd.MultiIndex][pandas.MultiIndex],
114+
this is a no-op (although the value of copy is respected).
115+
"""
116+
return self.ensure_index_is_multiindex(copy=copy)
117+
77118
def fix_index_name_after_groupby_quantile(
78119
self, new_name: str = "quantile", copy: bool = False
79120
) -> pd.DataFrame:
@@ -496,6 +537,33 @@ def plot_plume_after_calculating_quantiles( # noqa: PLR0913
496537
observed=observed,
497538
)
498539

540+
def set_index_levels(
541+
self,
542+
levels_to_set: dict[str, Any | Collection[Any]],
543+
copy: bool = True,
544+
) -> pd.DataFrame:
545+
"""
546+
Set the index levels
547+
548+
Parameters
549+
----------
550+
levels_to_set
551+
Mapping of level names to values to set
552+
553+
copy
554+
Should the [pd.DataFrame][pandas.DataFrame] be copied before returning?
555+
556+
Returns
557+
-------
558+
:
559+
[pd.DataFrame][pandas.DataFrame] with updates applied to its index
560+
"""
561+
return set_index_levels_func(
562+
self._df,
563+
levels_to_set=levels_to_set,
564+
copy=copy,
565+
)
566+
499567
def to_category_index(self) -> pd.DataFrame:
500568
"""
501569
Convert the index's values to categories
@@ -717,33 +785,6 @@ def update_index_levels_from_other(
717785
remove_unused_levels=remove_unused_levels,
718786
)
719787

720-
def set_index_levels(
721-
self,
722-
levels_to_set: dict[str, Any | Collection[Any]],
723-
copy: bool = True,
724-
) -> pd.DataFrame:
725-
"""
726-
Set the index levels
727-
728-
Parameters
729-
----------
730-
levels_to_set
731-
Mapping of level names to values to set
732-
733-
copy
734-
Should the [pd.DataFrame][pandas.DataFrame] be copied before returning?
735-
736-
Returns
737-
-------
738-
:
739-
[pd.DataFrame][pandas.DataFrame] with updates applied to its index
740-
"""
741-
return set_index_levels_func(
742-
self._df,
743-
levels_to_set=levels_to_set,
744-
copy=copy,
745-
)
746-
747788

748789
def register_pandas_accessor(namespace: str = "openscm") -> None:
749790
"""

src/pandas_openscm/index_manipulation.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,16 @@ def ensure_index_is_multiindex(pandas_obj: P, copy: bool = True) -> P:
9090
-------
9191
:
9292
`pandas_obj` with a [pd.MultiIndex][pandas.MultiIndex]
93-
"""
94-
# TODOO: accessor and tests
95-
if isinstance(pandas_obj.index, pd.MultiIndex):
96-
return pandas_obj
9793
94+
If the index was already a [pd.MultiIndex][pandas.MultiIndex],
95+
this is a no-op (although the value of copy is respected).
96+
"""
9897
if copy:
9998
pandas_obj = pandas_obj.copy()
10099

100+
if isinstance(pandas_obj.index, pd.MultiIndex):
101+
return pandas_obj
102+
101103
pandas_obj.index = ensure_is_multiindex(pandas_obj.index)
102104

103105
return pandas_obj

tests/integration/index_manipulation/test_integration_index_manipulation_ensure_index_is_multiindex.py

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,104 @@
55
from __future__ import annotations
66

77
import numpy as np
8+
import pandas as pd
9+
import pytest
810

9-
from pandas_openscm.index_manipulation import convert_index_to_category_index
11+
from pandas_openscm.index_manipulation import ensure_index_is_multiindex
1012
from pandas_openscm.testing import create_test_df
1113

1214

13-
def ensure_index_is_multiindex():
14-
raise NotImplementedError
15-
units = ["Mt", "kg", "W"]
15+
@pytest.mark.parametrize("copy, copy_exp", ((None, True), (True, True), (False, False)))
16+
def test_ensure_index_is_multiindex(copy, copy_exp):
17+
start = pd.DataFrame(
18+
[[1, 2], [3, 4]],
19+
columns=[10, 20],
20+
index=pd.Index(["a", "b"], name="variable"),
21+
)
22+
23+
call_kwargs = {}
24+
if copy is not None:
25+
call_kwargs["copy"] = copy
26+
27+
res = ensure_index_is_multiindex(start, **call_kwargs)
28+
29+
assert isinstance(res.index, pd.MultiIndex)
30+
assert res.index.names == ["variable"]
31+
32+
if copy_exp:
33+
# New object returned
34+
assert id(start) != id(res)
1635

17-
# Biggish DataFrame
36+
assert isinstance(start.index, pd.Index)
37+
38+
else:
39+
# Same object returned
40+
assert id(start) == id(res)
41+
# Therefore affects input object too
42+
assert isinstance(start.index, pd.MultiIndex)
43+
44+
45+
@pytest.mark.parametrize("copy, copy_exp", ((None, True), (True, True), (False, False)))
46+
def test_ensure_index_is_multiindex_no_op(copy, copy_exp):
1847
start = create_test_df(
19-
variables=[(f"variable_{i}", units[i % len(units)]) for i in range(25)],
20-
n_scenarios=30,
21-
n_runs=60,
48+
variables=[("Temperature", "K")],
49+
n_scenarios=2,
50+
n_runs=2,
2251
timepoints=np.arange(1750.0, 2100.0 + 1.0),
2352
)
2453

25-
res = convert_index_to_category_index(start)
54+
call_kwargs = {}
55+
if copy is not None:
56+
call_kwargs["copy"] = copy
2657

27-
run_checks(res, start)
58+
res = ensure_index_is_multiindex(start, **call_kwargs)
2859

60+
# Already a MultiIndex, should be no change
61+
pd.testing.assert_index_equal(res.index, start.index)
2962

30-
def test_accessor(setup_pandas_accessor):
31-
raise NotImplementedError
32-
units = ["Mt", "kg", "W"]
63+
# Behaviour of copy should be respected to avoid confusing behaviour
64+
if copy_exp:
65+
# New object returned
66+
assert id(start) != id(res)
3367

34-
# Biggish DataFrame
35-
start = create_test_df(
36-
variables=[(f"variable_{i}", units[i % len(units)]) for i in range(25)],
37-
n_scenarios=30,
38-
n_runs=60,
39-
timepoints=np.arange(1750.0, 2100.0 + 1.0),
68+
else:
69+
# Same object returned
70+
assert id(start) == id(res)
71+
72+
73+
@pytest.mark.parametrize("copy, copy_exp", ((None, True), (True, True), (False, False)))
74+
def test_accessor(setup_pandas_accessor, copy, copy_exp):
75+
start = pd.DataFrame(
76+
[[1, 2], [3, 4]],
77+
columns=[10, 20],
78+
index=pd.Index(["a", "b"], name="variable"),
4079
)
4180

42-
res = start.openscm.to_category_index()
81+
call_kwargs = {}
82+
if copy is not None:
83+
call_kwargs["copy"] = copy
84+
85+
res = start.openscm.ensure_index_is_multiindex(**call_kwargs)
86+
87+
assert isinstance(res.index, pd.MultiIndex)
88+
assert res.index.names == ["variable"]
89+
if copy_exp:
90+
# New object returned
91+
assert id(start) != id(res)
92+
93+
else:
94+
# Same object returned
95+
assert id(start) == id(res)
96+
97+
# Test alias too
98+
res_short = start.openscm.eiim(**call_kwargs)
99+
100+
assert isinstance(res_short.index, pd.MultiIndex)
101+
assert res_short.index.names == ["variable"]
102+
if copy_exp:
103+
# New object returned
104+
assert id(start) != id(res_short)
43105

44-
run_checks(res, start)
106+
else:
107+
# Same object returned
108+
assert id(start) == id(res_short)

0 commit comments

Comments
 (0)