Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Missing data handling for arithmetic operations. 

3 

4In particular, pandas conventions regarding division by zero differ 

5from numpy in the following ways: 

6 1) np.array([-1, 0, 1], dtype=dtype1) // np.array([0, 0, 0], dtype=dtype2) 

7 gives [nan, nan, nan] for most dtype combinations, and [0, 0, 0] for 

8 the remaining pairs 

9 (the remaining being dtype1==dtype2==intN and dtype==dtype2==uintN). 

10 

11 pandas convention is to return [-inf, nan, inf] for all dtype 

12 combinations. 

13 

14 Note: the numpy behavior described here is py3-specific. 

15 

16 2) np.array([-1, 0, 1], dtype=dtype1) % np.array([0, 0, 0], dtype=dtype2) 

17 gives precisely the same results as the // operation. 

18 

19 pandas convention is to return [nan, nan, nan] for all dtype 

20 combinations. 

21 

22 3) divmod behavior consistent with 1) and 2). 

23""" 

24import operator 

25 

26import numpy as np 

27 

28from pandas.core.dtypes.common import is_float_dtype, is_integer_dtype, is_scalar 

29 

30from pandas.core.ops.roperator import rdivmod, rfloordiv, rmod 

31 

32 

33def fill_zeros(result, x, y): 

34 """ 

35 If this is a reversed op, then flip x,y 

36 

37 If we have an integer value (or array in y) 

38 and we have 0's, fill them with np.nan, 

39 return the result. 

40 

41 Mask the nan's from x. 

42 """ 

43 if is_float_dtype(result.dtype): 

44 return result 

45 

46 is_variable_type = hasattr(y, "dtype") or hasattr(y, "type") 

47 is_scalar_type = is_scalar(y) 

48 

49 if not is_variable_type and not is_scalar_type: 

50 return result 

51 

52 if is_scalar_type: 

53 y = np.array(y) 

54 

55 if is_integer_dtype(y.dtype): 

56 

57 if (y == 0).any(): 

58 

59 # GH#7325, mask and nans must be broadcastable (also: GH#9308) 

60 # Raveling and then reshaping makes np.putmask faster 

61 mask = ((y == 0) & ~np.isnan(result)).ravel() 

62 

63 shape = result.shape 

64 result = result.astype("float64", copy=False).ravel() 

65 

66 np.putmask(result, mask, np.nan) 

67 

68 result = result.reshape(shape) 

69 

70 return result 

71 

72 

73def mask_zero_div_zero(x, y, result): 

74 """ 

75 Set results of 0 / 0 or 0 // 0 to np.nan, regardless of the dtypes 

76 of the numerator or the denominator. 

77 

78 Parameters 

79 ---------- 

80 x : ndarray 

81 y : ndarray 

82 result : ndarray 

83 

84 Returns 

85 ------- 

86 filled_result : ndarray 

87 

88 Examples 

89 -------- 

90 >>> x = np.array([1, 0, -1], dtype=np.int64) 

91 >>> y = 0 # int 0; numpy behavior is different with float 

92 >>> result = x / y 

93 >>> result # raw numpy result does not fill division by zero 

94 array([0, 0, 0]) 

95 >>> mask_zero_div_zero(x, y, result) 

96 array([ inf, nan, -inf]) 

97 """ 

98 if not isinstance(result, np.ndarray): 

99 # FIXME: SparseArray would raise TypeError with np.putmask 

100 return result 

101 

102 if is_scalar(y): 

103 y = np.array(y) 

104 

105 zmask = y == 0 

106 

107 if isinstance(zmask, bool): 

108 # FIXME: numpy did not evaluate pointwise, seen in docs build 

109 return result 

110 

111 if zmask.any(): 

112 shape = result.shape 

113 

114 # Flip sign if necessary for -0.0 

115 zneg_mask = zmask & np.signbit(y) 

116 zpos_mask = zmask & ~zneg_mask 

117 

118 nan_mask = (zmask & (x == 0)).ravel() 

119 with np.errstate(invalid="ignore"): 

120 neginf_mask = ((zpos_mask & (x < 0)) | (zneg_mask & (x > 0))).ravel() 

121 posinf_mask = ((zpos_mask & (x > 0)) | (zneg_mask & (x < 0))).ravel() 

122 

123 if nan_mask.any() or neginf_mask.any() or posinf_mask.any(): 

124 # Fill negative/0 with -inf, positive/0 with +inf, 0/0 with NaN 

125 result = result.astype("float64", copy=False).ravel() 

126 

127 np.putmask(result, nan_mask, np.nan) 

128 np.putmask(result, posinf_mask, np.inf) 

129 np.putmask(result, neginf_mask, -np.inf) 

130 

131 result = result.reshape(shape) 

132 

133 return result 

134 

135 

136def dispatch_fill_zeros(op, left, right, result): 

137 """ 

138 Call fill_zeros with the appropriate fill value depending on the operation, 

139 with special logic for divmod and rdivmod. 

140 

141 Parameters 

142 ---------- 

143 op : function (operator.add, operator.div, ...) 

144 left : object (np.ndarray for non-reversed ops) 

145 right : object (np.ndarray for reversed ops) 

146 result : ndarray 

147 

148 Returns 

149 ------- 

150 result : np.ndarray 

151 

152 Notes 

153 ----- 

154 For divmod and rdivmod, the `result` parameter and returned `result` 

155 is a 2-tuple of ndarray objects. 

156 """ 

157 if op is divmod: 

158 result = ( 

159 mask_zero_div_zero(left, right, result[0]), 

160 fill_zeros(result[1], left, right), 

161 ) 

162 elif op is rdivmod: 

163 result = ( 

164 mask_zero_div_zero(right, left, result[0]), 

165 fill_zeros(result[1], right, left), 

166 ) 

167 elif op is operator.floordiv: 

168 # Note: no need to do this for truediv; in py3 numpy behaves the way 

169 # we want. 

170 result = mask_zero_div_zero(left, right, result) 

171 elif op is rfloordiv: 

172 # Note: no need to do this for rtruediv; in py3 numpy behaves the way 

173 # we want. 

174 result = mask_zero_div_zero(right, left, result) 

175 elif op is operator.mod: 

176 result = fill_zeros(result, left, right) 

177 elif op is rmod: 

178 result = fill_zeros(result, right, left) 

179 return result