Skip to content

Commit e8b80b1

Browse files
jbrockmendeljreback
authored andcommitted
De-duplicate add_offset_array methods (#19835)
1 parent bd76ce9 commit e8b80b1

File tree

4 files changed

+70
-111
lines changed

4 files changed

+70
-111
lines changed

Diff for: pandas/core/indexes/datetimelike.py

+65-32
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
Base and utility classes for tseries type pandas objects.
33
"""
44
import warnings
5-
5+
import operator
66
from datetime import datetime, timedelta
77

88
from pandas import compat
99
from pandas.compat.numpy import function as nv
1010
from pandas.core.tools.timedeltas import to_timedelta
1111

1212
import numpy as np
13+
14+
from pandas._libs import lib, iNaT, NaT
15+
from pandas._libs.tslibs.period import Period
16+
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
17+
from pandas._libs.tslibs.timestamps import round_ns
18+
1319
from pandas.core.dtypes.common import (
1420
_ensure_int64,
1521
is_dtype_equal,
@@ -25,18 +31,15 @@
2531
is_integer_dtype,
2632
is_object_dtype,
2733
is_string_dtype,
34+
is_period_dtype,
2835
is_timedelta64_dtype)
2936
from pandas.core.dtypes.generic import (
3037
ABCIndex, ABCSeries, ABCPeriodIndex, ABCIndexClass)
3138
from pandas.core.dtypes.missing import isna
3239
from pandas.core import common as com, algorithms, ops
3340
from pandas.core.algorithms import checked_add_with_arr
34-
from pandas.errors import NullFrequencyError
41+
from pandas.errors import NullFrequencyError, PerformanceWarning
3542
import pandas.io.formats.printing as printing
36-
from pandas._libs import lib, iNaT, NaT
37-
from pandas._libs.tslibs.period import Period
38-
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
39-
from pandas._libs.tslibs.timestamps import round_ns
4043

4144
from pandas.core.indexes.base import Index, _index_shared_docs
4245
from pandas.util._decorators import Appender, cache_readonly
@@ -637,13 +640,33 @@ def _sub_datelike(self, other):
637640
def _sub_period(self, other):
638641
return NotImplemented
639642

640-
def _add_offset_array(self, other):
641-
# Array/Index of DateOffset objects
642-
return NotImplemented
643+
def _addsub_offset_array(self, other, op):
644+
"""
645+
Add or subtract array-like of DateOffset objects
643646
644-
def _sub_offset_array(self, other):
645-
# Array/Index of DateOffset objects
646-
return NotImplemented
647+
Parameters
648+
----------
649+
other : Index, np.ndarray
650+
object-dtype containing pd.DateOffset objects
651+
op : {operator.add, operator.sub}
652+
653+
Returns
654+
-------
655+
result : same class as self
656+
"""
657+
assert op in [operator.add, operator.sub]
658+
if len(other) == 1:
659+
return op(self, other[0])
660+
661+
warnings.warn("Adding/subtracting array of DateOffsets to "
662+
"{cls} not vectorized"
663+
.format(cls=type(self).__name__), PerformanceWarning)
664+
665+
res_values = op(self.astype('O').values, np.array(other))
666+
kwargs = {}
667+
if not is_period_dtype(self):
668+
kwargs['freq'] = 'infer'
669+
return self._constructor(res_values, **kwargs)
647670

648671
@classmethod
649672
def _add_datetimelike_methods(cls):
@@ -660,26 +683,31 @@ def __add__(self, other):
660683
other = lib.item_from_zerodim(other)
661684
if isinstance(other, ABCSeries):
662685
return NotImplemented
663-
elif is_timedelta64_dtype(other):
686+
687+
# scalar others
688+
elif isinstance(other, (DateOffset, timedelta, np.timedelta64)):
664689
result = self._add_delta(other)
665-
elif isinstance(other, (DateOffset, timedelta)):
690+
elif isinstance(other, (datetime, np.datetime64)):
691+
result = self._add_datelike(other)
692+
elif is_integer(other):
693+
# This check must come after the check for np.timedelta64
694+
# as is_integer returns True for these
695+
result = self.shift(other)
696+
697+
# array-like others
698+
elif is_timedelta64_dtype(other):
699+
# TimedeltaIndex, ndarray[timedelta64]
666700
result = self._add_delta(other)
667701
elif is_offsetlike(other):
668702
# Array/Index of DateOffset objects
669-
result = self._add_offset_array(other)
703+
result = self._addsub_offset_array(other, operator.add)
670704
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
671705
if hasattr(other, '_add_delta'):
672706
# i.e. DatetimeIndex, TimedeltaIndex, or PeriodIndex
673707
result = other._add_delta(self)
674708
else:
675709
raise TypeError("cannot add TimedeltaIndex and {typ}"
676710
.format(typ=type(other)))
677-
elif is_integer(other):
678-
# This check must come after the check for timedelta64_dtype
679-
# or else it will incorrectly catch np.timedelta64 objects
680-
result = self.shift(other)
681-
elif isinstance(other, (datetime, np.datetime64)):
682-
result = self._add_datelike(other)
683711
elif isinstance(other, Index):
684712
result = self._add_datelike(other)
685713
elif is_integer_dtype(other) and self.freq is None:
@@ -709,28 +737,33 @@ def __sub__(self, other):
709737
other = lib.item_from_zerodim(other)
710738
if isinstance(other, ABCSeries):
711739
return NotImplemented
712-
elif is_timedelta64_dtype(other):
740+
741+
# scalar others
742+
elif isinstance(other, (DateOffset, timedelta, np.timedelta64)):
713743
result = self._add_delta(-other)
714-
elif isinstance(other, (DateOffset, timedelta)):
744+
elif isinstance(other, (datetime, np.datetime64)):
745+
result = self._sub_datelike(other)
746+
elif is_integer(other):
747+
# This check must come after the check for np.timedelta64
748+
# as is_integer returns True for these
749+
result = self.shift(-other)
750+
elif isinstance(other, Period):
751+
result = self._sub_period(other)
752+
753+
# array-like others
754+
elif is_timedelta64_dtype(other):
755+
# TimedeltaIndex, ndarray[timedelta64]
715756
result = self._add_delta(-other)
716757
elif is_offsetlike(other):
717758
# Array/Index of DateOffset objects
718-
result = self._sub_offset_array(other)
759+
result = self._addsub_offset_array(other, operator.sub)
719760
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
720761
# We checked above for timedelta64_dtype(other) so this
721762
# must be invalid.
722763
raise TypeError("cannot subtract TimedeltaIndex and {typ}"
723764
.format(typ=type(other).__name__))
724765
elif isinstance(other, DatetimeIndex):
725766
result = self._sub_datelike(other)
726-
elif is_integer(other):
727-
# This check must come after the check for timedelta64_dtype
728-
# or else it will incorrectly catch np.timedelta64 objects
729-
result = self.shift(-other)
730-
elif isinstance(other, (datetime, np.datetime64)):
731-
result = self._sub_datelike(other)
732-
elif isinstance(other, Period):
733-
result = self._sub_period(other)
734767
elif isinstance(other, Index):
735768
raise TypeError("cannot subtract {typ1} and {typ2}"
736769
.format(typ1=type(self).__name__,

Diff for: pandas/core/indexes/datetimes.py

-23
Original file line numberDiff line numberDiff line change
@@ -964,29 +964,6 @@ def _add_offset(self, offset):
964964
"or DatetimeIndex", PerformanceWarning)
965965
return self.astype('O') + offset
966966

967-
def _add_offset_array(self, other):
968-
# Array/Index of DateOffset objects
969-
if len(other) == 1:
970-
return self + other[0]
971-
else:
972-
warnings.warn("Adding/subtracting array of DateOffsets to "
973-
"{} not vectorized".format(type(self)),
974-
PerformanceWarning)
975-
return self.astype('O') + np.array(other)
976-
# TODO: pass freq='infer' like we do in _sub_offset_array?
977-
# TODO: This works for __add__ but loses dtype in __sub__
978-
979-
def _sub_offset_array(self, other):
980-
# Array/Index of DateOffset objects
981-
if len(other) == 1:
982-
return self - other[0]
983-
else:
984-
warnings.warn("Adding/subtracting array of DateOffsets to "
985-
"{} not vectorized".format(type(self)),
986-
PerformanceWarning)
987-
res_values = self.astype('O').values - np.array(other)
988-
return self.__class__(res_values, freq='infer')
989-
990967
def _format_native_types(self, na_rep='NaT', date_format=None, **kwargs):
991968
from pandas.io.formats.format import _get_format_datetime64_from_values
992969
format = _get_format_datetime64_from_values(self, date_format)

Diff for: pandas/core/indexes/period.py

-23
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
from pandas.util._decorators import (Appender, Substitution, cache_readonly,
4545
deprecate_kwarg)
4646
from pandas.compat import zip, u
47-
from pandas.errors import PerformanceWarning
4847

4948
import pandas.core.indexes.base as ibase
5049
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
@@ -745,28 +744,6 @@ def _sub_period(self, other):
745744
# result must be Int64Index or Float64Index
746745
return Index(new_data)
747746

748-
def _add_offset_array(self, other):
749-
# Array/Index of DateOffset objects
750-
if len(other) == 1:
751-
return self + other[0]
752-
else:
753-
warnings.warn("Adding/subtracting array of DateOffsets to "
754-
"{cls} not vectorized"
755-
.format(cls=type(self).__name__), PerformanceWarning)
756-
res_values = self.astype('O').values + np.array(other)
757-
return self.__class__(res_values)
758-
759-
def _sub_offset_array(self, other):
760-
# Array/Index of DateOffset objects
761-
if len(other) == 1:
762-
return self - other[0]
763-
else:
764-
warnings.warn("Adding/subtracting array of DateOffsets to "
765-
"{cls} not vectorized"
766-
.format(cls=type(self).__name__), PerformanceWarning)
767-
res_values = self.astype('O').values - np.array(other)
768-
return self.__class__(res_values)
769-
770747
def shift(self, n):
771748
"""
772749
Specialized shift which produces an PeriodIndex

Diff for: pandas/core/indexes/timedeltas.py

+5-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
""" implement the TimedeltaIndex """
22

33
from datetime import timedelta
4-
import warnings
54

65
import numpy as np
76
from pandas.core.dtypes.common import (
@@ -433,43 +432,16 @@ def _sub_datelike(self, other):
433432
else:
434433
raise TypeError("cannot subtract a datelike from a TimedeltaIndex")
435434

436-
def _add_offset_array(self, other):
437-
# Array/Index of DateOffset objects
435+
def _addsub_offset_array(self, other, op):
436+
# Add or subtract Array-like of DateOffset objects
438437
try:
439438
# TimedeltaIndex can only operate with a subset of DateOffset
440439
# subclasses. Incompatible classes will raise AttributeError,
441440
# which we re-raise as TypeError
442-
if len(other) == 1:
443-
return self + other[0]
444-
else:
445-
from pandas.errors import PerformanceWarning
446-
warnings.warn("Adding/subtracting array of DateOffsets to "
447-
"{} not vectorized".format(type(self)),
448-
PerformanceWarning)
449-
return self.astype('O') + np.array(other)
450-
# TODO: pass freq='infer' like we do in _sub_offset_array?
451-
# TODO: This works for __add__ but loses dtype in __sub__
452-
except AttributeError:
453-
raise TypeError("Cannot add non-tick DateOffset to TimedeltaIndex")
454-
455-
def _sub_offset_array(self, other):
456-
# Array/Index of DateOffset objects
457-
try:
458-
# TimedeltaIndex can only operate with a subset of DateOffset
459-
# subclasses. Incompatible classes will raise AttributeError,
460-
# which we re-raise as TypeError
461-
if len(other) == 1:
462-
return self - other[0]
463-
else:
464-
from pandas.errors import PerformanceWarning
465-
warnings.warn("Adding/subtracting array of DateOffsets to "
466-
"{} not vectorized".format(type(self)),
467-
PerformanceWarning)
468-
res_values = self.astype('O').values - np.array(other)
469-
return self.__class__(res_values, freq='infer')
441+
return DatetimeIndexOpsMixin._addsub_offset_array(self, other, op)
470442
except AttributeError:
471-
raise TypeError("Cannot subtrack non-tick DateOffset from"
472-
" TimedeltaIndex")
443+
raise TypeError("Cannot add/subtract non-tick DateOffset to {cls}"
444+
.format(cls=type(self).__name__))
473445

474446
def _format_native_types(self, na_rep=u('NaT'),
475447
date_format=None, **kwargs):

0 commit comments

Comments
 (0)