Skip to content

Commit 46c52e2

Browse files
committed
Merge pull request #8634 from jreback/numeric_index
BUG: bug in Float/Int index with ndarray for add/sub numeric ops (GH8608)
2 parents 85069eb + 2fcbc7f commit 46c52e2

File tree

8 files changed

+109
-67
lines changed

8 files changed

+109
-67
lines changed

doc/source/whatsnew/v0.15.1.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,5 @@ Bug Fixes
4949
~~~~~~~~~
5050

5151
- Bug in ``cut``/``qcut`` when using ``Series`` and ``retbins=True`` (:issue:`8589`)
52-
52+
- Bug in numeric index operations of add/sub with Float/Index Index with numpy arrays (:issue:`8608`)
5353
- Fix ``shape`` attribute for ``MultiIndex`` (:issue:`8609`)

pandas/core/index.py

+25-13
Original file line numberDiff line numberDiff line change
@@ -1147,21 +1147,21 @@ def __add__(self, other):
11471147
"use '|' or .union()",FutureWarning)
11481148
return self.union(other)
11491149
return Index(np.array(self) + other)
1150-
11511150
__iadd__ = __add__
1152-
__eq__ = _indexOp('__eq__')
1153-
__ne__ = _indexOp('__ne__')
1154-
__lt__ = _indexOp('__lt__')
1155-
__gt__ = _indexOp('__gt__')
1156-
__le__ = _indexOp('__le__')
1157-
__ge__ = _indexOp('__ge__')
11581151

11591152
def __sub__(self, other):
11601153
if isinstance(other, Index):
11611154
warnings.warn("using '-' to provide set differences with Indexes is deprecated, "
11621155
"use .difference()",FutureWarning)
11631156
return self.difference(other)
11641157

1158+
__eq__ = _indexOp('__eq__')
1159+
__ne__ = _indexOp('__ne__')
1160+
__lt__ = _indexOp('__lt__')
1161+
__gt__ = _indexOp('__gt__')
1162+
__le__ = _indexOp('__le__')
1163+
__ge__ = _indexOp('__ge__')
1164+
11651165
def __and__(self, other):
11661166
return self.intersection(other)
11671167

@@ -2098,7 +2098,7 @@ def _invalid_op(self, other=None):
20982098
def _add_numeric_methods(cls):
20992099
""" add in numeric methods """
21002100

2101-
def _make_evaluate_binop(op, opstr):
2101+
def _make_evaluate_binop(op, opstr, reversed=False):
21022102

21032103
def _evaluate_numeric_binop(self, other):
21042104
import pandas.tseries.offsets as offsets
@@ -2128,7 +2128,13 @@ def _evaluate_numeric_binop(self, other):
21282128
else:
21292129
if not (com.is_float(other) or com.is_integer(other)):
21302130
raise TypeError("can only perform ops with scalar values")
2131-
return self._shallow_copy(op(self.values, other))
2131+
2132+
# if we are a reversed non-communative op
2133+
values = self.values
2134+
if reversed:
2135+
values, other = other, values
2136+
2137+
return self._shallow_copy(op(values, other))
21322138

21332139
return _evaluate_numeric_binop
21342140

@@ -2145,16 +2151,23 @@ def _evaluate_numeric_unary(self):
21452151

21462152
return _evaluate_numeric_unary
21472153

2154+
cls.__add__ = cls.__radd__ = _make_evaluate_binop(operator.add,'__add__')
2155+
cls.__sub__ = _make_evaluate_binop(operator.sub,'__sub__')
2156+
cls.__rsub__ = _make_evaluate_binop(operator.sub,'__sub__',reversed=True)
21482157
cls.__mul__ = cls.__rmul__ = _make_evaluate_binop(operator.mul,'__mul__')
2149-
cls.__floordiv__ = cls.__rfloordiv__ = _make_evaluate_binop(operator.floordiv,'__floordiv__')
2150-
cls.__truediv__ = cls.__rtruediv__ = _make_evaluate_binop(operator.truediv,'__truediv__')
2158+
cls.__floordiv__ = _make_evaluate_binop(operator.floordiv,'__floordiv__')
2159+
cls.__rfloordiv__ = _make_evaluate_binop(operator.floordiv,'__floordiv__',reversed=True)
2160+
cls.__truediv__ = _make_evaluate_binop(operator.truediv,'__truediv__')
2161+
cls.__rtruediv__ = _make_evaluate_binop(operator.truediv,'__truediv__',reversed=True)
21512162
if not compat.PY3:
2152-
cls.__div__ = cls.__rdiv__ = _make_evaluate_binop(operator.div,'__div__')
2163+
cls.__div__ = _make_evaluate_binop(operator.div,'__div__')
2164+
cls.__rdiv__ = _make_evaluate_binop(operator.div,'__div__',reversed=True)
21532165
cls.__neg__ = _make_evaluate_unary(lambda x: -x,'__neg__')
21542166
cls.__pos__ = _make_evaluate_unary(lambda x: x,'__pos__')
21552167
cls.__abs__ = _make_evaluate_unary(lambda x: np.abs(x),'__abs__')
21562168
cls.__inv__ = _make_evaluate_unary(lambda x: -x,'__inv__')
21572169

2170+
21582171
Index._add_numeric_methods_disabled()
21592172

21602173
class NumericIndex(Index):
@@ -2166,7 +2179,6 @@ class NumericIndex(Index):
21662179
"""
21672180
_is_numeric_dtype = True
21682181

2169-
21702182
class Int64Index(NumericIndex):
21712183

21722184
"""

pandas/stats/plm.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def _add_entity_effects(self, panel):
240240

241241
self.log('-- Excluding dummy for entity: %s' % to_exclude)
242242

243-
dummies = dummies.filter(dummies.columns - [to_exclude])
243+
dummies = dummies.filter(dummies.columns.difference([to_exclude]))
244244

245245
dummies = dummies.add_prefix('FE_')
246246
panel = panel.join(dummies)
@@ -286,7 +286,7 @@ def _add_categorical_dummies(self, panel, cat_mappings):
286286
self.log(
287287
'-- Excluding dummy for %s: %s' % (effect, to_exclude))
288288

289-
dummies = dummies.filter(dummies.columns - [mapped_name])
289+
dummies = dummies.filter(dummies.columns.difference([mapped_name]))
290290
dropped_dummy = True
291291

292292
dummies = _convertDummies(dummies, cat_mappings.get(effect))

pandas/tests/test_index.py

+28-3
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ def test_add(self):
595595

596596
# - API change GH 8226
597597
with tm.assert_produces_warning():
598-
self.strIndex + self.dateIndex
598+
self.strIndex + self.strIndex
599599

600600
firstCat = self.strIndex.union(self.dateIndex)
601601
secondCat = self.strIndex.union(self.strIndex)
@@ -729,7 +729,7 @@ def test_symmetric_diff(self):
729729

730730
# other isn't iterable
731731
with tm.assertRaises(TypeError):
732-
idx1 - 1
732+
Index(idx1,dtype='object') - 1
733733

734734
def test_pickle(self):
735735

@@ -1132,12 +1132,37 @@ def test_numeric_compat(self):
11321132
tm.assert_index_equal(result,
11331133
Float64Index(np.arange(5,dtype='float64')*(np.arange(5,dtype='float64')+0.1)))
11341134

1135-
11361135
# invalid
11371136
self.assertRaises(TypeError, lambda : idx * date_range('20130101',periods=5))
11381137
self.assertRaises(ValueError, lambda : idx * self._holder(np.arange(3)))
11391138
self.assertRaises(ValueError, lambda : idx * np.array([1,2]))
11401139

1140+
1141+
def test_explicit_conversions(self):
1142+
1143+
# GH 8608
1144+
# add/sub are overriden explicity for Float/Int Index
1145+
idx = self._holder(np.arange(5,dtype='int64'))
1146+
1147+
# float conversions
1148+
arr = np.arange(5,dtype='int64')*3.2
1149+
expected = Float64Index(arr)
1150+
fidx = idx * 3.2
1151+
tm.assert_index_equal(fidx,expected)
1152+
fidx = 3.2 * idx
1153+
tm.assert_index_equal(fidx,expected)
1154+
1155+
# interops with numpy arrays
1156+
expected = Float64Index(arr)
1157+
a = np.zeros(5,dtype='float64')
1158+
result = fidx - a
1159+
tm.assert_index_equal(result,expected)
1160+
1161+
expected = Float64Index(-arr)
1162+
a = np.zeros(5,dtype='float64')
1163+
result = a - fidx
1164+
tm.assert_index_equal(result,expected)
1165+
11411166
def test_ufunc_compat(self):
11421167
idx = self._holder(np.arange(5,dtype='int64'))
11431168
result = np.sin(idx)

pandas/tseries/base.py

+50-46
Original file line numberDiff line numberDiff line change
@@ -317,50 +317,56 @@ def _add_datelike(self, other):
317317
def _sub_datelike(self, other):
318318
raise NotImplementedError
319319

320-
def __add__(self, other):
321-
from pandas.core.index import Index
322-
from pandas.tseries.tdi import TimedeltaIndex
323-
from pandas.tseries.offsets import DateOffset
324-
if isinstance(other, TimedeltaIndex):
325-
return self._add_delta(other)
326-
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
327-
if hasattr(other,'_add_delta'):
328-
return other._add_delta(self)
329-
raise TypeError("cannot add TimedeltaIndex and {typ}".format(typ=type(other)))
330-
elif isinstance(other, Index):
331-
return self.union(other)
332-
elif isinstance(other, (DateOffset, timedelta, np.timedelta64, tslib.Timedelta)):
333-
return self._add_delta(other)
334-
elif com.is_integer(other):
335-
return self.shift(other)
336-
elif isinstance(other, (tslib.Timestamp, datetime)):
337-
return self._add_datelike(other)
338-
else: # pragma: no cover
339-
return NotImplemented
340-
341-
def __sub__(self, other):
342-
from pandas.core.index import Index
343-
from pandas.tseries.tdi import TimedeltaIndex
344-
from pandas.tseries.offsets import DateOffset
345-
if isinstance(other, TimedeltaIndex):
346-
return self._add_delta(-other)
347-
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
348-
if not isinstance(other, TimedeltaIndex):
349-
raise TypeError("cannot subtract TimedeltaIndex and {typ}".format(typ=type(other)))
350-
return self._add_delta(-other)
351-
elif isinstance(other, Index):
352-
return self.difference(other)
353-
elif isinstance(other, (DateOffset, timedelta, np.timedelta64, tslib.Timedelta)):
354-
return self._add_delta(-other)
355-
elif com.is_integer(other):
356-
return self.shift(-other)
357-
elif isinstance(other, (tslib.Timestamp, datetime)):
358-
return self._sub_datelike(other)
359-
else: # pragma: no cover
360-
return NotImplemented
361-
362-
__iadd__ = __add__
363-
__isub__ = __sub__
320+
@classmethod
321+
def _add_datetimelike_methods(cls):
322+
""" add in the datetimelike methods (as we may have to override the superclass) """
323+
324+
def __add__(self, other):
325+
from pandas.core.index import Index
326+
from pandas.tseries.tdi import TimedeltaIndex
327+
from pandas.tseries.offsets import DateOffset
328+
if isinstance(other, TimedeltaIndex):
329+
return self._add_delta(other)
330+
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
331+
if hasattr(other,'_add_delta'):
332+
return other._add_delta(self)
333+
raise TypeError("cannot add TimedeltaIndex and {typ}".format(typ=type(other)))
334+
elif isinstance(other, Index):
335+
return self.union(other)
336+
elif isinstance(other, (DateOffset, timedelta, np.timedelta64, tslib.Timedelta)):
337+
return self._add_delta(other)
338+
elif com.is_integer(other):
339+
return self.shift(other)
340+
elif isinstance(other, (tslib.Timestamp, datetime)):
341+
return self._add_datelike(other)
342+
else: # pragma: no cover
343+
return NotImplemented
344+
cls.__add__ = __add__
345+
346+
def __sub__(self, other):
347+
from pandas.core.index import Index
348+
from pandas.tseries.tdi import TimedeltaIndex
349+
from pandas.tseries.offsets import DateOffset
350+
if isinstance(other, TimedeltaIndex):
351+
return self._add_delta(-other)
352+
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
353+
if not isinstance(other, TimedeltaIndex):
354+
raise TypeError("cannot subtract TimedeltaIndex and {typ}".format(typ=type(other)))
355+
return self._add_delta(-other)
356+
elif isinstance(other, Index):
357+
return self.difference(other)
358+
elif isinstance(other, (DateOffset, timedelta, np.timedelta64, tslib.Timedelta)):
359+
return self._add_delta(-other)
360+
elif com.is_integer(other):
361+
return self.shift(-other)
362+
elif isinstance(other, (tslib.Timestamp, datetime)):
363+
return self._sub_datelike(other)
364+
else: # pragma: no cover
365+
return NotImplemented
366+
cls.__sub__ = __sub__
367+
368+
cls.__iadd__ = __add__
369+
cls.__isub__ = __sub__
364370

365371
def _add_delta(self, other):
366372
return NotImplemented
@@ -469,5 +475,3 @@ def repeat(self, repeats, axis=None):
469475
"""
470476
return self._simple_new(self.values.repeat(repeats),
471477
name=self.name)
472-
473-

pandas/tseries/index.py

+1
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,7 @@ def to_julian_date(self):
16651665
self.nanosecond/3600.0/1e+9
16661666
)/24.0)
16671667
DatetimeIndex._add_numeric_methods_disabled()
1668+
DatetimeIndex._add_datetimelike_methods()
16681669

16691670
def _generate_regular_range(start, end, periods, offset):
16701671
if isinstance(offset, Tick):

pandas/tseries/period.py

+1
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,7 @@ def tz_localize(self, tz, infer_dst=False):
12631263
raise NotImplementedError("Not yet implemented for PeriodIndex")
12641264

12651265
PeriodIndex._add_numeric_methods_disabled()
1266+
PeriodIndex._add_datetimelike_methods()
12661267

12671268
def _get_ordinal_range(start, end, periods, freq):
12681269
if com._count_not_none(start, end, periods) < 2:

pandas/tseries/tdi.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ def delete(self, loc):
898898
return TimedeltaIndex(new_tds, name=self.name, freq=freq)
899899

900900
TimedeltaIndex._add_numeric_methods()
901+
TimedeltaIndex._add_datetimelike_methods()
901902

902903
def _is_convertible_to_index(other):
903904
""" return a boolean whether I can attempt conversion to a TimedeltaIndex """
@@ -978,5 +979,3 @@ def timedelta_range(start=None, end=None, periods=None, freq='D',
978979
return TimedeltaIndex(start=start, end=end, periods=periods,
979980
freq=freq, name=name,
980981
closed=closed)
981-
982-

0 commit comments

Comments
 (0)