Skip to content
Merged
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
4 changes: 4 additions & 0 deletions changelog/24.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- Renamed pandas_openscm.register_pandas_accessor to [pandas_openscm.register_pandas_accessors][] (with a trailing 's') as accessors are now also registered for [pandas Series][pandas.Series]
- Renamed pandas_openscm.accessors.DataFramePandasOpenSCMAccessor to [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor][]
- Renamed `df_unit_level` to `unit_level` in [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.convert_unit_like][]
- Renamed `df` to `pobj` in [pandas_openscm.index_manipulation.set_index_levels_func][], [pandas_openscm.unit_conversion.convert_unit_from_target_series][], [pandas_openscm.unit_conversion.convert_unit][] and [pandas_openscm.unit_conversion.convert_unit_like][]
1 change: 1 addition & 0 deletions changelog/24.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added some accessors for [pandas Series][pandas.Series] via [pandas_openscm.accessors.PandasSeriesOpenSCMAccessor][]. Note that this is not feature complete yet, tracking in [#25](https://github.com/openscm/pandas-openscm/issues/25)
1 change: 1 addition & 0 deletions changelog/24.improvement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[pandas_openscm.index_manipulation.set_index_levels_func][], [pandas_openscm.unit_conversion.convert_unit_from_target_series][], [pandas_openscm.unit_conversion.convert_unit][] and [pandas_openscm.unit_conversion.convert_unit_like][] now explicitly support [pd.Series][pandas.Series]
1 change: 1 addition & 0 deletions changelog/24.trivial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added [pandas_openscm.testing.convert_to_desired_type] and [pandas_openscm.testing.check_result] to help with testing support for [pd.DataFrame][pandas.DataFrame] and [pd.Series][pandas.Series]
14 changes: 7 additions & 7 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ from the examples given in that link.

### 🆕 Features

- - Added unit conversion APIs: [pandas_openscm.unit_conversion.convert_unit] and [pandas_openscm.unit_conversion.convert_unit_like] and the corresponding accessors [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.convert_unit] and [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.convert_unit_like]
- Added the helper: [pandas_openscm.index_manipulation.ensure_is_multiindex] and the corresponding accessors [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.ensure_index_is_multiindex] and [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.eiim]
- - Added unit conversion APIs: [pandas_openscm.unit_conversion.convert_unit] and [pandas_openscm.unit_conversion.convert_unit_like] and the corresponding accessors [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.convert_unit] and [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.convert_unit_like]
- Added the helper: [pandas_openscm.index_manipulation.ensure_is_multiindex] and the corresponding accessors [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.ensure_index_is_multiindex] and [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.eiim]

([#23](https://github.com/openscm/pandas-openscm/pull/23))

Expand All @@ -46,7 +46,7 @@ from the examples given in that link.

### 🆕 Features

- Added [pandas_openscm.index_manipulation.set_levels][] and the corresponding accessor [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.set_index_levels][] ([#18](https://github.com/openscm/pandas-openscm/pull/18))
- Added [pandas_openscm.index_manipulation.set_levels][] and the corresponding accessor [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.set_index_levels][] ([#18](https://github.com/openscm/pandas-openscm/pull/18))


## Pandas-OpenSCM v0.5.0 (2025-05-10)
Expand Down Expand Up @@ -84,7 +84,7 @@ from the examples given in that link.
### 🆕 Features

- Add compare_close function to compare two dataframes. ([#16](https://github.com/openscm/pandas-openscm/pull/16))
- Added [pandas_openscm.index_manipulation.update_levels_from_other][] and the corresponding accessor [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.update_index_levels_from_other][] ([#17](https://github.com/openscm/pandas-openscm/pull/17))
- Added [pandas_openscm.index_manipulation.update_levels_from_other][] and the corresponding accessor [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.update_index_levels_from_other][] ([#17](https://github.com/openscm/pandas-openscm/pull/17))


## Pandas-OpenSCM v0.4.1 (2025-04-12)
Expand All @@ -93,21 +93,21 @@ from the examples given in that link.

- Fixed up [pandas_openscm.index_manipulation.update_levels][].
It now drops unused levels by default first, to avoid applying the updates to values that aren't being used.
The same fixes are propagated to [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.update_index_levels][] and [pandas_openscm.index_manipulation.update_index_levels_func][]. ([#14](https://github.com/openscm/pandas-openscm/pull/14))
The same fixes are propagated to [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.update_index_levels][] and [pandas_openscm.index_manipulation.update_index_levels_func][]. ([#14](https://github.com/openscm/pandas-openscm/pull/14))


## Pandas-OpenSCM v0.4.0 (2025-04-11)

### 🆕 Features

- Added [pandas_openscm.index_manipulation.update_levels][] and the corresponding accessor [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.update_index_levels][] ([#13](https://github.com/openscm/pandas-openscm/pull/13))
- Added [pandas_openscm.index_manipulation.update_levels][] and the corresponding accessor [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.update_index_levels][] ([#13](https://github.com/openscm/pandas-openscm/pull/13))


## Pandas-OpenSCM v0.3.3 (2025-03-30)

### 🆕 Features

- - Added a method for converting to long data, see [pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.to_long_data][pandas_openscm.accessors.DataFramePandasOpenSCMAccessor.to_long_data] ([#12](https://github.com/openscm/pandas-openscm/pull/12))
- - Added a method for converting to long data, see [pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.to_long_data][pandas_openscm.accessors.PandasDataFrameOpenSCMAccessor.to_long_data] ([#12](https://github.com/openscm/pandas-openscm/pull/12))


## Pandas-OpenSCM v0.3.2 (2025-03-27)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
if GIT_REPO.is_dirty():
COMMIT = f"{COMMIT}-dirty"

pandas_openscm.register_pandas_accessor()
pandas_openscm.register_pandas_accessors()


@define
Expand Down
4 changes: 2 additions & 2 deletions docs/how-to-guides/how-to-make-a-plume-plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import numpy as np
import openscm_units

from pandas_openscm import register_pandas_accessor
from pandas_openscm import register_pandas_accessors
from pandas_openscm.plotting import PlumePlotter
from pandas_openscm.testing import create_test_df

Expand All @@ -40,7 +40,7 @@
# Register the openscm accessor for pandas objects
# (we don't do this on import
# as we have had bad experiences with implicit behaviour like that)
register_pandas_accessor()
register_pandas_accessors()

# %% [markdown]
# ## Basics
Expand Down
22 changes: 15 additions & 7 deletions docs/pandas-accessors.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,43 @@ The accessors must be registered before they can be used
(we do this to avoid imports of any of our modules having side effects,
which is a pattern we have had bad experiences with in the past).
This is done with
[register_pandas_accessor][pandas_openscm.accessors.register_pandas_accessor],
[register_pandas_accessors][pandas_openscm.accessors.register_pandas_accessors],

By default, the accessors are provided under the "openscm" namespace
and this is how the accessors are documented below.
However, the namespace can be customised when using
[register_pandas_accessor][pandas_openscm.accessors.register_pandas_accessor],
[register_pandas_accessors][pandas_openscm.accessors.register_pandas_accessors],
should you wish to use a different namespace for the accessor.

For the avoidance of doubt, in order to register/activate the accessors,
you will need to run something like:

```python
from pandas_openscm.accessors import register_pandas_accessor
from pandas_openscm.accessors import register_pandas_accessors

# The 'pd.DataFrame.openscm' namespace will not be available at this point.
# The 'pd.DataFrame.openscm' and 'pd.Series.openscm' namespace
# will not be available at this point.

# Register the accessors
register_pandas_accessor()
register_pandas_accessors()

# The 'pd.DataFrame.openscm' namespace
# The 'pd.DataFrame.openscm' and 'pd.Series.openscm' namespace
# (or whatever other custom namespace you chose to register)
# will now be available.
```

The full accessor API is documented below.

::: pandas_openscm.accessors.DataFramePandasOpenSCMAccessor
::: pandas_openscm.accessors.dataframe.PandasDataFrameOpenSCMAccessor
handler: python_accessors
options:
namespace: "pd.DataFrame.openscm"
show_root_full_path: false
show_root_heading: true

::: pandas_openscm.accessors.series.PandasSeriesOpenSCMAccessor
handler: python_accessors
options:
namespace: "pd.Series.openscm"
show_root_full_path: false
show_root_heading: true
4 changes: 2 additions & 2 deletions docs/tutorials/unit-conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import pandas_indexing as pix
import pint

from pandas_openscm import register_pandas_accessor
from pandas_openscm import register_pandas_accessors
from pandas_openscm.testing import create_test_df
from pandas_openscm.unit_conversion import (
AmbiguousTargetUnitError,
Expand All @@ -45,7 +45,7 @@
# Register the openscm accessor for pandas objects
# (we don't do this on import
# as we have had bad experiences with implicit behaviour like that)
register_pandas_accessor()
register_pandas_accessors()

# %% [markdown]
# ## Basics
Expand Down
4 changes: 2 additions & 2 deletions src/pandas_openscm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import importlib.metadata

from pandas_openscm.accessors import register_pandas_accessor
from pandas_openscm.accessors import register_pandas_accessors

__version__ = importlib.metadata.version("pandas_openscm")

__all__ = ["register_pandas_accessor"]
__all__ = ["register_pandas_accessors"]
76 changes: 76 additions & 0 deletions src/pandas_openscm/accessors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
API for [pandas][] accessors.

Accessors for [pd.DataFrame][pandas.DataFrame]'s,
[pd.Series][pandas.Series]'s
and [pd.Index][pandas.Index]'s.

**Notes for developers**

We try and keep the accessors as a super-thin layer.
This makes it easier to re-use functionality in a functional way,
which is beneficial
(particularly if we one day need to switch to
a different kind of dataframe e.g. dask).

As a result, we effectively duplicate our API in the accessor layer.
This is ok for now, because this repo is not so big.
Pandas and pandas-indexing use pandas' `pandas.util._decorators.docs` decorator
(see https://github.com/pandas-dev/pandas/blob/05de25381f71657bd425d2c4045d81a46b2d3740/pandas/util/_decorators.py#L342)
to avoid duplicating the docs.
We could use the same pattern, but I have found that this magic
almost always goes wrong so I would stay away from this as long as we can.

We would like to move to a less error-prone, less manual solution.
We tried using mix-ins, but this is just a yuck pattern
that makes it really hard to see where functionality comes from
(a common issue with inheritance)
and makes the type hinting hard.
As a result, we aren't using it.

Probably the next thing to try is auto-generating the code from some template.
This is basically the same idea as using a macro in C.
It likely wouldn't be that hard, and would be much more robust.
"""

from __future__ import annotations

import pandas as pd

from pandas_openscm.accessors.dataframe import PandasDataFrameOpenSCMAccessor
from pandas_openscm.accessors.series import PandasSeriesOpenSCMAccessor


def register_pandas_accessors(namespace: str = "openscm") -> None:
"""
Register the pandas accessors

This registers accessors
for [DataFrame][pandas.DataFrame]'s, [Series][pandas.Series]'s
and [Index][pandas.Index]'s.
If you only want to register accessors for one of these,
we leave it up to you to copy the line(s) you need.

For details of how these accessors work, see
[pandas' docs](https://pandas.pydata.org/docs/development/extending.html#registering-custom-accessors).

We provide this as a separate function
because we have had really bad experiences with imports having side effects
(which seems to be the more normal pattern)
and don't want to pass those bad experiences on.

Parameters
----------
namespace
Namespace to use for the accessor

E.g. if namespace is 'custom'
then the pandas-openscm API will be available under
`pd.DataFrame.custom.pandas_openscm_function`
e.g. `pd.DataFrame.custom.convert_unit`.
"""
pd.api.extensions.register_dataframe_accessor(namespace)(
PandasDataFrameOpenSCMAccessor
)
pd.api.extensions.register_series_accessor(namespace)(PandasSeriesOpenSCMAccessor)
# pd.api.extensions.register_index_accessor(namespace)(PandasIndexOpenSCMAccessor)
Loading
Loading