From 0d2867d5518ac8e5032e7dfca5d0adb5811fa84d Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 9 Dec 2019 20:41:30 -0800 Subject: [PATCH 1/5] BUG+DEPR: undeprecate item, fix dt64/td64 output type --- doc/source/whatsnew/v1.0.0.rst | 3 +++ pandas/core/base.py | 18 +++++++------- pandas/core/indexes/period.py | 15 +----------- pandas/tests/base/test_ops.py | 8 +++--- pandas/tests/series/test_api.py | 43 +++++++++++++++++++++++++++------ 5 files changed, 51 insertions(+), 36 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 19fb4bdcd9536..12ffbc441b54b 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -490,6 +490,7 @@ Deprecations - :func:`eval` keyword argument "truediv" is deprecated and will be removed in a future version (:issue:`29812`) - :meth:`Categorical.take_nd` is deprecated, use :meth:`Categorical.take` instead (:issue:`27745`) - The parameter ``numeric_only`` of :meth:`Categorical.min` and :meth:`Categorical.max` is deprecated and replaced with ``skipna`` (:issue:`25303`) +- :meth:`Series.item` and :meth:`Index.item` have been _undeprecated_ (:issue:`?????`) - .. _whatsnew_1000.prior_deprecations: @@ -683,6 +684,8 @@ Datetimelike - Bug in :meth:`Series.var` failing to raise ``TypeError`` when called with ``timedelta64[ns]`` dtype (:issue:`28289`) - Bug in :meth:`DatetimeIndex.strftime` and :meth:`Series.dt.strftime` where ``NaT`` was converted to the string ``'NaT'`` instead of ``np.nan`` (:issue:`29578`) - Bug in :attr:`Timestamp.resolution` being a property instead of a class attribute (:issue:`29910`) +- Bug in :meth:`Series.item` with ``datetime64`` or ``timedelta64`` dtype, :meth:`DatetimeIndex.item`, and :meth:`TimedeltaIndex.item` returning an integer instead of a :class:`Timestamp` or :class:`Timedelta` (:issue:`????`) +- Timedelta ^^^^^^^^^ diff --git a/pandas/core/base.py b/pandas/core/base.py index 88b8fe405c8e4..472e139a06e1d 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -5,7 +5,6 @@ from collections import OrderedDict import textwrap from typing import Dict, FrozenSet, List, Optional -import warnings import numpy as np @@ -677,19 +676,20 @@ def item(self): """ Return the first element of the underlying data as a python scalar. - .. deprecated:: 0.25.0 - Returns ------- scalar The first element of %(klass)s. + + Raises + ------ + ValueError + If the data is not length-1. """ - warnings.warn( - "`item` has been deprecated and will be removed in a future version", - FutureWarning, - stacklevel=2, - ) - return self.values.item() + if len(self) == 1: + return self[0] + else: + raise ValueError("can only convert an array of size 1 to a Python scalar") @property def nbytes(self): diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index d63de10d92921..843291ce52b25 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -import warnings import weakref import numpy as np @@ -890,23 +889,11 @@ def __setstate__(self, state): def item(self): """ - Return the first element of the underlying data as a python - scalar - - .. deprecated:: 0.25.0 - + Return the first element of the underlying data as a python scalar. """ - warnings.warn( - "`item` has been deprecated and will be removed in a future version", - FutureWarning, - stacklevel=2, - ) - # TODO(DatetimeArray): remove if len(self) == 1: return self[0] else: - # TODO: is this still necessary? - # copy numpy's message here because Py26 raises an IndexError raise ValueError("can only convert an array of size 1 to a Python scalar") def memory_usage(self, deep=False): diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index bcd6b931a0f85..04277ce929bca 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -236,15 +236,13 @@ def test_ndarray_compat_properties(self): assert not hasattr(o, p) with pytest.raises(ValueError): - with tm.assert_produces_warning(FutureWarning): - o.item() # len > 1 + o.item() # len > 1 assert o.ndim == 1 assert o.size == len(o) - with tm.assert_produces_warning(FutureWarning): - assert Index([1]).item() == 1 - assert Series([1]).item() == 1 + assert Index([1]).item() == 1 + assert Series([1]).item() == 1 def test_value_counts_unique_nunique(self): for orig in self.objs: diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index 5da0ee9b5b1c0..410a985db77cf 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -12,13 +12,14 @@ DatetimeIndex, Index, Series, + Timedelta, TimedeltaIndex, + Timestamp, date_range, period_range, timedelta_range, ) from pandas.core.arrays import PeriodArray -from pandas.core.indexes.datetimes import Timestamp import pandas.util.testing as tm import pandas.io.formats.printing as printing @@ -398,6 +399,39 @@ def test_numpy_unique(self, datetime_series): # it works! np.unique(datetime_series) + def test_item(self): + s = Series([1]) + result = s.item() + assert result == 1 + assert s.item() == s.iloc[0] + + ser = Series([1, 2]) + msg = "can only convert an array of size 1" + with pytest.raises(ValueError, match=msg): + ser.item() + + dti = pd.date_range("2016-01-01", periods=2) + with pytest.raises(ValueError, match=msg): + dti.item() + with pytest.raises(ValueError, match=msg): + Series(dti).item() + + val = dti[:1].item() + assert isinstance(val, Timestamp) + val = Series(dti)[:1].item() + assert isinstance(val, Timestamp) + + tdi = dti - dti + with pytest.raises(ValueError, match=msg): + tdi.item() + with pytest.raises(ValueError, match=msg): + Series(tdi).item() + + val = tdi[:1].item() + assert isinstance(val, Timedelta) + val = Series(tdi)[:1].item() + assert isinstance(val, Timedelta) + def test_ndarray_compat(self): # test numpy compat with Series as sub-class of NDFrame @@ -414,13 +448,6 @@ def f(x): expected = tsdf.max() tm.assert_series_equal(result, expected) - # .item() - with tm.assert_produces_warning(FutureWarning): - s = Series([1]) - result = s.item() - assert result == 1 - assert s.item() == s.iloc[0] - # using an ndarray like function s = Series(np.random.randn(10)) result = Series(np.ones_like(s)) From ef1c9ed003fc6fa74351087292af8bb41d9cd4d6 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 10 Dec 2019 08:16:14 -0800 Subject: [PATCH 2/5] Update pandas/core/indexes/period.py Co-Authored-By: Stephan Hoyer --- pandas/core/indexes/period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 843291ce52b25..91775eaa52aa1 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -889,7 +889,7 @@ def __setstate__(self, state): def item(self): """ - Return the first element of the underlying data as a python scalar. + Return the sole element of the underlying data as a python scalar. """ if len(self) == 1: return self[0] From a98fda2b04cd215ecbc62f486902caf2000dda76 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 10 Dec 2019 09:46:15 -0800 Subject: [PATCH 3/5] restore python-scalar behavior, tests --- pandas/core/base.py | 8 +++++++- pandas/tests/series/test_api.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pandas/core/base.py b/pandas/core/base.py index 472e139a06e1d..82aab557d44f9 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -25,6 +25,7 @@ is_object_dtype, is_scalar, is_timedelta64_ns_dtype, + needs_i8_conversion, ) from pandas.core.dtypes.generic import ABCDataFrame, ABCIndexClass, ABCSeries from pandas.core.dtypes.missing import isna @@ -686,8 +687,13 @@ def item(self): ValueError If the data is not length-1. """ + if not needs_i8_conversion(self.dtype): + # numpy returns ints instead of datetime64/timedelta64 objects, + # which we need to wrap in Timestamp/Timedelta/Period regardless. + return self.values.item() + if len(self) == 1: - return self[0] + return next(iter(self)) else: raise ValueError("can only convert an array of size 1 to a Python scalar") diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index 410a985db77cf..f8cf6b6a54d14 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -403,7 +403,13 @@ def test_item(self): s = Series([1]) result = s.item() assert result == 1 - assert s.item() == s.iloc[0] + assert result == s.iloc[0] + assert isinstance(result, int) # i.e. not np.int64 + + ser = Series([0.5], index=[3]) + result = ser.item() + assert isinstance(result, float) + assert result == 0.5 ser = Series([1, 2]) msg = "can only convert an array of size 1" @@ -432,6 +438,11 @@ def test_item(self): val = Series(tdi)[:1].item() assert isinstance(val, Timedelta) + # Case where ser[0] would not work + ser = Series(dti, index=[5, 6]) + val = ser[:1].item() + assert val == dti[0] + def test_ndarray_compat(self): # test numpy compat with Series as sub-class of NDFrame From 39c13df2e4da084052e32b014e6d10ea36c29b28 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 10 Dec 2019 09:47:13 -0800 Subject: [PATCH 4/5] PeriodIndex.item no longer necessary --- pandas/core/indexes/period.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 91775eaa52aa1..1a343a01ff926 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -887,15 +887,6 @@ def __setstate__(self, state): _unpickle_compat = __setstate__ - def item(self): - """ - Return the sole element of the underlying data as a python scalar. - """ - if len(self) == 1: - return self[0] - else: - raise ValueError("can only convert an array of size 1 to a Python scalar") - def memory_usage(self, deep=False): result = super().memory_usage(deep=deep) if hasattr(self, "_cache") and "_int64index" in self._cache: From f6cb1b7fc04a22c0994145d5d1dd9e5d7858fc1f Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 12 Dec 2019 17:01:14 -0800 Subject: [PATCH 5/5] catch EA dtype --- pandas/core/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/core/base.py b/pandas/core/base.py index e1a80ef186017..92f78dbf060fd 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -25,6 +25,7 @@ is_object_dtype, is_scalar, is_timedelta64_ns_dtype, + needs_i8_conversion, ) from pandas.core.dtypes.generic import ABCDataFrame, ABCIndexClass, ABCSeries from pandas.core.dtypes.missing import isna @@ -686,7 +687,9 @@ def item(self): ValueError If the data is not length-1. """ - if not is_extension_array_dtype(self.dtype): + if not ( + is_extension_array_dtype(self.dtype) or needs_i8_conversion(self.dtype) + ): # numpy returns ints instead of datetime64/timedelta64 objects, # which we need to wrap in Timestamp/Timedelta/Period regardless. return self.values.item()