|
1 | 1 | """
|
2 | 2 | Routines for filling missing data
|
3 | 3 | """
|
| 4 | +import operator |
4 | 5 |
|
5 | 6 | import numpy as np
|
6 | 7 | from distutils.version import LooseVersion
|
@@ -650,6 +651,87 @@ def fill_zeros(result, x, y, name, fill):
|
650 | 651 | return result
|
651 | 652 |
|
652 | 653 |
|
| 654 | +def mask_zero_div_zero(x, y, result, copy=False): |
| 655 | + """ |
| 656 | + Set results of 0 / 0 or 0 // 0 to np.nan, regardless of the dtypes |
| 657 | + of the numerator or the denominator. |
| 658 | +
|
| 659 | + Parameters |
| 660 | + ---------- |
| 661 | + x : ndarray |
| 662 | + y : ndarray |
| 663 | + result : ndarray |
| 664 | + copy : bool (default False) |
| 665 | + Whether to always create a new array or try to fill in the existing |
| 666 | + array if possible. |
| 667 | +
|
| 668 | + Returns |
| 669 | + ------- |
| 670 | + filled_result : ndarray |
| 671 | +
|
| 672 | + Examples |
| 673 | + -------- |
| 674 | + >>> x = np.array([1, 0, -1], dtype=np.int64) |
| 675 | + >>> y = 0 # int 0; numpy behavior is different with float |
| 676 | + >>> result = x / y |
| 677 | + >>> result # raw numpy result does not fill division by zero |
| 678 | + array([0, 0, 0]) |
| 679 | + >>> mask_zero_div_zero(x, y, result) |
| 680 | + array([ inf, nan, -inf]) |
| 681 | + """ |
| 682 | + if is_scalar(y): |
| 683 | + y = np.array(y) |
| 684 | + |
| 685 | + zmask = y == 0 |
| 686 | + if zmask.any(): |
| 687 | + shape = result.shape |
| 688 | + |
| 689 | + nan_mask = (zmask & (x == 0)).ravel() |
| 690 | + neginf_mask = (zmask & (x < 0)).ravel() |
| 691 | + posinf_mask = (zmask & (x > 0)).ravel() |
| 692 | + |
| 693 | + if nan_mask.any() or neginf_mask.any() or posinf_mask.any(): |
| 694 | + # Fill negative/0 with -inf, positive/0 with +inf, 0/0 with NaN |
| 695 | + result = result.astype('float64', copy=copy).ravel() |
| 696 | + |
| 697 | + np.putmask(result, nan_mask, np.nan) |
| 698 | + np.putmask(result, posinf_mask, np.inf) |
| 699 | + np.putmask(result, neginf_mask, -np.inf) |
| 700 | + |
| 701 | + result = result.reshape(shape) |
| 702 | + |
| 703 | + return result |
| 704 | + |
| 705 | + |
| 706 | +def dispatch_missing(op, left, right, result): |
| 707 | + """ |
| 708 | + Fill nulls caused by division by zero, casting to a diffferent dtype |
| 709 | + if necessary. |
| 710 | +
|
| 711 | + Parameters |
| 712 | + ---------- |
| 713 | + op : function (operator.add, operator.div, ...) |
| 714 | + left : object (Index for non-reversed ops) |
| 715 | + right : object (Index fof reversed ops) |
| 716 | + result : ndarray |
| 717 | +
|
| 718 | + Returns |
| 719 | + ------- |
| 720 | + result : ndarray |
| 721 | + """ |
| 722 | + opstr = '__{opname}__'.format(opname=op.__name__).replace('____', '__') |
| 723 | + if op in [operator.truediv, operator.floordiv, |
| 724 | + getattr(operator, 'div', None)]: |
| 725 | + result = mask_zero_div_zero(left, right, result) |
| 726 | + elif op is operator.mod: |
| 727 | + result = fill_zeros(result, left, right, opstr, np.nan) |
| 728 | + elif op is divmod: |
| 729 | + res0 = mask_zero_div_zero(left, right, result[0]) |
| 730 | + res1 = fill_zeros(result[1], left, right, opstr, np.nan) |
| 731 | + result = (res0, res1) |
| 732 | + return result |
| 733 | + |
| 734 | + |
653 | 735 | def _interp_limit(invalid, fw_limit, bw_limit):
|
654 | 736 | """
|
655 | 737 | Get indexers of values that won't be filled
|
|
0 commit comments