Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 24 additions & 4 deletions plotnine/layer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import decimal
import typing
from copy import copy, deepcopy
from typing import Iterable, List, cast, overload
Expand Down Expand Up @@ -31,6 +32,23 @@
)


def _convert_decimal_columns(data: pd.DataFrame) -> pd.DataFrame:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to _decimal_columns_to_float and put function at the bottom of the module.

"""
Cast columns of decimal.Decimal values to float

polars' `to_pandas()` maps a Decimal column onto a pandas object column
holding `decimal.Decimal` values. plotnine treats object columns as
discrete, so such a column cannot be used with a continuous scale. Casting
it to float lets it behave like any other numeric column.
"""
for col in data.columns:
if data[col].dtype == object:
non_null = data[col].dropna()
if len(non_null) and isinstance(non_null.iloc[0], decimal.Decimal):
data[col] = data[col].astype("float64")
return data
Comment thread
has2k1 marked this conversation as resolved.
Outdated


class layer:
"""
Layer
Expand Down Expand Up @@ -222,7 +240,9 @@ def _make_layer_data(self, plot_data: DataLike | None):
if plot_data is None:
data = pd.DataFrame()
elif hasattr(plot_data, "to_pandas"):
data = cast("DataFrameConvertible", plot_data).to_pandas()
data = _convert_decimal_columns(
cast("DataFrameConvertible", plot_data).to_pandas()
)
else:
data = cast("pd.DataFrame", plot_data)

Expand All @@ -249,9 +269,9 @@ def _make_layer_data(self, plot_data: DataLike | None):
else:
# Recognise polars dataframes
if hasattr(self._data, "to_pandas"):
self.data = cast(
"DataFrameConvertible", self._data
).to_pandas()
self.data = _convert_decimal_columns(
cast("DataFrameConvertible", self._data).to_pandas()
)
elif isinstance(self._data, pd.DataFrame):
self.data = self._data.copy()
else:
Comment thread
has2k1 marked this conversation as resolved.
Expand Down
25 changes: 25 additions & 0 deletions tests/test_ggplot_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
coord_trans,
facet_null,
geom_bar,
geom_col,
geom_histogram,
geom_line,
geom_point,
Expand All @@ -21,6 +22,7 @@
labs,
lims,
scale_x_continuous,
scale_y_continuous,
stage,
stat_identity,
theme,
Expand Down Expand Up @@ -365,6 +367,29 @@ def to_pandas(self):
assert p2 == "to_pandas"


def test_to_pandas_decimal_columns():
# polars' to_pandas() returns a Decimal column as an object column of
# decimal.Decimal values, which would otherwise be treated as discrete
# and rejected by a continuous scale. See GH #1033.
from decimal import Decimal

class DecimalData:
def to_pandas(self):
return pd.DataFrame(
{
"x": ["a", "b", "c"],
"y": [Decimal(1), Decimal(2), Decimal(3)],
}
)
Comment thread
has2k1 marked this conversation as resolved.
Outdated

p = (
ggplot(DecimalData(), aes("x", "y"))
+ geom_col()
+ scale_y_continuous(limits=(0, 5))
)
p._build()


def test_callable_as_data():
def _fn(data):
return data.rename(columns={"xx": "x", "yy": "y"})
Expand Down