diff --git a/changelog/39.breaking.md b/changelog/39.breaking.md new file mode 100644 index 0000000..cfc232e --- /dev/null +++ b/changelog/39.breaking.md @@ -0,0 +1 @@ +Renamed `df` to `pobj` in [pandas_openscm.index_manipulation.update_index_levels_func] and [pandas_openscm.index_manipulation.update_index_levels_from_other_func] diff --git a/changelog/39.feature.md b/changelog/39.feature.md new file mode 100644 index 0000000..bb6682d --- /dev/null +++ b/changelog/39.feature.md @@ -0,0 +1 @@ +More methods for [pandas_openscm.accessors.series.PandasSeriesOpenSCMAccessor] and [pandas_openscm.accessors.index.PandasIndexOpenSCMAccessor], leading to feature completeness ([#25](https://github.com/openscm/pandas-openscm/issues/25)), specifically [pandas_openscm.accessors.series.PandasSeriesOpenSCMAccessor.update_index_levels], [pandas_openscm.accessors.series.PandasSeriesOpenSCMAccessor.update_index_levels_from_other], [pandas_openscm.accessors.index.PandasIndexOpenSCMAccessor.update_levels] and [pandas_openscm.accessors.index.PandasIndexOpenSCMAccessor.update_levels_from_other] diff --git a/src/pandas_openscm/accessors/index.py b/src/pandas_openscm/accessors/index.py index a658d32..413f486 100644 --- a/src/pandas_openscm/accessors/index.py +++ b/src/pandas_openscm/accessors/index.py @@ -4,11 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Generic, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar import pandas as pd -from pandas_openscm.index_manipulation import ensure_is_multiindex +from pandas_openscm.index_manipulation import ( + ensure_is_multiindex, + update_levels, + update_levels_from_other, +) if TYPE_CHECKING: # Hmm this is somehow not correct. @@ -72,3 +76,118 @@ def eim(self) -> pd.MultiIndex: this is a no-op (although the value of copy is respected). """ return self.ensure_is_multiindex() + + def update_levels( + self, + updates: dict[Any, Callable[[Any], Any]], + remove_unused_levels: bool = True, + ) -> pd.MultiIndex: + """ + Update the levels + + Parameters + ---------- + updates + Updates to apply + + Each key is the level to which the updates will be applied. + Each value is a function which updates the level to its new values. + + remove_unused_levels + Remove unused levels before applying the update + + Specifically, call + [pd.MultiIndex.remove_unused_levels][pandas.MultiIndex.remove_unused_levels]. + + This avoids trying to update levels that aren't being used. + + Returns + ------- + : + `index` with updates applied + """ + if not isinstance(self._index, pd.MultiIndex): + msg = ( + "This method is only intended to be used " + "when index is an instance of `MultiIndex`. " + f"Received {type(self._index)}" + ) + raise TypeError(msg) + + return update_levels( + self._index, + updates=updates, + remove_unused_levels=remove_unused_levels, + ) + + def update_levels_from_other( + self, + update_sources: dict[ + Any, + tuple[ + Any, + Callable[[Any], Any] | dict[Any, Any] | pd.Series[Any], + ] + | tuple[ + tuple[Any, ...], + Callable[[tuple[Any, ...]], Any] + | dict[tuple[Any, ...], Any] + | pd.Series[Any], + ], + ], + remove_unused_levels: bool = True, + ) -> pd.MultiIndex: + """ + Update levels based on other levels + + If the level to be updated doesn't exist, it is created. + + Parameters + ---------- + update_sources + Updates to apply and their source levels + + Each key is the level to which the updates will be applied + (or the level that will be created if it doesn't already exist). + + There are two options for the values. + + The first is used when only one level is used to update the 'target level'. + In this case, each value is a tuple of which the first element + is the level to use to generate the values (the 'source level') + and the second is mapper of the form used by + [pd.Index.map][pandas.Index.map] + which will be applied to the source level + to update/create the level of interest. + + Each value is a tuple of which the first element + is the level or levels (if a tuple) + to use to generate the values (the 'source level') + and the second is mapper of the form used by + [pd.Index.map][pandas.Index.map] + which will be applied to the source level + to update/create the level of interest. + + remove_unused_levels + Call `ini.remove_unused_levels` before updating the levels + + This avoids trying to update based on levels that aren't being used. + + Returns + ------- + : + `index` with updates applied + """ + if not isinstance(self._index, pd.MultiIndex): + msg = ( + "This method is only intended to be used " + "when index is an instance of `MultiIndex`. " + f"Received {type(self._index)}" + ) + raise TypeError(msg) + + return update_levels_from_other( + self._index, + update_sources=update_sources, + remove_unused_levels=remove_unused_levels, + ) diff --git a/src/pandas_openscm/accessors/series.py b/src/pandas_openscm/accessors/series.py index e2fba48..afe61f8 100644 --- a/src/pandas_openscm/accessors/series.py +++ b/src/pandas_openscm/accessors/series.py @@ -5,7 +5,7 @@ from __future__ import annotations from collections.abc import Collection, Mapping -from typing import TYPE_CHECKING, Any, Generic, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar import pandas as pd @@ -17,6 +17,8 @@ convert_index_to_category_index, ensure_index_is_multiindex, set_index_levels_func, + update_index_levels_from_other_func, + update_index_levels_func, ) from pandas_openscm.indexing import mi_loc from pandas_openscm.unit_conversion import convert_unit, convert_unit_like @@ -387,91 +389,101 @@ def to_category_index(self) -> S: # Figuring this out is a job for another day return res # type: ignore - # def update_index_levels( - # self, - # updates: dict[Any, Callable[[Any], Any]], - # copy: bool = True, - # remove_unused_levels: bool = True, - # ) -> pd.DataFrame: - # """ - # Update the index levels - # - # Parameters - # ---------- - # updates - # Updates to apply to the index levels - # - # Each key is the index level to which the updates will be applied. - # Each value is a function which updates the levels to their new values. - # - # copy - # Should the [pd.DataFrame][pandas.DataFrame] be copied before returning? - # - # remove_unused_levels - # Remove unused levels before applying the update - # - # Specifically, call - # [pd.MultiIndex.remove_unused_levels][pandas.MultiIndex.remove_unused_levels]. # noqa: E501 - # - # This avoids trying to update levels that aren't being used. - # - # Returns - # ------- - # : - # [pd.DataFrame][pandas.DataFrame] with updates applied to its index - # """ - # return update_index_levels_func( - # self._df, - # updates=updates, - # copy=copy, - # remove_unused_levels=remove_unused_levels, - # ) - # - # def update_index_levels_from_other( - # self, - # update_sources: dict[ - # Any, tuple[Any, Callable[[Any], Any] | dict[Any, Any] | pd.Series[Any]] - # ], - # copy: bool = True, - # remove_unused_levels: bool = True, - # ) -> pd.DataFrame: - # """ - # Update the index levels based on other index levels - # - # Parameters - # ---------- - # update_sources - # Updates to apply to `df`'s index - # - # Each key is the level to which the updates will be applied - # (or the level that will be created if it doesn't already exist). - # - # Each value is a tuple of which the first element - # is the level to use to generate the values (the 'source level') - # and the second is mapper of the form used by - # [pd.Index.map][pandas.Index.map] - # which will be applied to the source level - # to update/create the level of interest. - # - # copy - # Should the [pd.DataFrame][pandas.DataFrame] be copied before returning? - # - # remove_unused_levels - # Remove unused levels before applying the update - # - # Specifically, call - # [pd.MultiIndex.remove_unused_levels][pandas.MultiIndex.remove_unused_levels]. # noqa: E501 - # - # This avoids trying to update levels that aren't being used. - # - # Returns - # ------- - # : - # [pd.DataFrame][pandas.DataFrame] with updates applied to its index - # """ - # return update_index_levels_from_other_func( - # self._df, - # update_sources=update_sources, - # copy=copy, - # remove_unused_levels=remove_unused_levels, - # ) + def update_index_levels( + self, + updates: dict[Any, Callable[[Any], Any]], + copy: bool = True, + remove_unused_levels: bool = True, + ) -> S: + """ + Update the index levels + + Parameters + ---------- + updates + Updates to apply to the index levels + + Each key is the index level to which the updates will be applied. + Each value is a function which updates the levels to their new values. + + copy + Should the [pd.Series][pandas.Series] be copied before returning? + + remove_unused_levels + Remove unused levels before applying the update + + Specifically, call + [pd.MultiIndex.remove_unused_levels][pandas.MultiIndex.remove_unused_levels]. + + This avoids trying to update levels that aren't being used. + + Returns + ------- + : + [pd.Series][pandas.Series] with updates applied to its index + """ + res = update_index_levels_func( + self._series, + updates=updates, + copy=copy, + remove_unused_levels=remove_unused_levels, + ) + + # Ignore return type + # because I've done something wrong with how I've set this up. + # Figuring this out is a job for another day + return res # type: ignore + + def update_index_levels_from_other( + self, + update_sources: dict[ + Any, tuple[Any, Callable[[Any], Any] | dict[Any, Any] | pd.Series[Any]] + ], + copy: bool = True, + remove_unused_levels: bool = True, + ) -> S: + """ + Update the index levels based on other index levels + + Parameters + ---------- + update_sources + Updates to apply to the index levels + + Each key is the level to which the updates will be applied + (or the level that will be created if it doesn't already exist). + + Each value is a tuple of which the first element + is the level to use to generate the values (the 'source level') + and the second is mapper of the form used by + [pd.Index.map][pandas.Index.map] + which will be applied to the source level + to update/create the level of interest. + + copy + Should the [pd.Series][pandas.Series] be copied before returning? + + remove_unused_levels + Remove unused levels before applying the update + + Specifically, call + [pd.MultiIndex.remove_unused_levels][pandas.MultiIndex.remove_unused_levels]. + + This avoids trying to update levels that aren't being used. + + Returns + ------- + : + [pd.Series][pandas.Series] with updates applied to its index + """ + res = update_index_levels_from_other_func( + self._series, + update_sources=update_sources, + copy=copy, + remove_unused_levels=remove_unused_levels, + ) + + # Ignore return type + # because I've done something wrong with how I've set this up. + # Figuring this out is a job for another day + return res # type: ignore diff --git a/src/pandas_openscm/index_manipulation.py b/src/pandas_openscm/index_manipulation.py index ba8df15..bf7edaf 100644 --- a/src/pandas_openscm/index_manipulation.py +++ b/src/pandas_openscm/index_manipulation.py @@ -453,54 +453,54 @@ def create_new_level_and_codes_by_mapping_multiple( def update_index_levels_func( - df: pd.DataFrame, + pobj: P, updates: Mapping[Any, Callable[[Any], Any] | dict[Any, Any] | pd.Series[Any]], copy: bool = True, remove_unused_levels: bool = True, -) -> pd.DataFrame: +) -> P: """ - Update the index levels of a [pd.DataFrame][pandas.DataFrame] + Update the index levels of a [pandas][] object Parameters ---------- - df - [pd.DataFrame][pandas.DataFrame] to update + pobj + Supported [pandas][] object to update updates - Updates to apply to `df`'s index + Updates to apply to `pobj`'s index Each key is the index level to which the updates will be applied. Each value is a function which updates the levels to their new values. copy - Should `df` be copied before returning? + Should `pobj` be copied before returning? remove_unused_levels - Call `df.index.remove_unused_levels` before updating the levels + Call `pobj.index.remove_unused_levels` before updating the levels This avoids trying to update levels that aren't being used. Returns ------- : - `df` with updates applied to its index + `pobj` with updates applied to its index """ if copy: - df = df.copy() + pobj = pobj.copy() - if not isinstance(df.index, pd.MultiIndex): + if not isinstance(pobj.index, pd.MultiIndex): msg = ( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " - f"Received {type(df.index)=}" + "when `pobj`'s index is an instance of `MultiIndex`. " + f"Received {type(pobj.index)=}" ) raise TypeError(msg) - df.index = update_levels( - df.index, updates=updates, remove_unused_levels=remove_unused_levels + pobj.index = update_levels( + pobj.index, updates=updates, remove_unused_levels=remove_unused_levels ) - return df + return pobj def update_levels( @@ -609,7 +609,7 @@ def update_levels( def update_index_levels_from_other_func( - df: pd.DataFrame, + pobj: P, update_sources: dict[ Any, tuple[ @@ -625,20 +625,20 @@ def update_index_levels_from_other_func( ], copy: bool = True, remove_unused_levels: bool = True, -) -> pd.DataFrame: +) -> P: """ - Update the index levels based on other levels of a [pd.DataFrame][pandas.DataFrame] + Update the index levels based on other levels of a [pandas][] object If the level to be updated doesn't exist, it is created. Parameters ---------- - df - [pd.DataFrame][pandas.DataFrame] to update + pobj + Supported [pandas][] object to update update_sources - Updates to apply to `df`'s index + Updates to apply to `pobj`'s index Each key is the level to which the updates will be applied (or the level that will be created if it doesn't already exist). @@ -662,36 +662,36 @@ def update_index_levels_from_other_func( to update/create the level of interest. copy - Should `df` be copied before returning? + Should `pobj` be copied before returning? remove_unused_levels - Call `df.index.remove_unused_levels` before updating the levels + Call `pobj.index.remove_unused_levels` before updating the levels This avoids trying to update levels that aren't being used. Returns ------- : - `df` with updates applied to its index + `pobj` with updates applied to its index """ if copy: - df = df.copy() + pobj = pobj.copy() - if not isinstance(df.index, pd.MultiIndex): + if not isinstance(pobj.index, pd.MultiIndex): msg = ( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " - f"Received {type(df.index)=}" + "when `pobj`'s index is an instance of `MultiIndex`. " + f"Received {type(pobj.index)=}" ) raise TypeError(msg) - df.index = update_levels_from_other( - df.index, + pobj.index = update_levels_from_other( + pobj.index, update_sources=update_sources, remove_unused_levels=remove_unused_levels, ) - return df + return pobj def update_levels_from_other( @@ -749,7 +749,7 @@ def update_levels_from_other( remove_unused_levels Call `ini.remove_unused_levels` before updating the levels - This avoids trying to update bsaed on levels that aren't being used. + This avoids trying to update based on levels that aren't being used. Returns ------- @@ -1061,7 +1061,7 @@ def set_index_levels_func( Parameters ---------- pobj - [pd.DataFrame][pandas.DataFrame] to update + Supported [pandas][] object to update levels_to_set Mapping of level names to values to set @@ -1077,7 +1077,7 @@ def set_index_levels_func( if not isinstance(pobj.index, pd.MultiIndex): msg = ( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " + "when `pobj`'s index is an instance of `MultiIndex`. " f"Received {type(pobj.index)=}" ) raise TypeError(msg) diff --git a/src/pandas_openscm/indexing.py b/src/pandas_openscm/indexing.py index 7d6645a..a2d8b81 100644 --- a/src/pandas_openscm/indexing.py +++ b/src/pandas_openscm/indexing.py @@ -176,7 +176,7 @@ def multi_index_lookup(pandas_obj: P, locator: pd.MultiIndex) -> P: if not isinstance(pandas_obj.index, pd.MultiIndex): msg = ( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " + "when `pandas_obj`'s index is an instance of `MultiIndex`. " f"Received {type(pandas_obj.index)=}" ) raise TypeError(msg) @@ -280,7 +280,7 @@ def index_name_aware_lookup(pandas_obj: P, locator: pd.Index[Any]) -> P: if not isinstance(pandas_obj.index, pd.MultiIndex): msg = ( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " + "when `pandas_obj`'s index is an instance of `MultiIndex`. " f"Received {type(pandas_obj.index)=}" ) raise TypeError(msg) diff --git a/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels.py b/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels.py index 3e98f23..1c22ba5 100644 --- a/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels.py +++ b/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels.py @@ -11,6 +11,15 @@ import pytest from pandas_openscm.index_manipulation import update_index_levels_func, update_levels +from pandas_openscm.testing import check_result, convert_to_desired_type + +pobj_type = pytest.mark.parametrize( + "pobj_type", + ("DataFrame", "Series"), +) +""" +Parameterisation to use to check handling of both DataFrame and Series +""" @pytest.mark.parametrize( @@ -123,6 +132,50 @@ def test_update_levels_missing_level(): update_levels(start, updates=updates) +def test_accessor_index(setup_pandas_accessors): + start = pd.MultiIndex.from_tuples( + [ + ("sa", "va", "kg", 0), + ("sb", "vb", "m", -1), + ("sa", "va", "kg", -2), + ("sa", "vb", "kg", 2), + ], + names=["scenario", "variable", "unit", "run_id"], + ) + updates = { + "variable": lambda x: x.replace("v", "vv"), + "unit": lambda x: x.replace("kg", "g").replace("m", "km"), + } + + res = start.openscm.update_levels(updates=updates) + + exp = pd.MultiIndex.from_tuples( + [ + ("sa", "vva", "g", 0), + ("sb", "vvb", "km", -1), + ("sa", "vva", "g", -2), + ("sa", "vvb", "g", 2), + ], + names=["scenario", "variable", "unit", "run_id"], + ) + + pd.testing.assert_index_equal(res, exp) + + +def test_accessor_index_not_multiindex(setup_pandas_accessors): + start = pd.Index([1, 2, 3]) + + with pytest.raises( + TypeError, + match=re.escape( + "This method is only intended to be used " + "when index is an instance of `MultiIndex`. " + ) + + "Received .*Index.*'", + ): + start.openscm.update_levels(updates={}) + + def test_doesnt_trip_over_droped_levels(setup_pandas_accessors): def update_func(in_v: int) -> int: if in_v < 0: @@ -190,8 +243,34 @@ def update_func(in_v: int) -> int: updates, remove_unused_levels=False ) + # Same thing but from a Series + start_series = start_df[2020] + + res_series = update_index_levels_func(start_series.iloc[:-1], updates=updates) + + exp_series = pd.Series(np.zeros(exp.shape[0]), name=2020, index=exp) + + pd.testing.assert_series_equal(res_series, exp_series) + with exp_error_no_removal: + update_index_levels_func( + start_series.iloc[:-1], + updates=updates, + remove_unused_levels=False, + ) + + # Lastly, test the accessor + pd.testing.assert_series_equal( + start_series.iloc[:-1].openscm.update_index_levels(updates), + exp_series, + ) + with exp_error_no_removal: + start_series.iloc[:-1].openscm.update_index_levels( + updates, remove_unused_levels=False + ) + -def test_accessor(setup_pandas_accessors): +@pobj_type +def test_accessor(setup_pandas_accessors, pobj_type): start = pd.DataFrame( np.arange(2 * 4).reshape((4, 2)), columns=[2010, 2020], @@ -225,21 +304,26 @@ def test_accessor(setup_pandas_accessors): ), ) + start = convert_to_desired_type(start, pobj_type) + exp = convert_to_desired_type(exp, pobj_type) + res = start.openscm.update_index_levels(updates) - pd.testing.assert_frame_equal(res, exp) + check_result(res, exp) # Test function too res = update_index_levels_func(start, updates) - pd.testing.assert_frame_equal(res, exp) + check_result(res, exp) -def test_accessor_not_multiindex(setup_pandas_accessors): +@pobj_type +def test_accessor_not_multiindex(setup_pandas_accessors, pobj_type): start = pd.DataFrame(np.arange(2 * 4).reshape((4, 2))) + start = convert_to_desired_type(start, pobj_type) error_msg = re.escape( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " - "Received type(df.index)=" + "when `pobj`'s index is an instance of `MultiIndex`. " + "Received type(pobj.index)=" ) with pytest.raises(TypeError, match=error_msg): start.openscm.update_index_levels({}) diff --git a/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels_from_other.py b/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels_from_other.py index ff82f05..d48f0af 100644 --- a/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels_from_other.py +++ b/tests/integration/index_manipulation/test_integration_index_manipulation_update_levels_from_other.py @@ -14,6 +14,15 @@ update_index_levels_from_other_func, update_levels_from_other, ) +from pandas_openscm.testing import check_result, convert_to_desired_type + +pobj_type = pytest.mark.parametrize( + "pobj_type", + ("DataFrame", "Series"), +) +""" +Parameterisation to use to check handling of both DataFrame and Series +""" @pytest.mark.parametrize( @@ -273,7 +282,51 @@ def test_update_levels_from_other_missing_levels(sources, exp): update_levels_from_other(start, update_sources=update_sources) -def test_doesnt_trip_over_droped_levels(setup_pandas_accessors): +def test_accessor_index(setup_pandas_accessors): + start = pd.MultiIndex.from_tuples( + [ + ("sa", "va", "kg", 0), + ("sb", "vb", "m", -1), + ("sa", "va", "kg", -2), + ("sa", "vb", "kg", 2), + ], + names=["scenario", "variable", "unit", "run_id"], + ) + update_sources = { + "vv": ("variable", lambda x: x.replace("v", "vv")), + "uu": ("unit", lambda x: x.replace("kg", "g").replace("m", "km")), + } + + res = start.openscm.update_levels_from_other(update_sources=update_sources) + + exp = pd.MultiIndex.from_tuples( + [ + ("sa", "va", "kg", 0, "vva", "g"), + ("sb", "vb", "m", -1, "vvb", "km"), + ("sa", "va", "kg", -2, "vva", "g"), + ("sa", "vb", "kg", 2, "vvb", "g"), + ], + names=["scenario", "variable", "unit", "run_id", "vv", "uu"], + ) + + pd.testing.assert_index_equal(res, exp) + + +def test_accessor_index_not_multiindex(setup_pandas_accessors): + start = pd.Index([1, 2, 3]) + + with pytest.raises( + TypeError, + match=re.escape( + "This method is only intended to be used " + "when index is an instance of `MultiIndex`. " + ) + + "Received .*Index.*'", + ): + start.openscm.update_levels_from_other(update_sources={}) + + +def test_doesnt_trip_over_dropped_levels(setup_pandas_accessors): def update_func(in_v: int) -> int: if in_v < 0: msg = f"Value must be greater than zero, received {in_v}" @@ -347,8 +400,36 @@ def update_func(in_v: int) -> int: update_sources, remove_unused_levels=False ) + # Same thing but from a Series + start_series = start_df[2020] + + res_series = update_index_levels_from_other_func( + start_series.iloc[:-1], update_sources=update_sources + ) + + exp_series = pd.Series(np.zeros(exp.shape[0]), name=2020, index=exp) -def test_accessor(setup_pandas_accessors): + pd.testing.assert_series_equal(res_series, exp_series) + with exp_error_no_removal: + update_index_levels_from_other_func( + start_series.iloc[:-1], + update_sources=update_sources, + remove_unused_levels=False, + ) + + # Lastly, test the accessor + pd.testing.assert_series_equal( + start_series.iloc[:-1].openscm.update_index_levels_from_other(update_sources), + exp_series, + ) + with exp_error_no_removal: + start_series.iloc[:-1].openscm.update_index_levels_from_other( + update_sources, remove_unused_levels=False + ) + + +@pobj_type +def test_accessor(setup_pandas_accessors, pobj_type): start = pd.DataFrame( np.arange(2 * 4).reshape((4, 2)), columns=[2010, 2020], @@ -409,21 +490,26 @@ def test_accessor(setup_pandas_accessors): ), ) + start = convert_to_desired_type(start, pobj_type) + exp = convert_to_desired_type(exp, pobj_type) + res = start.openscm.update_index_levels_from_other(update_sources) - pd.testing.assert_frame_equal(res, exp) + check_result(res, exp) # Test function too res = update_index_levels_from_other_func(start, update_sources) - pd.testing.assert_frame_equal(res, exp) + check_result(res, exp) -def test_accessor_not_multiindex(setup_pandas_accessors): +@pobj_type +def test_accessor_not_multiindex(setup_pandas_accessors, pobj_type): start = pd.DataFrame(np.arange(2 * 4).reshape((4, 2))) + start = convert_to_desired_type(start, pobj_type) error_msg = re.escape( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " - "Received type(df.index)=" + "when `pobj`'s index is an instance of `MultiIndex`. " + "Received type(pobj.index)=" ) with pytest.raises(TypeError, match=error_msg): start.openscm.update_index_levels_from_other({}) diff --git a/tests/unit/test_indexing.py b/tests/unit/test_indexing.py index 4b1ae3e..de6a49b 100644 --- a/tests/unit/test_indexing.py +++ b/tests/unit/test_indexing.py @@ -48,7 +48,7 @@ def test_index_is_not_multi_index(): TypeError, match=re.escape( "This function is only intended to be used " - "when `df`'s index is an instance of `MultiIndex`. " + "when `pandas_obj`'s index is an instance of `MultiIndex`. " "Received type(pandas_obj.index)=