Skip to content

Commit 3749ca7

Browse files
Support of pandas.Series (#378)
Co-authored-by: Auguste Baum <[email protected]>
1 parent 1b9b1ad commit 3749ca7

File tree

8 files changed

+147
-20
lines changed

8 files changed

+147
-20
lines changed

src/skore/item/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from skore.item.media_item import MediaItem
1111
from skore.item.numpy_array_item import NumpyArrayItem
1212
from skore.item.pandas_dataframe_item import PandasDataFrameItem
13+
from skore.item.pandas_series_item import PandasSeriesItem
1314
from skore.item.primitive_item import PrimitiveItem
1415
from skore.item.sklearn_base_estimator_item import SklearnBaseEstimatorItem
1516

@@ -19,6 +20,7 @@ def object_to_item(object: Any) -> Item:
1920
for cls in (
2021
PrimitiveItem,
2122
PandasDataFrameItem,
23+
PandasSeriesItem,
2224
NumpyArrayItem,
2325
SklearnBaseEstimatorItem,
2426
MediaItem,
@@ -42,6 +44,7 @@ def object_to_item(object: Any) -> Item:
4244
"MediaItem",
4345
"NumpyArrayItem",
4446
"PandasDataFrameItem",
47+
"PandasSeriesItem",
4548
"PrimitiveItem",
4649
"SklearnBaseEstimatorItem",
4750
"object_to_item",

src/skore/item/item_repository.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from skore.item.media_item import MediaItem
1717
from skore.item.numpy_array_item import NumpyArrayItem
1818
from skore.item.pandas_dataframe_item import PandasDataFrameItem
19+
from skore.item.pandas_series_item import PandasSeriesItem
1920
from skore.item.primitive_item import PrimitiveItem
2021
from skore.item.sklearn_base_estimator_item import SklearnBaseEstimatorItem
2122

@@ -31,6 +32,7 @@ class ItemRepository:
3132
"MediaItem": MediaItem,
3233
"NumpyArrayItem": NumpyArrayItem,
3334
"PandasDataFrameItem": PandasDataFrameItem,
35+
"PandasSeriesItem": PandasSeriesItem,
3436
"PrimitiveItem": PrimitiveItem,
3537
"SklearnBaseEstimatorItem": SklearnBaseEstimatorItem,
3638
}

src/skore/item/pandas_dataframe_item.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,7 @@ def __init__(
4747

4848
@cached_property
4949
def dataframe(self) -> pandas.DataFrame:
50-
"""
51-
Convert the stored dictionary to a pandas DataFrame.
52-
53-
Returns
54-
-------
55-
pd.DataFrame
56-
The pandas DataFrame representation of the stored dictionary.
57-
"""
50+
"""The pandas DataFrame."""
5851
import pandas
5952

6053
return pandas.DataFrame.from_dict(self.dataframe_dict, orient="tight")
@@ -74,9 +67,9 @@ def factory(cls, dataframe: pandas.DataFrame) -> PandasDataFrameItem:
7467
PandasDataFrameItem
7568
A new PandasDataFrameItem instance.
7669
"""
77-
import pandas.core.frame
70+
import pandas
7871

79-
if not isinstance(dataframe, pandas.core.frame.DataFrame):
72+
if not isinstance(dataframe, pandas.DataFrame):
8073
raise TypeError(f"Type '{dataframe.__class__}' is not supported.")
8174

8275
instance = cls(dataframe_dict=dataframe.to_dict(orient="tight"))
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""PandasSeriesItem.
2+
3+
This module defines the PandasSeriesItem class,
4+
which represents a pandas Series item.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from functools import cached_property
10+
from typing import TYPE_CHECKING
11+
12+
if TYPE_CHECKING:
13+
import pandas
14+
15+
from skore.item.item import Item
16+
17+
18+
class PandasSeriesItem(Item):
19+
"""
20+
A class to represent a pandas Series item.
21+
22+
This class encapsulates a pandas Series along with its
23+
creation and update timestamps.
24+
"""
25+
26+
def __init__(
27+
self,
28+
series_list: list,
29+
created_at: str | None = None,
30+
updated_at: str | None = None,
31+
):
32+
"""
33+
Initialize a PandasSeriesItem.
34+
35+
Parameters
36+
----------
37+
series_list : list
38+
The list representation of the series.
39+
created_at : str
40+
The creation timestamp in ISO format.
41+
updated_at : str
42+
The last update timestamp in ISO format.
43+
"""
44+
super().__init__(created_at, updated_at)
45+
46+
self.series_list = series_list
47+
48+
@cached_property
49+
def series(self) -> pandas.Series:
50+
"""The pandas Series."""
51+
import pandas
52+
53+
return pandas.Series(self.series_list)
54+
55+
@classmethod
56+
def factory(cls, series: pandas.Series) -> PandasSeriesItem:
57+
"""
58+
Create a new PandasSeriesItem instance from a pandas Series.
59+
60+
Parameters
61+
----------
62+
series : pd.Series
63+
The pandas Series to store.
64+
65+
Returns
66+
-------
67+
PandasSeriesItem
68+
A new PandasSeriesItem instance.
69+
"""
70+
import pandas
71+
72+
if not isinstance(series, pandas.Series):
73+
raise TypeError(f"Type '{series.__class__}' is not supported.")
74+
75+
instance = cls(series_list=series.to_list())
76+
77+
# add series as cached property
78+
instance.series = series
79+
80+
return instance

src/skore/project.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
MediaItem,
1212
NumpyArrayItem,
1313
PandasDataFrameItem,
14+
PandasSeriesItem,
1415
PrimitiveItem,
1516
SklearnBaseEstimatorItem,
1617
object_to_item,
@@ -120,6 +121,8 @@ def get(self, key: str) -> Any:
120121
return item.array
121122
elif isinstance(item, PandasDataFrameItem):
122123
return item.dataframe
124+
elif isinstance(item, PandasSeriesItem):
125+
return item.series
123126
elif isinstance(item, SklearnBaseEstimatorItem):
124127
return item.estimator
125128
elif isinstance(item, MediaItem):

src/skore/ui/report.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from skore.item.media_item import MediaItem
1313
from skore.item.numpy_array_item import NumpyArrayItem
1414
from skore.item.pandas_dataframe_item import PandasDataFrameItem
15+
from skore.item.pandas_series_item import PandasSeriesItem
1516
from skore.item.primitive_item import PrimitiveItem
1617
from skore.item.sklearn_base_estimator_item import SklearnBaseEstimatorItem
1718
from skore.project import Project
@@ -55,6 +56,9 @@ def __serialize_project(project: Project) -> SerializedProject:
5556
elif isinstance(item, PandasDataFrameItem):
5657
value = item.dataframe_dict
5758
media_type = "application/vnd.dataframe+json"
59+
elif isinstance(item, PandasSeriesItem):
60+
value = item.series_list
61+
media_type = "text/markdown"
5862
elif isinstance(item, SklearnBaseEstimatorItem):
5963
value = item.estimator_html_repr
6064
media_type = "application/vnd.sklearn.estimator+html"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from pandas import Series
3+
from pandas.testing import assert_series_equal
4+
from skore.item import PandasSeriesItem
5+
6+
7+
class TestPandasSeriesItem:
8+
@pytest.fixture(autouse=True)
9+
def monkeypatch_datetime(self, monkeypatch, MockDatetime):
10+
monkeypatch.setattr("skore.item.item.datetime", MockDatetime)
11+
12+
@pytest.mark.order(0)
13+
def test_factory(self, mock_nowstr):
14+
series = Series([0, 1, 2])
15+
series_list = series.to_list()
16+
17+
item = PandasSeriesItem.factory(series)
18+
19+
assert item.series_list == series_list
20+
assert item.created_at == mock_nowstr
21+
assert item.updated_at == mock_nowstr
22+
23+
@pytest.mark.order(1)
24+
def test_series(self, mock_nowstr):
25+
series = Series([0, 1, 2])
26+
series_list = series.to_list()
27+
28+
item1 = PandasSeriesItem.factory(series)
29+
item2 = PandasSeriesItem(
30+
series_list=series_list,
31+
created_at=mock_nowstr,
32+
updated_at=mock_nowstr,
33+
)
34+
35+
assert_series_equal(item1.series, series)
36+
assert_series_equal(item2.series, series)

tests/unit/test_project.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,39 +26,45 @@ def project():
2626

2727

2828
def test_put_string_item(project):
29-
project.put("string_item", "Hello, World!") # JSONItem
29+
project.put("string_item", "Hello, World!")
3030
assert project.get("string_item") == "Hello, World!"
3131

3232

3333
def test_put_int_item(project):
34-
project.put("int_item", 42) # JSONItem
34+
project.put("int_item", 42)
3535
assert project.get("int_item") == 42
3636

3737

3838
def test_put_float_item(project):
39-
project.put("float_item", 3.14) # JSONItem
39+
project.put("float_item", 3.14)
4040
assert project.get("float_item") == 3.14
4141

4242

4343
def test_put_bool_item(project):
44-
project.put("bool_item", True) # JSONItem
44+
project.put("bool_item", True)
4545
assert project.get("bool_item") is True
4646

4747

4848
def test_put_list_item(project):
49-
project.put("list_item", [1, 2, 3]) # JSONItem
49+
project.put("list_item", [1, 2, 3])
5050
assert project.get("list_item") == [1, 2, 3]
5151

5252

5353
def test_put_dict_item(project):
54-
project.put("dict_item", {"key": "value"}) # JSONItem
54+
project.put("dict_item", {"key": "value"})
5555
assert project.get("dict_item") == {"key": "value"}
5656

5757

58-
def test_put_pandas_df(project):
59-
df = pandas.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
60-
project.put("pandas_df", df) # DataFrameItem
61-
pandas.testing.assert_frame_equal(project.get("pandas_df"), df)
58+
def test_put_pandas_dataframe(project):
59+
dataframe = pandas.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
60+
project.put("pandas_dataframe", dataframe)
61+
pandas.testing.assert_frame_equal(project.get("pandas_dataframe"), dataframe)
62+
63+
64+
def test_put_pandas_series(project):
65+
series = pandas.Series([0, 1, 2])
66+
project.put("pandas_series", series)
67+
pandas.testing.assert_series_equal(project.get("pandas_series"), series)
6268

6369

6470
def test_put_numpy_array(project):

0 commit comments

Comments
 (0)