Skip to content

Commit 62529cc

Browse files
committed
Merge pull request #8298 from jreback/iat
BUG: Bug in iat return boxing for Timestamp/Timedelta (GH7729)
2 parents 4c36db3 + 0fd2cd0 commit 62529cc

File tree

9 files changed

+43
-76
lines changed

9 files changed

+43
-76
lines changed

pandas/core/api.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from pandas.tseries.tools import to_datetime
2626
from pandas.tseries.index import (DatetimeIndex, Timestamp,
2727
date_range, bdate_range)
28+
from pandas.tseries.tdi import TimedeltaIndex, Timedelta
2829
from pandas.tseries.period import Period, PeriodIndex
2930

3031
# legacy

pandas/core/common.py

+6-17
Original file line numberDiff line numberDiff line change
@@ -1827,9 +1827,8 @@ def _possibly_convert_objects(values, convert_dates=True,
18271827
if convert_timedeltas and values.dtype == np.object_:
18281828

18291829
if convert_timedeltas == 'coerce':
1830-
from pandas.tseries.timedeltas import \
1831-
_possibly_cast_to_timedelta
1832-
values = _possibly_cast_to_timedelta(values, coerce=True)
1830+
from pandas.tseries.timedeltas import to_timedelta
1831+
values = to_timedelta(values, coerce=True)
18331832

18341833
# if we are all nans then leave me alone
18351834
if not isnull(new_values).all():
@@ -1889,7 +1888,7 @@ def _possibly_cast_to_datetime(value, dtype, coerce=False):
18891888
""" try to cast the array/value to a datetimelike dtype, converting float
18901889
nan to iNaT
18911890
"""
1892-
from pandas.tseries.timedeltas import _possibly_cast_to_timedelta
1891+
from pandas.tseries.timedeltas import to_timedelta
18931892
from pandas.tseries.tools import to_datetime
18941893

18951894
if dtype is not None:
@@ -1931,8 +1930,7 @@ def _possibly_cast_to_datetime(value, dtype, coerce=False):
19311930
if is_datetime64:
19321931
value = to_datetime(value, coerce=coerce).values
19331932
elif is_timedelta64:
1934-
value = _possibly_cast_to_timedelta(value,
1935-
dtype=dtype)
1933+
value = to_timedelta(value, coerce=coerce).values
19361934
except (AttributeError, ValueError):
19371935
pass
19381936

@@ -1949,7 +1947,7 @@ def _possibly_cast_to_datetime(value, dtype, coerce=False):
19491947
value = value.astype(_NS_DTYPE)
19501948

19511949
elif dtype.kind == 'm' and dtype != _TD_DTYPE:
1952-
value = _possibly_cast_to_timedelta(value)
1950+
value = to_timedelta(value)
19531951

19541952
# only do this if we have an array and the dtype of the array is not
19551953
# setup already we are not an integer/object, so don't bother with this
@@ -2005,16 +2003,7 @@ def _try_timedelta(v):
20052003
try:
20062004
return to_timedelta(v).values.reshape(shape)
20072005
except:
2008-
2009-
# this is for compat with numpy < 1.7
2010-
# but string-likes will fail here
2011-
2012-
from pandas.tseries.timedeltas import \
2013-
_possibly_cast_to_timedelta
2014-
try:
2015-
return _possibly_cast_to_timedelta(v, coerce='compat').reshape(shape)
2016-
except:
2017-
return v
2006+
return v
20182007

20192008
# do a quick inference for perf
20202009
sample = v[:min(3,len(v))]

pandas/core/ops.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def _validate(self):
314314

315315
def _convert_to_array(self, values, name=None, other=None):
316316
"""converts values to ndarray"""
317-
from pandas.tseries.timedeltas import _possibly_cast_to_timedelta
317+
from pandas.tseries.timedeltas import to_timedelta
318318

319319
coerce = True
320320
if not is_list_like(values):
@@ -337,7 +337,7 @@ def _convert_to_array(self, values, name=None, other=None):
337337
values = tslib.array_to_datetime(values)
338338
elif inferred_type in ('timedelta', 'timedelta64'):
339339
# have a timedelta, convert to to ns here
340-
values = _possibly_cast_to_timedelta(values, coerce=coerce, dtype='timedelta64[ns]')
340+
values = to_timedelta(values, coerce=coerce)
341341
elif inferred_type == 'integer':
342342
# py3 compat where dtype is 'm' but is an integer
343343
if values.dtype.kind == 'm':
@@ -356,7 +356,7 @@ def _convert_to_array(self, values, name=None, other=None):
356356
"datetime/timedelta operations [{0}]".format(
357357
', '.join([com.pprint_thing(v)
358358
for v in values[mask]])))
359-
values = _possibly_cast_to_timedelta(os, coerce=coerce)
359+
values = to_timedelta(os, coerce=coerce)
360360
elif inferred_type == 'floating':
361361

362362
# all nan, so ok, use the other dtype (e.g. timedelta or datetime)

pandas/core/series.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
_possibly_cast_to_datetime, _possibly_castable,
2121
_possibly_convert_platform, _try_sort,
2222
ABCSparseArray, _maybe_match_name, _coerce_to_dtype,
23-
_ensure_object, SettingWithCopyError)
23+
_ensure_object, SettingWithCopyError,
24+
_maybe_box_datetimelike)
2425
from pandas.core.index import (Index, MultiIndex, InvalidIndexError,
2526
_ensure_index)
2627
from pandas.core.indexing import _check_bool_indexer, _maybe_convert_indices
@@ -781,7 +782,7 @@ def get_value(self, label, takeable=False):
781782
value : scalar value
782783
"""
783784
if takeable is True:
784-
return self.values[label]
785+
return _maybe_box_datetimelike(self.values[label])
785786
return self.index.get_value(self.values, label)
786787

787788
def set_value(self, label, value, takeable=False):

pandas/tests/test_indexing.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import pandas.core.common as com
1515
from pandas import option_context
1616
from pandas.core.api import (DataFrame, Index, Series, Panel, isnull,
17-
MultiIndex, Float64Index, Timestamp)
17+
MultiIndex, Float64Index, Timestamp, Timedelta)
1818
from pandas.util.testing import (assert_almost_equal, assert_series_equal,
1919
assert_frame_equal, assert_panel_equal,
2020
assert_attr_equal)
@@ -322,7 +322,7 @@ def _check(f, func, values = False):
322322
_check(d['ts'], 'at')
323323
_check(d['floats'],'at')
324324

325-
def test_at_timestamp(self):
325+
def test_at_iat_coercion(self):
326326

327327
# as timestamp is not a tuple!
328328
dates = date_range('1/1/2000', periods=8)
@@ -333,6 +333,22 @@ def test_at_timestamp(self):
333333
xp = s.values[5]
334334
self.assertEqual(result, xp)
335335

336+
# GH 7729
337+
# make sure we are boxing the returns
338+
s = Series(['2014-01-01', '2014-02-02'], dtype='datetime64[ns]')
339+
expected = Timestamp('2014-02-02')
340+
341+
for r in [ lambda : s.iat[1], lambda : s.iloc[1] ]:
342+
result = r()
343+
self.assertEqual(result, expected)
344+
345+
s = Series(['1 days','2 days'], dtype='timedelta64[ns]')
346+
expected = Timedelta('2 days')
347+
348+
for r in [ lambda : s.iat[1], lambda : s.iloc[1] ]:
349+
result = r()
350+
self.assertEqual(result, expected)
351+
336352
def test_iat_invalid_args(self):
337353
pass
338354

pandas/tests/test_series.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -3286,15 +3286,12 @@ def test_bfill(self):
32863286
assert_series_equal(ts.bfill(), ts.fillna(method='bfill'))
32873287

32883288
def test_sub_of_datetime_from_TimeSeries(self):
3289-
from pandas.tseries.timedeltas import _possibly_cast_to_timedelta
3289+
from pandas.tseries.timedeltas import to_timedelta
32903290
from datetime import datetime
32913291
a = Timestamp(datetime(1993, 0o1, 0o7, 13, 30, 00))
32923292
b = datetime(1993, 6, 22, 13, 30)
32933293
a = Series([a])
3294-
result = _possibly_cast_to_timedelta(np.abs(a - b))
3295-
self.assertEqual(result.dtype, 'timedelta64[ns]')
3296-
3297-
result = _possibly_cast_to_timedelta(np.abs(b - a))
3294+
result = to_timedelta(np.abs(a - b))
32983295
self.assertEqual(result.dtype, 'timedelta64[ns]')
32993296

33003297
def test_datetime64_with_index(self):

pandas/tseries/tests/test_base.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,9 @@ def test_sub_isub(self):
213213

214214
for rng, other, expected in [(rng1, other1, expected1), (rng2, other2, expected2),
215215
(rng3, other3, expected3)]:
216-
result_add = rng - other
217-
result_union = rng.diff(other)
216+
result_union = rng.difference(other)
218217

219-
tm.assert_index_equal(result_add, expected)
220218
tm.assert_index_equal(result_union, expected)
221-
rng -= other
222-
tm.assert_index_equal(rng, expected)
223219

224220
# offset
225221
offsets = [pd.offsets.Hour(2), timedelta(hours=2), np.timedelta64(2, 'h'),
@@ -859,13 +855,8 @@ def test_sub_isub(self):
859855
(rng3, other3, expected3), (rng4, other4, expected4),
860856
(rng5, other5, expected5), (rng6, other6, expected6),
861857
(rng7, other7, expected7),]:
862-
result_add = rng - other
863-
result_union = rng.diff(other)
864-
865-
tm.assert_index_equal(result_add, expected)
858+
result_union = rng.difference(other)
866859
tm.assert_index_equal(result_union, expected)
867-
rng -= other
868-
tm.assert_index_equal(rng, expected)
869860

870861
# offset
871862
# DateOffset

pandas/tseries/timedeltas.py

+7-35
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
is_timedelta64_dtype, _values_from_object,
1313
is_list_like, isnull, _ensure_object)
1414

15-
def to_timedelta(arg, unit='ns', box=True):
15+
def to_timedelta(arg, unit='ns', box=True, coerce=False):
1616
"""
1717
Convert argument to timedelta
1818
@@ -23,6 +23,7 @@ def to_timedelta(arg, unit='ns', box=True):
2323
box : boolean, default True
2424
If True returns a Timedelta/TimedeltaIndex of the results
2525
if False returns a np.timedelta64 or ndarray of values of dtype timedelta64[ns]
26+
coerce : force errors to NaT (False by default)
2627
2728
Returns
2829
-------
@@ -43,14 +44,14 @@ def _convert_listlike(arg, box, unit):
4344
value = arg.astype('timedelta64[{0}]'.format(unit)).astype('timedelta64[ns]')
4445
else:
4546
try:
46-
value = tslib.array_to_timedelta64(_ensure_object(arg), unit=unit)
47+
value = tslib.array_to_timedelta64(_ensure_object(arg), unit=unit, coerce=coerce)
4748
except:
4849

4950
# try to process strings fast; may need to fallback
5051
try:
5152
value = np.array([ _get_string_converter(r, unit=unit)() for r in arg ],dtype='m8[ns]')
5253
except:
53-
value = np.array([ _coerce_scalar_to_timedelta_type(r, unit=unit) for r in arg ])
54+
value = np.array([ _coerce_scalar_to_timedelta_type(r, unit=unit, coerce=coerce) for r in arg ])
5455

5556
if box:
5657
from pandas import TimedeltaIndex
@@ -67,7 +68,7 @@ def _convert_listlike(arg, box, unit):
6768
return _convert_listlike(arg, box=box, unit=unit)
6869

6970
# ...so it must be a scalar value. Return scalar.
70-
return _coerce_scalar_to_timedelta_type(arg, unit=unit, box=box)
71+
return _coerce_scalar_to_timedelta_type(arg, unit=unit, box=box, coerce=coerce)
7172

7273
_unit_map = {
7374
'Y' : 'Y',
@@ -135,7 +136,7 @@ def _validate_timedelta_unit(arg):
135136
_full_search2 = re.compile(''.join(
136137
["^\s*(?P<neg>-?)\s*"] + [ "(?P<" + p + ">\\d+\.?\d*\s*(" + ss + "))?\\s*" for p, ss in abbrevs ] + ['$']))
137138

138-
def _coerce_scalar_to_timedelta_type(r, unit='ns', box=True):
139+
def _coerce_scalar_to_timedelta_type(r, unit='ns', box=True, coerce=False):
139140
""" convert strings to timedelta; coerce to Timedelta (if box), else np.timedelta64"""
140141

141142
if isinstance(r, compat.string_types):
@@ -145,7 +146,7 @@ def _coerce_scalar_to_timedelta_type(r, unit='ns', box=True):
145146
r = converter()
146147
unit='ns'
147148

148-
result = tslib.convert_to_timedelta(r,unit)
149+
result = tslib.convert_to_timedelta(r,unit,coerce)
149150
if box:
150151
result = tslib.Timedelta(result)
151152

@@ -262,32 +263,3 @@ def convert(r=None, unit=None, m=m):
262263
# no converter
263264
raise ValueError("cannot create timedelta string converter for [{0}]".format(r))
264265

265-
def _possibly_cast_to_timedelta(value, coerce=True, dtype=None):
266-
""" try to cast to timedelta64, if already a timedeltalike, then make
267-
sure that we are [ns] (as numpy 1.6.2 is very buggy in this regards,
268-
don't force the conversion unless coerce is True
269-
270-
if dtype is passed then this is the target dtype
271-
"""
272-
273-
# deal with numpy not being able to handle certain timedelta operations
274-
if isinstance(value, (ABCSeries, np.ndarray)):
275-
276-
# i8 conversions
277-
if value.dtype == 'int64' and np.dtype(dtype) == 'timedelta64[ns]':
278-
value = value.astype('timedelta64[ns]')
279-
return value
280-
elif value.dtype.kind == 'm':
281-
if value.dtype != 'timedelta64[ns]':
282-
value = value.astype('timedelta64[ns]')
283-
return value
284-
285-
# we don't have a timedelta, but we want to try to convert to one (but
286-
# don't force it)
287-
if coerce:
288-
new_value = tslib.array_to_timedelta64(
289-
_values_from_object(value).astype(object), coerce=False)
290-
if new_value.dtype == 'i8':
291-
value = np.array(new_value, dtype='timedelta64[ns]')
292-
293-
return value

pandas/util/decorators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def wrapper(*args, **kwargs):
7777
else:
7878
new_arg_value = old_arg_value
7979
msg = "the '%s' keyword is deprecated, " \
80-
"use '%s' instead" % (old_arg_name, new_arg_name)
80+
"use '%s' instead" % (old_arg_name, new_arg_name)
8181
warnings.warn(msg, FutureWarning)
8282
if kwargs.get(new_arg_name, None) is not None:
8383
msg = "Can only specify '%s' or '%s', not both" % \

0 commit comments

Comments
 (0)