PageRenderTime 33ms CodeModel.GetById 45ms RepoModel.GetById 0ms app.codeStats 0ms

/pandas/tests/arrays/integer/test_arithmetic.py

https://github.com/pydata/pandas
Python | 352 lines | 244 code | 83 blank | 25 comment | 12 complexity | 7fe2d9bf46a77e6173ebbd859f4caf24 MD5 | raw file
Possible License(s): BSD-3-Clause
  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. import pandas.core.ops as ops
  8. # Basic test for the arithmetic array ops
  9. # -----------------------------------------------------------------------------
  10. @pytest.mark.parametrize(
  11. "opname, exp",
  12. [("add", [1, 3, None, None, 9]), ("mul", [0, 2, None, None, 20])],
  13. ids=["add", "mul"],
  14. )
  15. def test_add_mul(dtype, opname, exp):
  16. a = pd.array([0, 1, None, 3, 4], dtype=dtype)
  17. b = pd.array([1, 2, 3, None, 5], dtype=dtype)
  18. # array / array
  19. expected = pd.array(exp, dtype=dtype)
  20. op = getattr(operator, opname)
  21. result = op(a, b)
  22. tm.assert_extension_array_equal(result, expected)
  23. op = getattr(ops, "r" + opname)
  24. result = op(a, b)
  25. tm.assert_extension_array_equal(result, expected)
  26. def test_sub(dtype):
  27. a = pd.array([1, 2, 3, None, 5], dtype=dtype)
  28. b = pd.array([0, 1, None, 3, 4], dtype=dtype)
  29. result = a - b
  30. expected = pd.array([1, 1, None, None, 1], dtype=dtype)
  31. tm.assert_extension_array_equal(result, expected)
  32. def test_div(dtype):
  33. a = pd.array([1, 2, 3, None, 5], dtype=dtype)
  34. b = pd.array([0, 1, None, 3, 4], dtype=dtype)
  35. result = a / b
  36. expected = pd.array([np.inf, 2, None, None, 1.25], dtype="Float64")
  37. tm.assert_extension_array_equal(result, expected)
  38. @pytest.mark.parametrize("zero, negative", [(0, False), (0.0, False), (-0.0, True)])
  39. def test_divide_by_zero(zero, negative):
  40. # https://github.com/pandas-dev/pandas/issues/27398, GH#22793
  41. a = pd.array([0, 1, -1, None], dtype="Int64")
  42. result = a / zero
  43. expected = FloatingArray(
  44. np.array([np.nan, np.inf, -np.inf, 1], dtype="float64"),
  45. np.array([False, False, False, True]),
  46. )
  47. if negative:
  48. expected *= -1
  49. tm.assert_extension_array_equal(result, expected)
  50. def test_floordiv(dtype):
  51. a = pd.array([1, 2, 3, None, 5], dtype=dtype)
  52. b = pd.array([0, 1, None, 3, 4], dtype=dtype)
  53. result = a // b
  54. # Series op sets 1//0 to np.inf, which IntegerArray does not do (yet)
  55. expected = pd.array([0, 2, None, None, 1], dtype=dtype)
  56. tm.assert_extension_array_equal(result, expected)
  57. def test_mod(dtype):
  58. a = pd.array([1, 2, 3, None, 5], dtype=dtype)
  59. b = pd.array([0, 1, None, 3, 4], dtype=dtype)
  60. result = a % b
  61. expected = pd.array([0, 0, None, None, 1], dtype=dtype)
  62. tm.assert_extension_array_equal(result, expected)
  63. def test_pow_scalar():
  64. a = pd.array([-1, 0, 1, None, 2], dtype="Int64")
  65. result = a**0
  66. expected = pd.array([1, 1, 1, 1, 1], dtype="Int64")
  67. tm.assert_extension_array_equal(result, expected)
  68. result = a**1
  69. expected = pd.array([-1, 0, 1, None, 2], dtype="Int64")
  70. tm.assert_extension_array_equal(result, expected)
  71. result = a**pd.NA
  72. expected = pd.array([None, None, 1, None, None], dtype="Int64")
  73. tm.assert_extension_array_equal(result, expected)
  74. result = a**np.nan
  75. expected = FloatingArray(
  76. np.array([np.nan, np.nan, 1, np.nan, np.nan], dtype="float64"),
  77. np.array([False, False, False, True, False]),
  78. )
  79. tm.assert_extension_array_equal(result, expected)
  80. # reversed
  81. a = a[1:] # Can't raise integers to negative powers.
  82. result = 0**a
  83. expected = pd.array([1, 0, None, 0], dtype="Int64")
  84. tm.assert_extension_array_equal(result, expected)
  85. result = 1**a
  86. expected = pd.array([1, 1, 1, 1], dtype="Int64")
  87. tm.assert_extension_array_equal(result, expected)
  88. result = pd.NA**a
  89. expected = pd.array([1, None, None, None], dtype="Int64")
  90. tm.assert_extension_array_equal(result, expected)
  91. result = np.nan**a
  92. expected = FloatingArray(
  93. np.array([1, np.nan, np.nan, np.nan], dtype="float64"),
  94. np.array([False, False, True, False]),
  95. )
  96. tm.assert_extension_array_equal(result, expected)
  97. def test_pow_array():
  98. a = pd.array([0, 0, 0, 1, 1, 1, None, None, None])
  99. b = pd.array([0, 1, None, 0, 1, None, 0, 1, None])
  100. result = a**b
  101. expected = pd.array([1, 0, None, 1, 1, 1, 1, None, None])
  102. tm.assert_extension_array_equal(result, expected)
  103. def test_rpow_one_to_na():
  104. # https://github.com/pandas-dev/pandas/issues/22022
  105. # https://github.com/pandas-dev/pandas/issues/29997
  106. arr = pd.array([np.nan, np.nan], dtype="Int64")
  107. result = np.array([1.0, 2.0]) ** arr
  108. expected = pd.array([1.0, np.nan], dtype="Float64")
  109. tm.assert_extension_array_equal(result, expected)
  110. @pytest.mark.parametrize("other", [0, 0.5])
  111. def test_numpy_zero_dim_ndarray(other):
  112. arr = pd.array([1, None, 2])
  113. result = arr + np.array(other)
  114. expected = arr + other
  115. tm.assert_equal(result, expected)
  116. # Test generic characteristics / errors
  117. # -----------------------------------------------------------------------------
  118. def test_error_invalid_values(data, all_arithmetic_operators):
  119. op = all_arithmetic_operators
  120. s = pd.Series(data)
  121. ops = getattr(s, op)
  122. # invalid scalars
  123. msg = "|".join(
  124. [
  125. r"can only perform ops with numeric values",
  126. r"IntegerArray cannot perform the operation mod",
  127. r"unsupported operand type",
  128. r"can only concatenate str \(not \"int\"\) to str",
  129. "not all arguments converted during string",
  130. "ufunc '.*' not supported for the input types, and the inputs could not",
  131. "ufunc '.*' did not contain a loop with signature matching types",
  132. "Addition/subtraction of integers and integer-arrays with Timestamp",
  133. ]
  134. )
  135. with pytest.raises(TypeError, match=msg):
  136. ops("foo")
  137. with pytest.raises(TypeError, match=msg):
  138. ops(pd.Timestamp("20180101"))
  139. # invalid array-likes
  140. str_ser = pd.Series("foo", index=s.index)
  141. # with pytest.raises(TypeError, match=msg):
  142. if all_arithmetic_operators in [
  143. "__mul__",
  144. "__rmul__",
  145. ]: # (data[~data.isna()] >= 0).all():
  146. res = ops(str_ser)
  147. expected = pd.Series(["foo" * x for x in data], index=s.index)
  148. tm.assert_series_equal(res, expected)
  149. else:
  150. with pytest.raises(TypeError, match=msg):
  151. ops(str_ser)
  152. msg = "|".join(
  153. [
  154. "can only perform ops with numeric values",
  155. "cannot perform .* with this index type: DatetimeArray",
  156. "Addition/subtraction of integers and integer-arrays "
  157. "with DatetimeArray is no longer supported. *",
  158. "unsupported operand type",
  159. r"can only concatenate str \(not \"int\"\) to str",
  160. "not all arguments converted during string",
  161. "cannot subtract DatetimeArray from ndarray",
  162. ]
  163. )
  164. with pytest.raises(TypeError, match=msg):
  165. ops(pd.Series(pd.date_range("20180101", periods=len(s))))
  166. # Various
  167. # -----------------------------------------------------------------------------
  168. # TODO test unsigned overflow
  169. def test_arith_coerce_scalar(data, all_arithmetic_operators):
  170. op = tm.get_op_from_name(all_arithmetic_operators)
  171. s = pd.Series(data)
  172. other = 0.01
  173. result = op(s, other)
  174. expected = op(s.astype(float), other)
  175. expected = expected.astype("Float64")
  176. # rmod results in NaN that wasn't NA in original nullable Series -> unmask it
  177. if all_arithmetic_operators == "__rmod__":
  178. mask = (s == 0).fillna(False).to_numpy(bool)
  179. expected.array._mask[mask] = False
  180. tm.assert_series_equal(result, expected)
  181. @pytest.mark.parametrize("other", [1.0, np.array(1.0)])
  182. def test_arithmetic_conversion(all_arithmetic_operators, other):
  183. # if we have a float operand we should have a float result
  184. # if that is equal to an integer
  185. op = tm.get_op_from_name(all_arithmetic_operators)
  186. s = pd.Series([1, 2, 3], dtype="Int64")
  187. result = op(s, other)
  188. assert result.dtype == "Float64"
  189. def test_cross_type_arithmetic():
  190. df = pd.DataFrame(
  191. {
  192. "A": pd.Series([1, 2, np.nan], dtype="Int64"),
  193. "B": pd.Series([1, np.nan, 3], dtype="UInt8"),
  194. "C": [1, 2, 3],
  195. }
  196. )
  197. result = df.A + df.C
  198. expected = pd.Series([2, 4, np.nan], dtype="Int64")
  199. tm.assert_series_equal(result, expected)
  200. result = (df.A + df.C) * 3 == 12
  201. expected = pd.Series([False, True, None], dtype="boolean")
  202. tm.assert_series_equal(result, expected)
  203. result = df.A + df.B
  204. expected = pd.Series([2, np.nan, np.nan], dtype="Int64")
  205. tm.assert_series_equal(result, expected)
  206. @pytest.mark.parametrize("op", ["mean"])
  207. def test_reduce_to_float(op):
  208. # some reduce ops always return float, even if the result
  209. # is a rounded number
  210. df = pd.DataFrame(
  211. {
  212. "A": ["a", "b", "b"],
  213. "B": [1, None, 3],
  214. "C": pd.array([1, None, 3], dtype="Int64"),
  215. }
  216. )
  217. # op
  218. result = getattr(df.C, op)()
  219. assert isinstance(result, float)
  220. # groupby
  221. result = getattr(df.groupby("A"), op)()
  222. expected = pd.DataFrame(
  223. {"B": np.array([1.0, 3.0]), "C": pd.array([1, 3], dtype="Float64")},
  224. index=pd.Index(["a", "b"], name="A"),
  225. )
  226. tm.assert_frame_equal(result, expected)
  227. @pytest.mark.parametrize(
  228. "source, neg_target, abs_target",
  229. [
  230. ([1, 2, 3], [-1, -2, -3], [1, 2, 3]),
  231. ([1, 2, None], [-1, -2, None], [1, 2, None]),
  232. ([-1, 0, 1], [1, 0, -1], [1, 0, 1]),
  233. ],
  234. )
  235. def test_unary_int_operators(any_signed_int_ea_dtype, source, neg_target, abs_target):
  236. dtype = any_signed_int_ea_dtype
  237. arr = pd.array(source, dtype=dtype)
  238. neg_result, pos_result, abs_result = -arr, +arr, abs(arr)
  239. neg_target = pd.array(neg_target, dtype=dtype)
  240. abs_target = pd.array(abs_target, dtype=dtype)
  241. tm.assert_extension_array_equal(neg_result, neg_target)
  242. tm.assert_extension_array_equal(pos_result, arr)
  243. assert not tm.shares_memory(pos_result, arr)
  244. tm.assert_extension_array_equal(abs_result, abs_target)
  245. def test_values_multiplying_large_series_by_NA():
  246. # GH#33701
  247. result = pd.NA * pd.Series(np.zeros(10001))
  248. expected = pd.Series([pd.NA] * 10001)
  249. tm.assert_series_equal(result, expected)
  250. def test_bitwise(dtype):
  251. left = pd.array([1, None, 3, 4], dtype=dtype)
  252. right = pd.array([None, 3, 5, 4], dtype=dtype)
  253. result = left | right
  254. expected = pd.array([None, None, 3 | 5, 4 | 4], dtype=dtype)
  255. tm.assert_extension_array_equal(result, expected)
  256. result = left & right
  257. expected = pd.array([None, None, 3 & 5, 4 & 4], dtype=dtype)
  258. tm.assert_extension_array_equal(result, expected)
  259. result = left ^ right
  260. expected = pd.array([None, None, 3 ^ 5, 4 ^ 4], dtype=dtype)
  261. tm.assert_extension_array_equal(result, expected)
  262. # TODO: desired behavior when operating with boolean? defer?
  263. floats = right.astype("Float64")
  264. with pytest.raises(TypeError, match="unsupported operand type"):
  265. left | floats
  266. with pytest.raises(TypeError, match="unsupported operand type"):
  267. left & floats
  268. with pytest.raises(TypeError, match="unsupported operand type"):
  269. left ^ floats