testing.pyx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import cmath
  2. import math
  3. import numpy as np
  4. from numpy cimport import_array
  5. import_array()
  6. from pandas._libs.util cimport (
  7. is_array,
  8. is_complex_object,
  9. is_real_number_object,
  10. )
  11. from pandas.core.dtypes.common import is_dtype_equal
  12. from pandas.core.dtypes.missing import (
  13. array_equivalent,
  14. isna,
  15. )
  16. cdef bint isiterable(obj):
  17. return hasattr(obj, "__iter__")
  18. cdef bint has_length(obj):
  19. return hasattr(obj, "__len__")
  20. cdef bint is_dictlike(obj):
  21. return hasattr(obj, "keys") and hasattr(obj, "__getitem__")
  22. cpdef assert_dict_equal(a, b, bint compare_keys=True):
  23. assert is_dictlike(a) and is_dictlike(b), (
  24. "Cannot compare dict objects, one or both is not dict-like"
  25. )
  26. a_keys = frozenset(a.keys())
  27. b_keys = frozenset(b.keys())
  28. if compare_keys:
  29. assert a_keys == b_keys
  30. for k in a_keys:
  31. assert_almost_equal(a[k], b[k])
  32. return True
  33. cpdef assert_almost_equal(a, b,
  34. rtol=1.e-5, atol=1.e-8,
  35. bint check_dtype=True,
  36. obj=None, lobj=None, robj=None, index_values=None):
  37. """
  38. Check that left and right objects are almost equal.
  39. Parameters
  40. ----------
  41. a : object
  42. b : object
  43. rtol : float, default 1e-5
  44. Relative tolerance.
  45. .. versionadded:: 1.1.0
  46. atol : float, default 1e-8
  47. Absolute tolerance.
  48. .. versionadded:: 1.1.0
  49. check_dtype: bool, default True
  50. check dtype if both a and b are np.ndarray.
  51. obj : str, default None
  52. Specify object name being compared, internally used to show
  53. appropriate assertion message.
  54. lobj : str, default None
  55. Specify left object name being compared, internally used to show
  56. appropriate assertion message.
  57. robj : str, default None
  58. Specify right object name being compared, internally used to show
  59. appropriate assertion message.
  60. index_values : ndarray, default None
  61. Specify shared index values of objects being compared, internally used
  62. to show appropriate assertion message.
  63. .. versionadded:: 1.1.0
  64. """
  65. cdef:
  66. double diff = 0.0
  67. Py_ssize_t i, na, nb
  68. double fa, fb
  69. bint is_unequal = False, a_is_ndarray, b_is_ndarray
  70. str first_diff = ""
  71. if lobj is None:
  72. lobj = a
  73. if robj is None:
  74. robj = b
  75. if isinstance(a, dict) or isinstance(b, dict):
  76. return assert_dict_equal(a, b)
  77. if isinstance(a, str) or isinstance(b, str):
  78. assert a == b, f"{a} != {b}"
  79. return True
  80. a_is_ndarray = is_array(a)
  81. b_is_ndarray = is_array(b)
  82. if obj is None:
  83. if a_is_ndarray or b_is_ndarray:
  84. obj = "numpy array"
  85. else:
  86. obj = "Iterable"
  87. if isiterable(a):
  88. if not isiterable(b):
  89. from pandas._testing import assert_class_equal
  90. # classes can't be the same, to raise error
  91. assert_class_equal(a, b, obj=obj)
  92. assert has_length(a) and has_length(b), (
  93. f"Can't compare objects without length, one or both is invalid: ({a}, {b})"
  94. )
  95. if a_is_ndarray and b_is_ndarray:
  96. na, nb = a.size, b.size
  97. if a.shape != b.shape:
  98. from pandas._testing import raise_assert_detail
  99. raise_assert_detail(
  100. obj, f"{obj} shapes are different", a.shape, b.shape)
  101. if check_dtype and not is_dtype_equal(a.dtype, b.dtype):
  102. from pandas._testing import assert_attr_equal
  103. assert_attr_equal("dtype", a, b, obj=obj)
  104. if array_equivalent(a, b, strict_nan=True):
  105. return True
  106. else:
  107. na, nb = len(a), len(b)
  108. if na != nb:
  109. from pandas._testing import raise_assert_detail
  110. # if we have a small diff set, print it
  111. if abs(na - nb) < 10:
  112. r = list(set(a) ^ set(b))
  113. else:
  114. r = None
  115. raise_assert_detail(obj, f"{obj} length are different", na, nb, r)
  116. for i in range(len(a)):
  117. try:
  118. assert_almost_equal(a[i], b[i], rtol=rtol, atol=atol)
  119. except AssertionError:
  120. is_unequal = True
  121. diff += 1
  122. if not first_diff:
  123. first_diff = (
  124. f"At positional index {i}, first diff: {a[i]} != {b[i]}"
  125. )
  126. if is_unequal:
  127. from pandas._testing import raise_assert_detail
  128. msg = (f"{obj} values are different "
  129. f"({np.round(diff * 100.0 / na, 5)} %)")
  130. raise_assert_detail(
  131. obj, msg, lobj, robj, first_diff=first_diff, index_values=index_values
  132. )
  133. return True
  134. elif isiterable(b):
  135. from pandas._testing import assert_class_equal
  136. # classes can't be the same, to raise error
  137. assert_class_equal(a, b, obj=obj)
  138. if isna(a) and isna(b):
  139. # TODO: Should require same-dtype NA?
  140. # nan / None comparison
  141. return True
  142. if isna(a) and not isna(b) or not isna(a) and isna(b):
  143. # boolean value of pd.NA is ambigous
  144. raise AssertionError(f"{a} != {b}")
  145. if a == b:
  146. # object comparison
  147. return True
  148. if is_real_number_object(a) and is_real_number_object(b):
  149. if array_equivalent(a, b, strict_nan=True):
  150. # inf comparison
  151. return True
  152. fa, fb = a, b
  153. if not math.isclose(fa, fb, rel_tol=rtol, abs_tol=atol):
  154. assert False, (f"expected {fb:.5f} but got {fa:.5f}, "
  155. f"with rtol={rtol}, atol={atol}")
  156. return True
  157. if is_complex_object(a) and is_complex_object(b):
  158. if array_equivalent(a, b, strict_nan=True):
  159. # inf comparison
  160. return True
  161. if not cmath.isclose(a, b, rel_tol=rtol, abs_tol=atol):
  162. assert False, (f"expected {b:.5f} but got {a:.5f}, "
  163. f"with rtol={rtol}, atol={atol}")
  164. return True
  165. raise AssertionError(f"{a} != {b}")