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

ExtensionArray.Round method and tests #54582

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1125199
add round abc
andrewgsavage Aug 16, 2023
a4ef563
add round test
andrewgsavage Aug 16, 2023
9f6ebc9
round tests
andrewgsavage Aug 16, 2023
2f0f100
lint
andrewgsavage Aug 16, 2023
241ba6c
lint
andrewgsavage Aug 16, 2023
457290d
Merge branch 'main' into round
andrewgsavage Aug 17, 2023
e2a4168
tests
andrewgsavage Aug 26, 2023
a908700
move methods tests, elementwise rounding
andrewgsavage Sep 4, 2023
fbbfe46
lint
andrewgsavage Sep 5, 2023
15915dc
remove test
andrewgsavage Sep 5, 2023
4167122
Merge branch 'main' into round
andrewgsavage Sep 5, 2023
e31d06a
add default round method
andrewgsavage Sep 6, 2023
bfd36f8
merge main
andrewgsavage Sep 6, 2023
bc4d8c6
remove invert
andrewgsavage Sep 6, 2023
362cd73
typo
andrewgsavage Sep 6, 2023
aa79869
decimal array works now
andrewgsavage Sep 6, 2023
2b694bf
move round to base calss
andrewgsavage Sep 24, 2023
01d1453
move pytest mark into if statement
andrewgsavage Sep 24, 2023
e5d500d
nitpick
andrewgsavage Sep 24, 2023
95d07fa
fix pyarrow round dtypes
andrewgsavage Sep 24, 2023
f28d82f
implement sparsearray round
andrewgsavage Oct 19, 2023
2299134
lint
andrewgsavage Oct 19, 2023
15a7cac
lint
andrewgsavage Oct 19, 2023
ee37b4d
mypy
andrewgsavage Oct 19, 2023
70ebf5a
ignore my py error
andrewgsavage Nov 7, 2023
bda7031
Merge branch 'main' into round
andrewgsavage Nov 7, 2023
e01130d
remove blocks ignore mypyr
andrewgsavage Nov 7, 2023
2e73b6a
Merge branch 'round' of https://github.com/andrewgsavage/pandas into …
andrewgsavage Nov 7, 2023
2e9cddf
revert cast
andrewgsavage Nov 8, 2023
76c9ba7
typerror for non numerics
andrewgsavage Nov 8, 2023
044f0a1
isna
andrewgsavage Nov 8, 2023
770edf3
cast pyarrow
andrewgsavage Nov 9, 2023
85f67b6
Merge branch 'main' into round
andrewgsavage Mar 4, 2024
f3d4520
Update blocks.py
andrewgsavage Mar 4, 2024
398e64b
Update blocks.py
andrewgsavage Mar 4, 2024
b0af597
rounding boolean shouldnt error
andrewgsavage Mar 13, 2024
70b6c89
whatsnew
andrewgsavage Mar 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ Sparse
ExtensionArray
^^^^^^^^^^^^^^
- Fixed bug in :meth:`api.types.is_datetime64_any_dtype` where a custom :class:`ExtensionDtype` would return ``False`` for array-likes (:issue:`57055`)
-
- A default, unoptimised :meth:`ExtensionArray.round` method is now provided for numeric ExtensionArrays (:issue:`49387`)

Styler
^^^^^^
Expand Down
5 changes: 4 additions & 1 deletion pandas/core/arrays/arrow/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,10 @@ def round(self, decimals: int = 0, *args, **kwargs) -> Self:
DataFrame.round : Round values of a DataFrame.
Series.round : Round values of a Series.
"""
return type(self)(pc.round(self._pa_array, ndigits=decimals))
if not self.dtype._is_numeric or self.dtype._is_boolean:
raise TypeError("Cannot round non-numeric type.")
result = pc.round(self._pa_array, ndigits=decimals)
return type(self)(result.cast(self._pa_array.type))
Copy link
Member

Choose a reason for hiding this comment

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

Why is this cast needed?


@doc(ExtensionArray.searchsorted)
def searchsorted(
Expand Down
16 changes: 16 additions & 0 deletions pandas/core/arrays/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2278,6 +2278,22 @@ def _mode(self, dropna: bool = True) -> Self:
# ndarray[Any, Any]]", expected "Self")
return mode(self, dropna=dropna) # type: ignore[return-value]

def round(self, decimals: int = 0, *args, **kwargs) -> Self:
# Implementer note: This is a non-optimized default implementation.
# Implementers are encouraged to override this method to avoid
# elementwise rounding.
if self.dtype._is_boolean:
return self
if not self.dtype._is_numeric:
raise TypeError(f"Cannot round {type(self)} dtype as it is non-numeric")
return self._from_sequence(
[
round(element) if not element_isna else element
for (element, element_isna) in zip(self, self.isna())
],
dtype=self.dtype,
)

def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
if any(
isinstance(other, (ABCSeries, ABCIndex, ABCDataFrame)) for other in inputs
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -2285,7 +2285,7 @@ def _round(self, freq, mode, ambiguous, nonexistent):
return self._simple_new(result, dtype=self.dtype)

@Appender((_round_doc + _round_example).format(op="round"))
def round(
def round( # type: ignore[override]
self,
freq,
ambiguous: TimeAmbiguous = "raise",
Expand Down
9 changes: 9 additions & 0 deletions pandas/core/arrays/sparse/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,15 @@ def argmin(self, skipna: bool = True) -> int:
raise NotImplementedError
return self._argmin_argmax("argmin")

def round(self, decimals: int = 0, *args, **kwargs) -> Self:
new_values = np.array(
[
round(element) if not isna(element) else element
for element in self.sp_values
]
)
return self._simple_new(new_values, self._sparse_index, self.dtype)

# ------------------------------------------------------------------------
# Ufuncs
# ------------------------------------------------------------------------
Expand Down
6 changes: 1 addition & 5 deletions pandas/core/internals/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1523,11 +1523,7 @@ def round(self, decimals: int) -> Self:
"""
if not self.is_numeric or self.is_bool:
return self.copy(deep=False)
# TODO: round only defined on BaseMaskedArray
# Series also does this, so would need to fix both places
# error: Item "ExtensionArray" of "Union[ndarray[Any, Any], ExtensionArray]"
# has no attribute "round"
values = self.values.round(decimals) # type: ignore[union-attr]
values = self.values.round(decimals)

refs = None
if values is self.values:
Expand Down
16 changes: 16 additions & 0 deletions pandas/tests/extension/base/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,3 +718,19 @@ def test_equals(self, data, na_value, as_series, box):
def test_equals_same_data_different_object(self, data):
# https://github.com/pandas-dev/pandas/issues/34660
assert pd.Series(data).equals(pd.Series(data))

def test_round(self, data):
if not data.dtype._is_numeric:
with pytest.raises(TypeError):
data.round()
elif data.dtype._is_boolean:
result = pd.Series(data).round()
expected = pd.Series(data)
tm.assert_series_equal(result, expected)
else:
result = pd.Series(data).round()
expected = pd.Series(
[round(element) if pd.notna(element) else element for element in data],
dtype=data.dtype,
)
tm.assert_series_equal(result, expected)
4 changes: 4 additions & 0 deletions pandas/tests/extension/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
else:
return super().check_reduce(ser, op_name, skipna)

@pytest.mark.skip("DatetimeArray uses a different function signature for round")
def test_round(self):
pass


class Test2DCompat(base.NDArrayBacked2DTests):
pass