|
2 | 2 | from __future__ import division
|
3 | 3 |
|
4 | 4 | from datetime import timedelta
|
5 |
| -import operator |
6 | 5 | import warnings
|
7 | 6 |
|
8 | 7 | import numpy as np
|
|
17 | 16 |
|
18 | 17 | from pandas.core.dtypes.common import (
|
19 | 18 | _TD_DTYPE, ensure_int64, is_datetime64_dtype, is_float_dtype,
|
20 |
| - is_integer_dtype, is_list_like, is_object_dtype, is_string_dtype, |
21 |
| - is_timedelta64_dtype) |
| 19 | + is_integer_dtype, is_list_like, is_object_dtype, is_scalar, |
| 20 | + is_string_dtype, is_timedelta64_dtype) |
22 | 21 | from pandas.core.dtypes.generic import (
|
23 | 22 | ABCDataFrame, ABCIndexClass, ABCSeries, ABCTimedeltaIndex)
|
24 | 23 | from pandas.core.dtypes.missing import isna
|
25 | 24 |
|
26 |
| -from pandas.core import ops |
27 | 25 | from pandas.core.algorithms import checked_add_with_arr, unique1d
|
28 | 26 | import pandas.core.common as com
|
29 | 27 |
|
@@ -106,29 +104,6 @@ def wrapper(self, other):
|
106 | 104 | return compat.set_function_name(wrapper, opname, cls)
|
107 | 105 |
|
108 | 106 |
|
109 |
| -def _wrap_tdi_op(op): |
110 |
| - """ |
111 |
| - Instead of re-implementing multiplication/division etc operations |
112 |
| - in the Array class, for now we dispatch to the TimedeltaIndex |
113 |
| - implementations. |
114 |
| - """ |
115 |
| - # TODO: implement directly here and wrap in TimedeltaIndex, instead of |
116 |
| - # the other way around |
117 |
| - def method(self, other): |
118 |
| - if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
119 |
| - return NotImplemented |
120 |
| - |
121 |
| - from pandas import TimedeltaIndex |
122 |
| - obj = TimedeltaIndex(self) |
123 |
| - result = op(obj, other) |
124 |
| - if is_timedelta64_dtype(result): |
125 |
| - return type(self)(result) |
126 |
| - return np.array(result) |
127 |
| - |
128 |
| - method.__name__ = '__{name}__'.format(name=op.__name__) |
129 |
| - return method |
130 |
| - |
131 |
| - |
132 | 107 | class TimedeltaArrayMixin(dtl.DatetimeLikeArrayMixin, dtl.TimelikeOps):
|
133 | 108 | _typ = "timedeltaarray"
|
134 | 109 | __array_priority__ = 1000
|
@@ -332,37 +307,41 @@ def _addsub_offset_array(self, other, op):
|
332 | 307 | raise TypeError("Cannot add/subtract non-tick DateOffset to {cls}"
|
333 | 308 | .format(cls=type(self).__name__))
|
334 | 309 |
|
335 |
| - def _evaluate_with_timedelta_like(self, other, op): |
336 |
| - if isinstance(other, ABCSeries): |
337 |
| - # GH#19042 |
| 310 | + def __mul__(self, other): |
| 311 | + other = lib.item_from_zerodim(other) |
| 312 | + |
| 313 | + if isinstance(other, (ABCDataFrame, ABCSeries, ABCIndexClass)): |
338 | 314 | return NotImplemented
|
339 | 315 |
|
340 |
| - opstr = '__{opname}__'.format(opname=op.__name__).replace('__r', '__') |
341 |
| - # allow division by a timedelta |
342 |
| - if opstr in ['__div__', '__truediv__', '__floordiv__']: |
343 |
| - if _is_convertible_to_td(other): |
344 |
| - other = Timedelta(other) |
345 |
| - if isna(other): |
346 |
| - raise NotImplementedError( |
347 |
| - "division by pd.NaT not implemented") |
348 |
| - |
349 |
| - i8 = self.asi8 |
350 |
| - left, right = i8, other.value |
351 |
| - |
352 |
| - if opstr in ['__floordiv__']: |
353 |
| - result = op(left, right) |
354 |
| - else: |
355 |
| - result = op(left, np.float64(right)) |
356 |
| - result = self._maybe_mask_results(result, fill_value=None, |
357 |
| - convert='float64') |
358 |
| - return result |
| 316 | + if is_scalar(other): |
| 317 | + # numpy will accept float and int, raise TypeError for others |
| 318 | + result = self._data * other |
| 319 | + freq = None |
| 320 | + if self.freq is not None and not isna(other): |
| 321 | + freq = self.freq * other |
| 322 | + return type(self)(result, freq=freq) |
| 323 | + |
| 324 | + if not hasattr(other, "dtype"): |
| 325 | + # list, tuple |
| 326 | + other = np.array(other) |
| 327 | + if len(other) != len(self) and not is_timedelta64_dtype(other): |
| 328 | + # Exclude timedelta64 here so we correctly raise TypeError |
| 329 | + # for that instead of ValueError |
| 330 | + raise ValueError("Cannot multiply with unequal lengths") |
| 331 | + |
| 332 | + if is_object_dtype(other): |
| 333 | + # this multiplication will succeed only if all elements of other |
| 334 | + # are int or float scalars, so we will end up with |
| 335 | + # timedelta64[ns]-dtyped result |
| 336 | + result = [self[n] * other[n] for n in range(len(self))] |
| 337 | + result = np.array(result) |
| 338 | + return type(self)(result) |
359 | 339 |
|
360 |
| - return NotImplemented |
| 340 | + # numpy will accept float or int dtype, raise TypeError for others |
| 341 | + result = self._data * other |
| 342 | + return type(self)(result) |
361 | 343 |
|
362 |
| - __mul__ = _wrap_tdi_op(operator.mul) |
363 | 344 | __rmul__ = __mul__
|
364 |
| - __floordiv__ = _wrap_tdi_op(operator.floordiv) |
365 |
| - __rfloordiv__ = _wrap_tdi_op(ops.rfloordiv) |
366 | 345 |
|
367 | 346 | def __truediv__(self, other):
|
368 | 347 | # timedelta / X is well-defined for timedelta-like or numeric X
|
@@ -464,6 +443,165 @@ def __rtruediv__(self, other):
|
464 | 443 | __div__ = __truediv__
|
465 | 444 | __rdiv__ = __rtruediv__
|
466 | 445 |
|
| 446 | + def __floordiv__(self, other): |
| 447 | + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
| 448 | + return NotImplemented |
| 449 | + |
| 450 | + other = lib.item_from_zerodim(other) |
| 451 | + if is_scalar(other): |
| 452 | + if isinstance(other, (timedelta, np.timedelta64, Tick)): |
| 453 | + other = Timedelta(other) |
| 454 | + if other is NaT: |
| 455 | + # treat this specifically as timedelta-NaT |
| 456 | + result = np.empty(self.shape, dtype=np.float64) |
| 457 | + result.fill(np.nan) |
| 458 | + return result |
| 459 | + |
| 460 | + # dispatch to Timedelta implementation |
| 461 | + result = other.__rfloordiv__(self._data) |
| 462 | + return result |
| 463 | + |
| 464 | + # at this point we should only have numeric scalars; anything |
| 465 | + # else will raise |
| 466 | + result = self.asi8 // other |
| 467 | + result[self._isnan] = iNaT |
| 468 | + freq = None |
| 469 | + if self.freq is not None: |
| 470 | + # Note: freq gets division, not floor-division |
| 471 | + freq = self.freq / other |
| 472 | + return type(self)(result.view('m8[ns]'), freq=freq) |
| 473 | + |
| 474 | + if not hasattr(other, "dtype"): |
| 475 | + # list, tuple |
| 476 | + other = np.array(other) |
| 477 | + if len(other) != len(self): |
| 478 | + raise ValueError("Cannot divide with unequal lengths") |
| 479 | + |
| 480 | + elif is_timedelta64_dtype(other): |
| 481 | + other = type(self)(other) |
| 482 | + |
| 483 | + # numpy timedelta64 does not natively support floordiv, so operate |
| 484 | + # on the i8 values |
| 485 | + result = self.asi8 // other.asi8 |
| 486 | + mask = self._isnan | other._isnan |
| 487 | + if mask.any(): |
| 488 | + result = result.astype(np.int64) |
| 489 | + result[mask] = np.nan |
| 490 | + return result |
| 491 | + |
| 492 | + elif is_object_dtype(other): |
| 493 | + result = [self[n] // other[n] for n in range(len(self))] |
| 494 | + result = np.array(result) |
| 495 | + if lib.infer_dtype(result) == 'timedelta': |
| 496 | + result, _ = sequence_to_td64ns(result) |
| 497 | + return type(self)(result) |
| 498 | + return result |
| 499 | + |
| 500 | + elif is_integer_dtype(other) or is_float_dtype(other): |
| 501 | + result = self._data // other |
| 502 | + return type(self)(result) |
| 503 | + |
| 504 | + else: |
| 505 | + dtype = getattr(other, "dtype", type(other).__name__) |
| 506 | + raise TypeError("Cannot divide {typ} by {cls}" |
| 507 | + .format(typ=dtype, cls=type(self).__name__)) |
| 508 | + |
| 509 | + def __rfloordiv__(self, other): |
| 510 | + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
| 511 | + return NotImplemented |
| 512 | + |
| 513 | + other = lib.item_from_zerodim(other) |
| 514 | + if is_scalar(other): |
| 515 | + if isinstance(other, (timedelta, np.timedelta64, Tick)): |
| 516 | + other = Timedelta(other) |
| 517 | + if other is NaT: |
| 518 | + # treat this specifically as timedelta-NaT |
| 519 | + result = np.empty(self.shape, dtype=np.float64) |
| 520 | + result.fill(np.nan) |
| 521 | + return result |
| 522 | + |
| 523 | + # dispatch to Timedelta implementation |
| 524 | + result = other.__floordiv__(self._data) |
| 525 | + return result |
| 526 | + |
| 527 | + raise TypeError("Cannot divide {typ} by {cls}" |
| 528 | + .format(typ=type(other).__name__, |
| 529 | + cls=type(self).__name__)) |
| 530 | + |
| 531 | + if not hasattr(other, "dtype"): |
| 532 | + # list, tuple |
| 533 | + other = np.array(other) |
| 534 | + if len(other) != len(self): |
| 535 | + raise ValueError("Cannot divide with unequal lengths") |
| 536 | + |
| 537 | + elif is_timedelta64_dtype(other): |
| 538 | + other = type(self)(other) |
| 539 | + |
| 540 | + # numpy timedelta64 does not natively support floordiv, so operate |
| 541 | + # on the i8 values |
| 542 | + result = other.asi8 // self.asi8 |
| 543 | + mask = self._isnan | other._isnan |
| 544 | + if mask.any(): |
| 545 | + result = result.astype(np.int64) |
| 546 | + result[mask] = np.nan |
| 547 | + return result |
| 548 | + |
| 549 | + elif is_object_dtype(other): |
| 550 | + result = [other[n] // self[n] for n in range(len(self))] |
| 551 | + result = np.array(result) |
| 552 | + return result |
| 553 | + |
| 554 | + else: |
| 555 | + dtype = getattr(other, "dtype", type(other).__name__) |
| 556 | + raise TypeError("Cannot divide {typ} by {cls}" |
| 557 | + .format(typ=dtype, cls=type(self).__name__)) |
| 558 | + |
| 559 | + def __mod__(self, other): |
| 560 | + # Note: This is a naive implementation, can likely be optimized |
| 561 | + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
| 562 | + return NotImplemented |
| 563 | + |
| 564 | + other = lib.item_from_zerodim(other) |
| 565 | + if isinstance(other, (timedelta, np.timedelta64, Tick)): |
| 566 | + other = Timedelta(other) |
| 567 | + return self - (self // other) * other |
| 568 | + |
| 569 | + def __rmod__(self, other): |
| 570 | + # Note: This is a naive implementation, can likely be optimized |
| 571 | + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
| 572 | + return NotImplemented |
| 573 | + |
| 574 | + other = lib.item_from_zerodim(other) |
| 575 | + if isinstance(other, (timedelta, np.timedelta64, Tick)): |
| 576 | + other = Timedelta(other) |
| 577 | + return other - (other // self) * self |
| 578 | + |
| 579 | + def __divmod__(self, other): |
| 580 | + # Note: This is a naive implementation, can likely be optimized |
| 581 | + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
| 582 | + return NotImplemented |
| 583 | + |
| 584 | + other = lib.item_from_zerodim(other) |
| 585 | + if isinstance(other, (timedelta, np.timedelta64, Tick)): |
| 586 | + other = Timedelta(other) |
| 587 | + |
| 588 | + res1 = self // other |
| 589 | + res2 = self - res1 * other |
| 590 | + return res1, res2 |
| 591 | + |
| 592 | + def __rdivmod__(self, other): |
| 593 | + # Note: This is a naive implementation, can likely be optimized |
| 594 | + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): |
| 595 | + return NotImplemented |
| 596 | + |
| 597 | + other = lib.item_from_zerodim(other) |
| 598 | + if isinstance(other, (timedelta, np.timedelta64, Tick)): |
| 599 | + other = Timedelta(other) |
| 600 | + |
| 601 | + res1 = other // self |
| 602 | + res2 = other - res1 * self |
| 603 | + return res1, res2 |
| 604 | + |
467 | 605 | # Note: TimedeltaIndex overrides this in call to cls._add_numeric_methods
|
468 | 606 | def __neg__(self):
|
469 | 607 | if self.freq is not None:
|
|
0 commit comments