test_arithmetic.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import operator
  2. import numpy as np
  3. import pytest
  4. import pandas as pd
  5. import pandas._testing as tm
  6. from pandas.core.arrays import FloatingArray
  7. # Basic test for the arithmetic array ops
  8. # -----------------------------------------------------------------------------
  9. @pytest.mark.parametrize(
  10. "opname, exp",
  11. [
  12. ("add", [1.1, 2.2, None, None, 5.5]),
  13. ("mul", [0.1, 0.4, None, None, 2.5]),
  14. ("sub", [0.9, 1.8, None, None, 4.5]),
  15. ("truediv", [10.0, 10.0, None, None, 10.0]),
  16. ("floordiv", [9.0, 9.0, None, None, 10.0]),
  17. ("mod", [0.1, 0.2, None, None, 0.0]),
  18. ],
  19. ids=["add", "mul", "sub", "div", "floordiv", "mod"],
  20. )
  21. def test_array_op(dtype, opname, exp):
  22. a = pd.array([1.0, 2.0, None, 4.0, 5.0], dtype=dtype)
  23. b = pd.array([0.1, 0.2, 0.3, None, 0.5], dtype=dtype)
  24. op = getattr(operator, opname)
  25. result = op(a, b)
  26. expected = pd.array(exp, dtype=dtype)
  27. tm.assert_extension_array_equal(result, expected)
  28. @pytest.mark.parametrize("zero, negative", [(0, False), (0.0, False), (-0.0, True)])
  29. def test_divide_by_zero(dtype, zero, negative):
  30. # TODO pending NA/NaN discussion
  31. # https://github.com/pandas-dev/pandas/issues/32265/
  32. a = pd.array([0, 1, -1, None], dtype=dtype)
  33. result = a / zero
  34. expected = FloatingArray(
  35. np.array([np.nan, np.inf, -np.inf, np.nan], dtype=dtype.numpy_dtype),
  36. np.array([False, False, False, True]),
  37. )
  38. if negative:
  39. expected *= -1
  40. tm.assert_extension_array_equal(result, expected)
  41. def test_pow_scalar(dtype):
  42. a = pd.array([-1, 0, 1, None, 2], dtype=dtype)
  43. result = a**0
  44. expected = pd.array([1, 1, 1, 1, 1], dtype=dtype)
  45. tm.assert_extension_array_equal(result, expected)
  46. result = a**1
  47. expected = pd.array([-1, 0, 1, None, 2], dtype=dtype)
  48. tm.assert_extension_array_equal(result, expected)
  49. result = a**pd.NA
  50. expected = pd.array([None, None, 1, None, None], dtype=dtype)
  51. tm.assert_extension_array_equal(result, expected)
  52. result = a**np.nan
  53. # TODO np.nan should be converted to pd.NA / missing before operation?
  54. expected = FloatingArray(
  55. np.array([np.nan, np.nan, 1, np.nan, np.nan], dtype=dtype.numpy_dtype),
  56. mask=a._mask,
  57. )
  58. tm.assert_extension_array_equal(result, expected)
  59. # reversed
  60. a = a[1:] # Can't raise integers to negative powers.
  61. result = 0**a
  62. expected = pd.array([1, 0, None, 0], dtype=dtype)
  63. tm.assert_extension_array_equal(result, expected)
  64. result = 1**a
  65. expected = pd.array([1, 1, 1, 1], dtype=dtype)
  66. tm.assert_extension_array_equal(result, expected)
  67. result = pd.NA**a
  68. expected = pd.array([1, None, None, None], dtype=dtype)
  69. tm.assert_extension_array_equal(result, expected)
  70. result = np.nan**a
  71. expected = FloatingArray(
  72. np.array([1, np.nan, np.nan, np.nan], dtype=dtype.numpy_dtype), mask=a._mask
  73. )
  74. tm.assert_extension_array_equal(result, expected)
  75. def test_pow_array(dtype):
  76. a = pd.array([0, 0, 0, 1, 1, 1, None, None, None], dtype=dtype)
  77. b = pd.array([0, 1, None, 0, 1, None, 0, 1, None], dtype=dtype)
  78. result = a**b
  79. expected = pd.array([1, 0, None, 1, 1, 1, 1, None, None], dtype=dtype)
  80. tm.assert_extension_array_equal(result, expected)
  81. def test_rpow_one_to_na():
  82. # https://github.com/pandas-dev/pandas/issues/22022
  83. # https://github.com/pandas-dev/pandas/issues/29997
  84. arr = pd.array([np.nan, np.nan], dtype="Float64")
  85. result = np.array([1.0, 2.0]) ** arr
  86. expected = pd.array([1.0, np.nan], dtype="Float64")
  87. tm.assert_extension_array_equal(result, expected)
  88. @pytest.mark.parametrize("other", [0, 0.5])
  89. def test_arith_zero_dim_ndarray(other):
  90. arr = pd.array([1, None, 2], dtype="Float64")
  91. result = arr + np.array(other)
  92. expected = arr + other
  93. tm.assert_equal(result, expected)
  94. # Test generic characteristics / errors
  95. # -----------------------------------------------------------------------------
  96. def test_error_invalid_values(data, all_arithmetic_operators):
  97. op = all_arithmetic_operators
  98. s = pd.Series(data)
  99. ops = getattr(s, op)
  100. # invalid scalars
  101. msg = "|".join(
  102. [
  103. r"can only perform ops with numeric values",
  104. r"FloatingArray cannot perform the operation mod",
  105. "unsupported operand type",
  106. "not all arguments converted during string formatting",
  107. "can't multiply sequence by non-int of type 'float'",
  108. "ufunc 'subtract' cannot use operands with types dtype",
  109. r"can only concatenate str \(not \"float\"\) to str",
  110. "ufunc '.*' not supported for the input types, and the inputs could not",
  111. "ufunc '.*' did not contain a loop with signature matching types",
  112. "Concatenation operation is not implemented for NumPy arrays",
  113. ]
  114. )
  115. with pytest.raises(TypeError, match=msg):
  116. ops("foo")
  117. with pytest.raises(TypeError, match=msg):
  118. ops(pd.Timestamp("20180101"))
  119. # invalid array-likes
  120. with pytest.raises(TypeError, match=msg):
  121. ops(pd.Series("foo", index=s.index))
  122. msg = "|".join(
  123. [
  124. "can only perform ops with numeric values",
  125. "cannot perform .* with this index type: DatetimeArray",
  126. "Addition/subtraction of integers and integer-arrays "
  127. "with DatetimeArray is no longer supported. *",
  128. "unsupported operand type",
  129. "not all arguments converted during string formatting",
  130. "can't multiply sequence by non-int of type 'float'",
  131. "ufunc 'subtract' cannot use operands with types dtype",
  132. (
  133. "ufunc 'add' cannot use operands with types "
  134. rf"dtype\('{tm.ENDIAN}M8\[ns\]'\)"
  135. ),
  136. r"ufunc 'add' cannot use operands with types dtype\('float\d{2}'\)",
  137. "cannot subtract DatetimeArray from ndarray",
  138. ]
  139. )
  140. with pytest.raises(TypeError, match=msg):
  141. ops(pd.Series(pd.date_range("20180101", periods=len(s))))
  142. # Various
  143. # -----------------------------------------------------------------------------
  144. def test_cross_type_arithmetic():
  145. df = pd.DataFrame(
  146. {
  147. "A": pd.array([1, 2, np.nan], dtype="Float64"),
  148. "B": pd.array([1, np.nan, 3], dtype="Float32"),
  149. "C": np.array([1, 2, 3], dtype="float64"),
  150. }
  151. )
  152. result = df.A + df.C
  153. expected = pd.Series([2, 4, np.nan], dtype="Float64")
  154. tm.assert_series_equal(result, expected)
  155. result = (df.A + df.C) * 3 == 12
  156. expected = pd.Series([False, True, None], dtype="boolean")
  157. tm.assert_series_equal(result, expected)
  158. result = df.A + df.B
  159. expected = pd.Series([2, np.nan, np.nan], dtype="Float64")
  160. tm.assert_series_equal(result, expected)
  161. @pytest.mark.parametrize(
  162. "source, neg_target, abs_target",
  163. [
  164. ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3], [1.1, 2.2, 3.3]),
  165. ([1.1, 2.2, None], [-1.1, -2.2, None], [1.1, 2.2, None]),
  166. ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1], [1.1, 0.0, 1.1]),
  167. ],
  168. )
  169. def test_unary_float_operators(float_ea_dtype, source, neg_target, abs_target):
  170. # GH38794
  171. dtype = float_ea_dtype
  172. arr = pd.array(source, dtype=dtype)
  173. neg_result, pos_result, abs_result = -arr, +arr, abs(arr)
  174. neg_target = pd.array(neg_target, dtype=dtype)
  175. abs_target = pd.array(abs_target, dtype=dtype)
  176. tm.assert_extension_array_equal(neg_result, neg_target)
  177. tm.assert_extension_array_equal(pos_result, arr)
  178. assert not tm.shares_memory(pos_result, arr)
  179. tm.assert_extension_array_equal(abs_result, abs_target)
  180. def test_bitwise(dtype):
  181. left = pd.array([1, None, 3, 4], dtype=dtype)
  182. right = pd.array([None, 3, 5, 4], dtype=dtype)
  183. with pytest.raises(TypeError, match="unsupported operand type"):
  184. left | right
  185. with pytest.raises(TypeError, match="unsupported operand type"):
  186. left & right
  187. with pytest.raises(TypeError, match="unsupported operand type"):
  188. left ^ right