Skip to content

Commit 4aa6323

Browse files
sinhrksjreback
authored andcommitted
BUG: Series ops with object dtype may incorrectly fail
closes #13043 closes #13072
1 parent 2a99394 commit 4aa6323

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

doc/source/whatsnew/v0.18.2.txt

+14
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,22 @@ Bug Fixes
128128

129129

130130

131+
- Bug in ``Series`` arithmetic raises ``TypeError`` if it contains datetime-like as ``object`` dtype (:issue:`13043`)
132+
131133

132134

133135
- Bug in ``NaT`` - ``Period`` raises ``AttributeError`` (:issue:`13071`)
134136
- Bug in ``Period`` addition raises ``TypeError`` if ``Period`` is on right hand side (:issue:`13069`)
135137
- Bug in ``pd.set_eng_float_format()`` that would prevent NaN's from formatting (:issue:`11981`)
138+
139+
140+
141+
142+
143+
144+
145+
146+
147+
148+
149+

pandas/core/ops.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pandas.tslib import iNaT
2020
from pandas.compat import bind_method
2121
import pandas.core.missing as missing
22+
import pandas.algos as _algos
2223
import pandas.core.algorithms as algos
2324
from pandas.core.common import (is_list_like, notnull, isnull,
2425
_values_from_object, _maybe_match_name,
@@ -600,6 +601,21 @@ def na_op(x, y):
600601
result = missing.fill_zeros(result, x, y, name, fill_zeros)
601602
return result
602603

604+
def safe_na_op(lvalues, rvalues):
605+
try:
606+
return na_op(lvalues, rvalues)
607+
except Exception:
608+
if isinstance(rvalues, ABCSeries):
609+
if is_object_dtype(rvalues):
610+
# if dtype is object, try elementwise op
611+
return _algos.arrmap_object(rvalues,
612+
lambda x: op(lvalues, x))
613+
else:
614+
if is_object_dtype(lvalues):
615+
return _algos.arrmap_object(lvalues,
616+
lambda x: op(x, rvalues))
617+
raise
618+
603619
def wrapper(left, right, name=name, na_op=na_op):
604620

605621
if isinstance(right, pd.DataFrame):
@@ -638,17 +654,17 @@ def wrapper(left, right, name=name, na_op=na_op):
638654
if ridx is not None:
639655
rvalues = algos.take_1d(rvalues, ridx)
640656

641-
arr = na_op(lvalues, rvalues)
642-
643-
return left._constructor(wrap_results(arr), index=index,
657+
result = wrap_results(safe_na_op(lvalues, rvalues))
658+
return left._constructor(result, index=index,
644659
name=name, dtype=dtype)
645660
else:
646661
# scalars
647662
if (hasattr(lvalues, 'values') and
648663
not isinstance(lvalues, pd.DatetimeIndex)):
649664
lvalues = lvalues.values
650665

651-
return left._constructor(wrap_results(na_op(lvalues, rvalues)),
666+
result = wrap_results(safe_na_op(lvalues, rvalues))
667+
return left._constructor(result,
652668
index=left.index, name=left.name,
653669
dtype=dtype)
654670

pandas/tseries/tests/test_period.py

+62
Original file line numberDiff line numberDiff line change
@@ -4151,6 +4151,68 @@ def test_intercept_astype_object(self):
41514151
result = df.values.squeeze()
41524152
self.assertTrue((result[:, 0] == expected.values).all())
41534153

4154+
def test_ops_series_timedelta(self):
4155+
# GH 13043
4156+
s = pd.Series([pd.Period('2015-01-01', freq='D'),
4157+
pd.Period('2015-01-02', freq='D')], name='xxx')
4158+
self.assertEqual(s.dtype, object)
4159+
4160+
exp = pd.Series([pd.Period('2015-01-02', freq='D'),
4161+
pd.Period('2015-01-03', freq='D')], name='xxx')
4162+
tm.assert_series_equal(s + pd.Timedelta('1 days'), exp)
4163+
tm.assert_series_equal(pd.Timedelta('1 days') + s, exp)
4164+
4165+
tm.assert_series_equal(s + pd.tseries.offsets.Day(), exp)
4166+
tm.assert_series_equal(pd.tseries.offsets.Day() + s, exp)
4167+
4168+
def test_ops_series_period(self):
4169+
# GH 13043
4170+
s = pd.Series([pd.Period('2015-01-01', freq='D'),
4171+
pd.Period('2015-01-02', freq='D')], name='xxx')
4172+
self.assertEqual(s.dtype, object)
4173+
4174+
p = pd.Period('2015-01-10', freq='D')
4175+
# dtype will be object because of original dtype
4176+
exp = pd.Series([9, 8], name='xxx', dtype=object)
4177+
tm.assert_series_equal(p - s, exp)
4178+
tm.assert_series_equal(s - p, -exp)
4179+
4180+
s2 = pd.Series([pd.Period('2015-01-05', freq='D'),
4181+
pd.Period('2015-01-04', freq='D')], name='xxx')
4182+
self.assertEqual(s2.dtype, object)
4183+
4184+
exp = pd.Series([4, 2], name='xxx', dtype=object)
4185+
tm.assert_series_equal(s2 - s, exp)
4186+
tm.assert_series_equal(s - s2, -exp)
4187+
4188+
def test_ops_frame_period(self):
4189+
# GH 13043
4190+
df = pd.DataFrame({'A': [pd.Period('2015-01', freq='M'),
4191+
pd.Period('2015-02', freq='M')],
4192+
'B': [pd.Period('2014-01', freq='M'),
4193+
pd.Period('2014-02', freq='M')]})
4194+
self.assertEqual(df['A'].dtype, object)
4195+
self.assertEqual(df['B'].dtype, object)
4196+
4197+
p = pd.Period('2015-03', freq='M')
4198+
# dtype will be object because of original dtype
4199+
exp = pd.DataFrame({'A': np.array([2, 1], dtype=object),
4200+
'B': np.array([14, 13], dtype=object)})
4201+
tm.assert_frame_equal(p - df, exp)
4202+
tm.assert_frame_equal(df - p, -exp)
4203+
4204+
df2 = pd.DataFrame({'A': [pd.Period('2015-05', freq='M'),
4205+
pd.Period('2015-06', freq='M')],
4206+
'B': [pd.Period('2015-05', freq='M'),
4207+
pd.Period('2015-06', freq='M')]})
4208+
self.assertEqual(df2['A'].dtype, object)
4209+
self.assertEqual(df2['B'].dtype, object)
4210+
4211+
exp = pd.DataFrame({'A': np.array([4, 4], dtype=object),
4212+
'B': np.array([16, 16], dtype=object)})
4213+
tm.assert_frame_equal(df2 - df, exp)
4214+
tm.assert_frame_equal(df - df2, -exp)
4215+
41544216

41554217
if __name__ == '__main__':
41564218
import nose

pandas/tseries/tests/test_timedeltas.py

+32
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,38 @@ def test_ops_series(self):
413413
tm.assert_series_equal(expected, td * other)
414414
tm.assert_series_equal(expected, other * td)
415415

416+
def test_ops_series_object(self):
417+
# GH 13043
418+
s = pd.Series([pd.Timestamp('2015-01-01', tz='US/Eastern'),
419+
pd.Timestamp('2015-01-01', tz='Asia/Tokyo')],
420+
name='xxx')
421+
self.assertEqual(s.dtype, object)
422+
423+
exp = pd.Series([pd.Timestamp('2015-01-02', tz='US/Eastern'),
424+
pd.Timestamp('2015-01-02', tz='Asia/Tokyo')],
425+
name='xxx')
426+
tm.assert_series_equal(s + pd.Timedelta('1 days'), exp)
427+
tm.assert_series_equal(pd.Timedelta('1 days') + s, exp)
428+
429+
# object series & object series
430+
s2 = pd.Series([pd.Timestamp('2015-01-03', tz='US/Eastern'),
431+
pd.Timestamp('2015-01-05', tz='Asia/Tokyo')],
432+
name='xxx')
433+
self.assertEqual(s2.dtype, object)
434+
exp = pd.Series([pd.Timedelta('2 days'), pd.Timedelta('4 days')],
435+
name='xxx')
436+
tm.assert_series_equal(s2 - s, exp)
437+
tm.assert_series_equal(s - s2, -exp)
438+
439+
s = pd.Series([pd.Timedelta('01:00:00'), pd.Timedelta('02:00:00')],
440+
name='xxx', dtype=object)
441+
self.assertEqual(s.dtype, object)
442+
443+
exp = pd.Series([pd.Timedelta('01:30:00'), pd.Timedelta('02:30:00')],
444+
name='xxx')
445+
tm.assert_series_equal(s + pd.Timedelta('00:30:00'), exp)
446+
tm.assert_series_equal(pd.Timedelta('00:30:00') + s, exp)
447+
416448
def test_compare_timedelta_series(self):
417449
# regresssion test for GH5963
418450
s = pd.Series([timedelta(days=1), timedelta(days=2)])

0 commit comments

Comments
 (0)