Skip to content

Commit 2fcbc7f

Browse files
committed
BUG: bug in Float/Int index with ndarray for add/sub numeric ops (GH8608)
1 parent 85069eb commit 2fcbc7f

File tree

8 files changed

+109
-67
lines changed

8 files changed

+109
-67
lines changed

doc/source/whatsnew/v0.15.1.txt

Lines changed: 1 addition & 1 deletion
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

Lines changed: 25 additions & 13 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 28 additions & 3 deletions
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

Lines changed: 50 additions & 46 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 2 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy