masked_shared.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """
  2. Tests shared by MaskedArray subclasses.
  3. """
  4. import numpy as np
  5. import pytest
  6. import pandas as pd
  7. import pandas._testing as tm
  8. from pandas.tests.extension.base import BaseOpsUtil
  9. class ComparisonOps(BaseOpsUtil):
  10. def _compare_other(self, data, op, other):
  11. # array
  12. result = pd.Series(op(data, other))
  13. expected = pd.Series(op(data._data, other), dtype="boolean")
  14. # fill the nan locations
  15. expected[data._mask] = pd.NA
  16. tm.assert_series_equal(result, expected)
  17. # series
  18. ser = pd.Series(data)
  19. result = op(ser, other)
  20. expected = op(pd.Series(data._data), other)
  21. # fill the nan locations
  22. expected[data._mask] = pd.NA
  23. expected = expected.astype("boolean")
  24. tm.assert_series_equal(result, expected)
  25. # subclass will override to parametrize 'other'
  26. def test_scalar(self, other, comparison_op, dtype):
  27. op = comparison_op
  28. left = pd.array([1, 0, None], dtype=dtype)
  29. result = op(left, other)
  30. if other is pd.NA:
  31. expected = pd.array([None, None, None], dtype="boolean")
  32. else:
  33. values = op(left._data, other)
  34. expected = pd.arrays.BooleanArray(values, left._mask, copy=True)
  35. tm.assert_extension_array_equal(result, expected)
  36. # ensure we haven't mutated anything inplace
  37. result[0] = pd.NA
  38. tm.assert_extension_array_equal(left, pd.array([1, 0, None], dtype=dtype))
  39. class NumericOps:
  40. # Shared by IntegerArray and FloatingArray, not BooleanArray
  41. def test_searchsorted_nan(self, dtype):
  42. # The base class casts to object dtype, for which searchsorted returns
  43. # 0 from the left and 10 from the right.
  44. arr = pd.array(range(10), dtype=dtype)
  45. assert arr.searchsorted(np.nan, side="left") == 10
  46. assert arr.searchsorted(np.nan, side="right") == 10
  47. def test_no_shared_mask(self, data):
  48. result = data + 1
  49. assert not tm.shares_memory(result, data)
  50. def test_array(self, comparison_op, dtype):
  51. op = comparison_op
  52. left = pd.array([0, 1, 2, None, None, None], dtype=dtype)
  53. right = pd.array([0, 1, None, 0, 1, None], dtype=dtype)
  54. result = op(left, right)
  55. values = op(left._data, right._data)
  56. mask = left._mask | right._mask
  57. expected = pd.arrays.BooleanArray(values, mask)
  58. tm.assert_extension_array_equal(result, expected)
  59. # ensure we haven't mutated anything inplace
  60. result[0] = pd.NA
  61. tm.assert_extension_array_equal(
  62. left, pd.array([0, 1, 2, None, None, None], dtype=dtype)
  63. )
  64. tm.assert_extension_array_equal(
  65. right, pd.array([0, 1, None, 0, 1, None], dtype=dtype)
  66. )
  67. def test_compare_with_booleanarray(self, comparison_op, dtype):
  68. op = comparison_op
  69. left = pd.array([True, False, None] * 3, dtype="boolean")
  70. right = pd.array([0] * 3 + [1] * 3 + [None] * 3, dtype=dtype)
  71. other = pd.array([False] * 3 + [True] * 3 + [None] * 3, dtype="boolean")
  72. expected = op(left, other)
  73. result = op(left, right)
  74. tm.assert_extension_array_equal(result, expected)
  75. # reversed op
  76. expected = op(other, left)
  77. result = op(right, left)
  78. tm.assert_extension_array_equal(result, expected)
  79. def test_compare_to_string(self, dtype):
  80. # GH#28930
  81. ser = pd.Series([1, None], dtype=dtype)
  82. result = ser == "a"
  83. expected = pd.Series([False, pd.NA], dtype="boolean")
  84. self.assert_series_equal(result, expected)
  85. def test_ufunc_with_out(self, dtype):
  86. arr = pd.array([1, 2, 3], dtype=dtype)
  87. arr2 = pd.array([1, 2, pd.NA], dtype=dtype)
  88. mask = arr == arr
  89. mask2 = arr2 == arr2
  90. result = np.zeros(3, dtype=bool)
  91. result |= mask
  92. # If MaskedArray.__array_ufunc__ handled "out" appropriately,
  93. # `result` should still be an ndarray.
  94. assert isinstance(result, np.ndarray)
  95. assert result.all()
  96. # result |= mask worked because mask could be cast losslessly to
  97. # boolean ndarray. mask2 can't, so this raises
  98. result = np.zeros(3, dtype=bool)
  99. msg = "Specify an appropriate 'na_value' for this dtype"
  100. with pytest.raises(ValueError, match=msg):
  101. result |= mask2
  102. # addition
  103. res = np.add(arr, arr2)
  104. expected = pd.array([2, 4, pd.NA], dtype=dtype)
  105. tm.assert_extension_array_equal(res, expected)
  106. # when passing out=arr, we will modify 'arr' inplace.
  107. res = np.add(arr, arr2, out=arr)
  108. assert res is arr
  109. tm.assert_extension_array_equal(res, expected)
  110. tm.assert_extension_array_equal(arr, expected)
  111. def test_mul_td64_array(self, dtype):
  112. # GH#45622
  113. arr = pd.array([1, 2, pd.NA], dtype=dtype)
  114. other = np.arange(3, dtype=np.int64).view("m8[ns]")
  115. result = arr * other
  116. expected = pd.array([pd.Timedelta(0), pd.Timedelta(2), pd.NaT])
  117. tm.assert_extension_array_equal(result, expected)