Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Pandas 2 #131

Merged
merged 13 commits into from
Jun 13, 2023
10 changes: 5 additions & 5 deletions .github/workflows/lint-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
runs-on: ubuntu-latest
name: Check (on Python3.9)
steps:
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: 3.9
- uses: actions/checkout@v2
- uses: pre-commit/action@v2.0.0
- uses: actions/checkout@v3
- uses: pre-commit/action@v3.0.0

test:
needs: check
Expand All @@ -26,10 +26,10 @@ jobs:
skip-viz: true
name: "Test (on Python ${{ matrix.py_version }})"
steps:
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.py_version }}
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Run main tests
run: make test
- name: Run forecast tests
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/PyCQA/isort
rev: 5.11.4 # New version tags can be found here: https://github.com/PyCQA/isort/tags
rev: 5.12.0 # New version tags can be found here: https://github.com/PyCQA/isort/tags
hooks:
- id: isort
name: isort (import sorting)
Expand Down
12 changes: 9 additions & 3 deletions timely_beliefs/beliefs/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
Union,
)

from packaging import version

if TYPE_CHECKING:
import altair as alt
from sktime.forecasting.base import BaseForecaster
Expand Down Expand Up @@ -662,8 +664,12 @@ class BeliefsSeries(pd.Series):
@property
def _constructor(self):
def f(*args, **kwargs):
"""Call __finalize__() after construction to inherit metadata."""
return BeliefsSeries(*args, **kwargs).__finalize__(self, method="inherit")
"""Pre-Pandas 2.0, call __finalize__() after construction to inherit metadata."""
if version.parse(pd.__version__) < version.parse("2.0.0"):
return BeliefsSeries(*args, **kwargs).__finalize__(
self, method="inherit"
)
return BeliefsSeries(*args, **kwargs)

return f

Expand All @@ -686,7 +692,7 @@ def __finalize__(self, other, method=None, **kwargs):
for name in self._metadata:
object.__setattr__(self, name, getattr(other, name, None))
if hasattr(other, "name"):
self.name = other.name
object.__setattr__(self, "name", getattr(other, "name"))
return self

def __init__(self, *args, **kwargs):
Expand Down
22 changes: 17 additions & 5 deletions timely_beliefs/tests/test_belief_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,26 +521,38 @@ def _test_agg_resampling_retains_metadata(resolution):
) # todo: the event_resolution metadata is only updated when resampling using df.resample_events(). A reason to override the original resample method, or otherwise something to document.


def test_groupby_retains_metadata():
@pytest.mark.parametrize(
"test_df",
[
"example_df",
"empty_df",
],
)
def test_groupby_retains_metadata(test_df):
"""Test whether grouping by index level retains the metadata.

Succeeds with pandas==1.0.0
Fails with pandas==1.1.0
Fixed with pandas==1.1.5
Fails with pandas==1.3.0
"""
example_df = get_example_df()
df = example_df
if test_df == "example_df":
original_df = get_example_df()
elif test_df == "empty_df":
original_df = tb.BeliefsDataFrame(sensor=tb.Sensor(name="test", unit="W"))
else:
raise NotImplementedError
df = original_df.copy()

def assert_function(x):
print(x)
assert_metadata_is_retained(x, original_df=example_df)
assert_metadata_is_retained(x, original_df=original_df)
return x

df = df.groupby(level="event_start", group_keys=False).apply(
lambda x: assert_function(x)
)
assert_metadata_is_retained(df, original_df=example_df)
assert_metadata_is_retained(df, original_df=original_df)


def test_copy_series_retains_name_and_metadata():
Expand Down
12 changes: 12 additions & 0 deletions timely_beliefs/tests/test_belief_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pandas as pd
import pytest

from timely_beliefs import BeliefsDataFrame, Sensor
from timely_beliefs.beliefs.probabilistic_utils import get_median_belief
from timely_beliefs.beliefs.utils import (
propagate_beliefs,
Expand All @@ -11,6 +12,17 @@
from timely_beliefs.tests.utils import equal_lists


def test_propagate_metadata_on_empty_frame():
"""Check that calling these functions, which use groupby().apply(), on an empty frame retains the frame's metadata."""
df = BeliefsDataFrame(sensor=Sensor("test", unit="kW"))
df = df.for_each_belief(get_median_belief)
Flix6x marked this conversation as resolved.
Show resolved Hide resolved
assert df.sensor.name == "test"
assert df.sensor.unit == "kW"
df = df.groupby(level=["event_start"], group_keys=False).apply(lambda x: x.head(1))
assert df.sensor.name == "test"
assert df.sensor.unit == "kW"


def test_propagate_multi_sourced_deterministic_beliefs():
# Start with a deterministic example frame (4 events, 2 sources and 2 belief times)
df = get_example_df().for_each_belief(get_median_belief)
Expand Down
1 change: 1 addition & 0 deletions timely_beliefs/tests/test_ignore_36__belief_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
("1M", ValueError, "not parse"),
("1Y", ValueError, "not parse"),
("1y", ValueError, "not parse"),
("y", ValueError, "not parse"),
],
)
def test_ambiguous_timedelta_parsing(td, ErrorType, match):
Expand Down
3 changes: 3 additions & 0 deletions timely_beliefs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ def parse_timedelta_like(
# catch cases like "H" -> "1H"
if "unit abbreviation w/o a number" in str(e):
td = pd.Timedelta(f"1{td}")
else:
# Reraise needed since pandas==2.0.0 throws a ValueError for ambiguous timedelta, rather than a FutureWarning
raise e
if isinstance(td, pd.Timedelta):
td = td.to_pytimedelta()
except (ValueError, FutureWarning) as e:
Expand Down