Skip to content

Commit f0cd23c

Browse files
jbrockmendeljreback
authored andcommitted
Remove timeop (#19277)
1 parent 1c0a48c commit f0cd23c

File tree

5 files changed

+28
-313
lines changed

5 files changed

+28
-313
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ Other API Changes
311311
- :func:`Series.to_csv` now accepts a ``compression`` argument that works in the same way as the ``compression`` argument in :func:`DataFrame.to_csv` (:issue:`18958`)
312312
- Addition or subtraction of ``NaT`` from :class:`TimedeltaIndex` will return ``TimedeltaIndex`` instead of ``DatetimeIndex`` (:issue:`19124`)
313313
- :func:`DatetimeIndex.shift` and :func:`TimedeltaIndex.shift` will now raise ``NullFrequencyError`` (which subclasses ``ValueError``, which was raised in older versions) when the index object frequency is ``None`` (:issue:`19147`)
314+
- Addition and subtraction of ``NaN`` from a :class:`Series` with ``dtype='timedelta64[ns]'`` will raise a ``TypeError` instead of treating the ``NaN`` as ``NaT`` (:issue:`19274`)
314315

315316
.. _whatsnew_0230.deprecations:
316317

pandas/core/indexes/timedeltas.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ def _evaluate_with_timedelta_like(self, other, op, opstr, reversed=False):
393393
if opstr in ['__floordiv__']:
394394
result = left // right
395395
else:
396-
result = op(left, float(right))
396+
result = op(left, np.float64(right))
397397
result = self._maybe_mask_results(result, convert='float64')
398398
return Index(result, name=self.name, copy=False)
399399

pandas/core/ops.py

+17-303
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,30 @@
66
# necessary to enforce truediv in Python 2.X
77
from __future__ import division
88
import operator
9-
import warnings
9+
1010
import numpy as np
1111
import pandas as pd
12-
import datetime
1312

1413
from pandas._libs import (lib, index as libindex,
15-
tslib as libts, algos as libalgos, iNaT)
14+
algos as libalgos)
1615

1716
from pandas import compat
1817
from pandas.util._decorators import Appender
1918

2019
from pandas.compat import bind_method
2120
import pandas.core.missing as missing
2221

23-
from pandas.errors import PerformanceWarning, NullFrequencyError
22+
from pandas.errors import NullFrequencyError
2423
from pandas.core.common import _values_from_object, _maybe_match_name
2524
from pandas.core.dtypes.missing import notna, isna
2625
from pandas.core.dtypes.common import (
2726
needs_i8_conversion,
2827
is_datetimelike_v_numeric,
2928
is_integer_dtype, is_categorical_dtype,
3029
is_object_dtype, is_timedelta64_dtype,
31-
is_datetime64_dtype, is_datetime64tz_dtype, is_datetime64_ns_dtype,
32-
is_bool_dtype, is_datetimetz,
33-
is_list_like, is_offsetlike,
30+
is_datetime64_dtype, is_datetime64tz_dtype,
31+
is_bool_dtype,
32+
is_list_like,
3433
is_scalar,
3534
_ensure_object)
3635
from pandas.core.dtypes.cast import (
@@ -39,7 +38,7 @@
3938
from pandas.core.dtypes.generic import (
4039
ABCSeries,
4140
ABCDataFrame,
42-
ABCIndex, ABCDatetimeIndex,
41+
ABCIndex,
4342
ABCPeriodIndex)
4443

4544
# -----------------------------------------------------------------------------
@@ -294,287 +293,6 @@ def add_flex_arithmetic_methods(cls, flex_arith_method,
294293
exclude=exclude)
295294

296295

297-
class _Op(object):
298-
299-
"""
300-
Wrapper around Series arithmetic operations.
301-
Generally, you should use classmethod ``_Op.get_op`` as an entry point.
302-
303-
This validates and coerces lhs and rhs depending on its dtype and
304-
based on op. See _TimeOp also.
305-
306-
Parameters
307-
----------
308-
left : Series
309-
lhs of op
310-
right : object
311-
rhs of op
312-
name : str
313-
name of op
314-
na_op : callable
315-
a function which wraps op
316-
"""
317-
318-
fill_value = np.nan
319-
wrap_results = staticmethod(lambda x: x)
320-
dtype = None
321-
322-
def __init__(self, left, right, name, na_op):
323-
self.left = left
324-
self.right = right
325-
326-
self.name = name
327-
self.na_op = na_op
328-
329-
self.lvalues = left
330-
self.rvalues = right
331-
332-
@classmethod
333-
def get_op(cls, left, right, name, na_op):
334-
"""
335-
Get op dispatcher, returns _Op or _TimeOp.
336-
337-
If ``left`` and ``right`` are appropriate for datetime arithmetic with
338-
operation ``name``, processes them and returns a ``_TimeOp`` object
339-
that stores all the required values. Otherwise, it will generate
340-
either a ``_Op``, indicating that the operation is performed via
341-
normal numpy path.
342-
"""
343-
is_timedelta_lhs = is_timedelta64_dtype(left)
344-
345-
if not is_timedelta_lhs:
346-
return _Op(left, right, name, na_op)
347-
else:
348-
return _TimeOp(left, right, name, na_op)
349-
350-
351-
class _TimeOp(_Op):
352-
"""
353-
Wrapper around Series datetime/time/timedelta arithmetic operations.
354-
Generally, you should use classmethod ``_Op.get_op`` as an entry point.
355-
"""
356-
fill_value = iNaT
357-
358-
def __init__(self, left, right, name, na_op):
359-
super(_TimeOp, self).__init__(left, right, name, na_op)
360-
361-
lvalues = self._convert_to_array(left, name=name)
362-
rvalues = self._convert_to_array(right, name=name, other=lvalues)
363-
364-
# left
365-
self.is_timedelta_lhs = is_timedelta64_dtype(lvalues)
366-
assert self.is_timedelta_lhs
367-
368-
# right
369-
self.is_offset_rhs = is_offsetlike(right)
370-
self.is_datetime64_rhs = is_datetime64_dtype(rvalues)
371-
self.is_datetime64tz_rhs = is_datetime64tz_dtype(rvalues)
372-
self.is_datetime_rhs = (self.is_datetime64_rhs or
373-
self.is_datetime64tz_rhs)
374-
self.is_timedelta_rhs = is_timedelta64_dtype(rvalues)
375-
self.is_integer_rhs = rvalues.dtype.kind in ('i', 'u')
376-
self.is_floating_rhs = rvalues.dtype.kind == 'f'
377-
378-
self._validate(lvalues, rvalues, name)
379-
self.lvalues, self.rvalues = self._convert_for_datetime(lvalues,
380-
rvalues)
381-
382-
def _validate_timedelta(self, name):
383-
# assumes self.is_timedelta_lhs
384-
385-
if self.is_integer_rhs or self.is_floating_rhs:
386-
# timedelta and integer mul/div
387-
self._check_timedelta_with_numeric(name)
388-
elif self.is_timedelta_rhs or self.is_offset_rhs:
389-
# 2 timedeltas
390-
if name not in ('__div__', '__rdiv__', '__truediv__',
391-
'__rtruediv__', '__add__', '__radd__', '__sub__',
392-
'__rsub__', '__floordiv__', '__rfloordiv__'):
393-
raise TypeError("can only operate on a timedeltas for addition"
394-
", subtraction, and division, but the operator"
395-
" [{name}] was passed".format(name=name))
396-
elif self.is_datetime_rhs:
397-
if name not in ('__add__', '__radd__', '__rsub__'):
398-
raise TypeError("can only operate on a timedelta/DateOffset "
399-
"with a rhs of a datetime for addition, "
400-
"but the operator [{name}] was passed"
401-
.format(name=name))
402-
else:
403-
raise TypeError('cannot operate on a series without a rhs '
404-
'of a series/ndarray of type datetime64[ns] '
405-
'or a timedelta')
406-
407-
def _validate(self, lvalues, rvalues, name):
408-
return self._validate_timedelta(name)
409-
410-
def _check_timedelta_with_numeric(self, name):
411-
if name not in ('__div__', '__truediv__', '__mul__', '__rmul__'):
412-
raise TypeError("can only operate on a timedelta and an "
413-
"integer or a float for division and "
414-
"multiplication, but the operator [{name}] "
415-
"was passed".format(name=name))
416-
417-
def _convert_to_array(self, values, name=None, other=None):
418-
"""converts values to ndarray"""
419-
from pandas.core.tools.timedeltas import to_timedelta
420-
421-
ovalues = values
422-
supplied_dtype = None
423-
if not is_list_like(values):
424-
values = np.array([values])
425-
426-
# if this is a Series that contains relevant dtype info, then use this
427-
# instead of the inferred type; this avoids coercing Series([NaT],
428-
# dtype='datetime64[ns]') to Series([NaT], dtype='timedelta64[ns]')
429-
elif (isinstance(values, (pd.Series, ABCDatetimeIndex)) and
430-
(is_timedelta64_dtype(values) or is_datetime64_dtype(values))):
431-
supplied_dtype = values.dtype
432-
433-
inferred_type = lib.infer_dtype(values)
434-
if (inferred_type in ('datetime64', 'datetime', 'date', 'time') or
435-
is_datetimetz(inferred_type)):
436-
# if we have a other of timedelta, but use pd.NaT here we
437-
# we are in the wrong path
438-
if (supplied_dtype is None and other is not None and
439-
(other.dtype in ('timedelta64[ns]', 'datetime64[ns]')) and
440-
isna(values).all()):
441-
values = np.empty(values.shape, dtype='timedelta64[ns]')
442-
values[:] = iNaT
443-
444-
elif isinstance(values, ABCDatetimeIndex):
445-
# a datelike
446-
pass
447-
elif isinstance(ovalues, datetime.datetime):
448-
# datetime scalar
449-
values = pd.DatetimeIndex(values)
450-
# datetime array with tz
451-
elif is_datetimetz(values):
452-
if isinstance(values, ABCSeries):
453-
values = values._values
454-
elif not (isinstance(values, (np.ndarray, ABCSeries)) and
455-
is_datetime64_dtype(values)):
456-
values = libts.array_to_datetime(values)
457-
elif (is_datetime64_dtype(values) and
458-
not is_datetime64_ns_dtype(values)):
459-
# GH#7996 e.g. np.datetime64('2013-01-01') is datetime64[D]
460-
values = values.astype('datetime64[ns]')
461-
462-
elif inferred_type in ('timedelta', 'timedelta64'):
463-
# have a timedelta, convert to to ns here
464-
values = to_timedelta(values, errors='coerce', box=False)
465-
if isinstance(other, ABCDatetimeIndex):
466-
# GH#13905
467-
# Defer to DatetimeIndex/TimedeltaIndex operations where
468-
# timezones are handled carefully.
469-
values = pd.TimedeltaIndex(values)
470-
elif inferred_type == 'integer':
471-
# py3 compat where dtype is 'm' but is an integer
472-
if values.dtype.kind == 'm':
473-
values = values.astype('timedelta64[ns]')
474-
elif isinstance(values, pd.PeriodIndex):
475-
values = values.to_timestamp().to_series()
476-
elif name not in ('__truediv__', '__div__', '__mul__', '__rmul__'):
477-
raise TypeError("incompatible type for a datetime/timedelta "
478-
"operation [{name}]".format(name=name))
479-
elif inferred_type == 'floating':
480-
if (isna(values).all() and
481-
name in ('__add__', '__radd__', '__sub__', '__rsub__')):
482-
values = np.empty(values.shape, dtype=other.dtype)
483-
values[:] = iNaT
484-
return values
485-
elif is_offsetlike(values):
486-
return values
487-
else:
488-
raise TypeError("incompatible type [{dtype}] for a "
489-
"datetime/timedelta operation"
490-
.format(dtype=np.array(values).dtype))
491-
492-
return values
493-
494-
def _convert_for_datetime(self, lvalues, rvalues):
495-
from pandas.core.tools.timedeltas import to_timedelta
496-
497-
mask = isna(lvalues) | isna(rvalues)
498-
499-
# datetimes require views
500-
if self.is_datetime_rhs:
501-
502-
# datetime subtraction means timedelta
503-
if self.is_datetime64tz_rhs:
504-
self.dtype = rvalues.dtype
505-
else:
506-
self.dtype = 'datetime64[ns]'
507-
508-
# if adding single offset try vectorized path
509-
# in DatetimeIndex; otherwise elementwise apply
510-
def _offset(lvalues, rvalues):
511-
if len(lvalues) == 1:
512-
rvalues = pd.DatetimeIndex(rvalues)
513-
lvalues = lvalues[0]
514-
else:
515-
warnings.warn("Adding/subtracting array of DateOffsets to "
516-
"Series not vectorized", PerformanceWarning)
517-
rvalues = rvalues.astype('O')
518-
519-
# pass thru on the na_op
520-
self.na_op = lambda x, y: getattr(x, self.name)(y)
521-
return lvalues, rvalues
522-
523-
if self.is_offset_rhs:
524-
rvalues, lvalues = _offset(rvalues, lvalues)
525-
else:
526-
527-
# with tz, convert to UTC
528-
if self.is_datetime64tz_rhs:
529-
rvalues = rvalues.tz_convert('UTC').tz_localize(None)
530-
531-
lvalues = lvalues.view(np.int64)
532-
rvalues = rvalues.view(np.int64)
533-
534-
# otherwise it's a timedelta
535-
else:
536-
537-
self.dtype = 'timedelta64[ns]'
538-
539-
# convert Tick DateOffset to underlying delta
540-
if self.is_offset_rhs:
541-
rvalues = to_timedelta(rvalues, box=False)
542-
543-
lvalues = lvalues.astype(np.int64)
544-
if not self.is_floating_rhs:
545-
rvalues = rvalues.astype(np.int64)
546-
547-
# time delta division -> unit less
548-
# integer gets converted to timedelta in np < 1.6
549-
if ((self.is_timedelta_lhs and self.is_timedelta_rhs) and
550-
not self.is_integer_rhs and
551-
self.name in ('__div__', '__rdiv__',
552-
'__truediv__', '__rtruediv__',
553-
'__floordiv__', '__rfloordiv__')):
554-
self.dtype = 'float64'
555-
self.fill_value = np.nan
556-
lvalues = lvalues.astype(np.float64)
557-
rvalues = rvalues.astype(np.float64)
558-
559-
# if we need to mask the results
560-
if mask.any():
561-
562-
def f(x):
563-
564-
# datetime64[ns]/timedelta64[ns] masking
565-
try:
566-
x = np.array(x, dtype=self.dtype)
567-
except TypeError:
568-
x = np.array(x, dtype='datetime64[ns]')
569-
570-
np.putmask(x, mask, self.fill_value)
571-
return x
572-
573-
self.wrap_results = f
574-
575-
return lvalues, rvalues
576-
577-
578296
def _align_method_SERIES(left, right, align_asobject=False):
579297
""" align lhs and rhs Series """
580298

@@ -678,26 +396,22 @@ def wrapper(left, right, name=name, na_op=na_op):
678396
index=left.index, name=res_name,
679397
dtype=result.dtype)
680398

681-
converted = _Op.get_op(left, right, name, na_op)
682-
683-
lvalues, rvalues = converted.lvalues, converted.rvalues
684-
dtype = converted.dtype
685-
wrap_results = converted.wrap_results
686-
na_op = converted.na_op
399+
elif is_timedelta64_dtype(left):
400+
result = dispatch_to_index_op(op, left, right, pd.TimedeltaIndex)
401+
res_name = _get_series_op_result_name(left, right)
402+
return construct_result(left, result,
403+
index=left.index, name=res_name,
404+
dtype=result.dtype)
687405

406+
lvalues = left.values
407+
rvalues = right
688408
if isinstance(rvalues, ABCSeries):
689-
lvalues = getattr(lvalues, 'values', lvalues)
690409
rvalues = getattr(rvalues, 'values', rvalues)
691-
# _Op aligns left and right
692-
else:
693-
if (hasattr(lvalues, 'values') and
694-
not isinstance(lvalues, ABCDatetimeIndex)):
695-
lvalues = lvalues.values
696410

697-
result = wrap_results(safe_na_op(lvalues, rvalues))
411+
result = safe_na_op(lvalues, rvalues)
698412
res_name = _get_series_op_result_name(left, right)
699413
return construct_result(left, result,
700-
index=left.index, name=res_name, dtype=dtype)
414+
index=left.index, name=res_name, dtype=None)
701415

702416
return wrapper
703417

0 commit comments

Comments
 (0)