diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 567b6853bd633..622e7be4ba831 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -781,6 +781,9 @@ Deprecations - The ``squeeze`` keyword in the ``groupby`` function is deprecated and will be removed in a future version (:issue:`32380`) - The ``tz`` keyword in :meth:`Period.to_timestamp` is deprecated and will be removed in a future version; use `per.to_timestamp(...).tz_localize(tz)`` instead (:issue:`34522`) - :meth:`DatetimeIndex.to_perioddelta` is deprecated and will be removed in a future version. Use ``index - index.to_period(freq).to_timestamp()`` instead (:issue:`34853`) +- :meth:`util.testing.assert_almost_equal` now accepts both relative and absolute + precision through the ``rtol``, and ``atol`` parameters, thus deprecating the + ``check_less_precise`` parameter. (:issue:`13357`). .. --------------------------------------------------------------------------- diff --git a/pandas/_libs/testing.pyx b/pandas/_libs/testing.pyx index ca18afebf410b..785a4d1f8b923 100644 --- a/pandas/_libs/testing.pyx +++ b/pandas/_libs/testing.pyx @@ -1,3 +1,5 @@ +import math + import numpy as np from numpy cimport import_array import_array() @@ -42,12 +44,6 @@ cdef bint is_dictlike(obj): return hasattr(obj, 'keys') and hasattr(obj, '__getitem__') -cdef bint decimal_almost_equal(double desired, double actual, int decimal): - # Code from - # https://numpy.org/doc/stable/reference/generated/numpy.testing.assert_almost_equal.html - return abs(desired - actual) < (0.5 * 10.0 ** -decimal) - - cpdef assert_dict_equal(a, b, bint compare_keys=True): assert is_dictlike(a) and is_dictlike(b), ( "Cannot compare dict objects, one or both is not dict-like" @@ -66,7 +62,7 @@ cpdef assert_dict_equal(a, b, bint compare_keys=True): cpdef assert_almost_equal(a, b, - check_less_precise=False, + rtol=1.e-5, atol=1.e-8, bint check_dtype=True, obj=None, lobj=None, robj=None, index_values=None): """ @@ -76,31 +72,33 @@ cpdef assert_almost_equal(a, b, ---------- a : object b : object - check_less_precise : bool or int, default False - Specify comparison precision. - 5 digits (False) or 3 digits (True) after decimal points are - compared. If an integer, then this will be the number of decimal - points to compare + rtol : float, default 1e-5 + Relative tolerance. + + .. versionadded:: 1.1.0 + atol : float, default 1e-8 + Absolute tolerance. + + .. versionadded:: 1.1.0 check_dtype: bool, default True - check dtype if both a and b are np.ndarray + check dtype if both a and b are np.ndarray. obj : str, default None Specify object name being compared, internally used to show - appropriate assertion message + appropriate assertion message. lobj : str, default None Specify left object name being compared, internally used to show - appropriate assertion message + appropriate assertion message. robj : str, default None Specify right object name being compared, internally used to show - appropriate assertion message + appropriate assertion message. index_values : ndarray, default None Specify shared index values of objects being compared, internally used - to show appropriate assertion message + to show appropriate assertion message. .. versionadded:: 1.1.0 """ cdef: - int decimal double diff = 0.0 Py_ssize_t i, na, nb double fa, fb @@ -111,8 +109,6 @@ cpdef assert_almost_equal(a, b, if robj is None: robj = b - assert isinstance(check_less_precise, (int, bool)) - if isinstance(a, dict) or isinstance(b, dict): return assert_dict_equal(a, b) @@ -170,8 +166,7 @@ cpdef assert_almost_equal(a, b, for i in range(len(a)): try: - assert_almost_equal(a[i], b[i], - check_less_precise=check_less_precise) + assert_almost_equal(a[i], b[i], rtol=rtol, atol=atol) except AssertionError: is_unequal = True diff += 1 @@ -203,24 +198,11 @@ cpdef assert_almost_equal(a, b, # inf comparison return True - if check_less_precise is True: - decimal = 3 - elif check_less_precise is False: - decimal = 5 - else: - decimal = check_less_precise - fa, fb = a, b - # case for zero - if abs(fa) < 1e-5: - if not decimal_almost_equal(fa, fb, decimal): - assert False, (f'(very low values) expected {fb:.5f} ' - f'but got {fa:.5f}, with decimal {decimal}') - else: - if not decimal_almost_equal(1, fb / fa, decimal): - assert False, (f'expected {fb:.5f} but got {fa:.5f}, ' - f'with decimal {decimal}') + if not math.isclose(fa, fb, rel_tol=rtol, abs_tol=atol): + assert False, (f"expected {fb:.5f} but got {fa:.5f}, " + f"with rtol={rtol}, atol={atol}") return True raise AssertionError(f"{a} != {b}") diff --git a/pandas/_testing.py b/pandas/_testing.py index ebb53dd81682c..fc6df7a95e348 100644 --- a/pandas/_testing.py +++ b/pandas/_testing.py @@ -22,6 +22,7 @@ set_locale, ) +from pandas._libs.lib import no_default import pandas._libs.testing as _testing from pandas._typing import Dtype, FilePathOrBuffer, FrameOrSeries from pandas.compat import _get_lzma_file, _import_lzma @@ -64,6 +65,7 @@ TimedeltaArray, period_array, ) +from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin from pandas.io.common import urlopen from pandas.io.formats.printing import pprint_thing @@ -303,11 +305,54 @@ def write_to_compressed(compression, path, data, dest="test"): getattr(f, method)(*args) +def _get_tol_from_less_precise(check_less_precise: Union[bool, int]) -> float: + """ + Return the tolerance equivalent to the deprecated `check_less_precise` + parameter. + + Parameters + ---------- + check_less_precise : bool or int + + Returns + ------- + float + Tolerance to be used as relative/absolute tolerance. + + Examples + -------- + >>> # Using check_less_precise as a bool: + >>> _get_tol_from_less_precise(False) + 0.5e-5 + >>> _get_tol_from_less_precise(True) + 0.5e-3 + >>> # Using check_less_precise as an int representing the decimal + >>> # tolerance intended: + >>> _get_tol_from_less_precise(2) + 0.5e-2 + >>> _get_tol_from_less_precise(8) + 0.5e-8 + + """ + if isinstance(check_less_precise, bool): + if check_less_precise: + # 3-digit tolerance + return 0.5e-3 + else: + # 5-digit tolerance + return 0.5e-5 + else: + # Equivalent to setting checking_less_precise= + return 0.5 * 10 ** -check_less_precise + + def assert_almost_equal( left, right, check_dtype: Union[bool, str] = "equiv", - check_less_precise: Union[bool, int] = False, + check_less_precise: Union[bool, int] = no_default, + rtol: float = 1.0e-5, + atol: float = 1.0e-8, **kwargs, ): """ @@ -334,14 +379,37 @@ def assert_almost_equal( they are equivalent within the specified precision. Otherwise, we compare the **ratio** of the second number to the first number and check whether it is equivalent to 1 within the specified precision. + + .. deprecated:: 1.1.0 + Use `rtol` and `atol` instead to define relative/absolute + tolerance, respectively. Similar to :func:`math.isclose`. + rtol : float, default 1e-5 + Relative tolerance. + + .. versionadded:: 1.1.0 + atol : float, default 1e-8 + Absolute tolerance. + + .. versionadded:: 1.1.0 """ + if check_less_precise is not no_default: + warnings.warn( + "The 'check_less_precise' keyword in testing.assert_*_equal " + "is deprecated and will be removed in a future version. " + "You can stop passing 'check_less_precise' to silence this warning.", + FutureWarning, + stacklevel=2, + ) + rtol = atol = _get_tol_from_less_precise(check_less_precise) + if isinstance(left, pd.Index): assert_index_equal( left, right, check_exact=False, exact=check_dtype, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, **kwargs, ) @@ -351,7 +419,8 @@ def assert_almost_equal( right, check_exact=False, check_dtype=check_dtype, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, **kwargs, ) @@ -361,7 +430,8 @@ def assert_almost_equal( right, check_exact=False, check_dtype=check_dtype, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, **kwargs, ) @@ -381,11 +451,7 @@ def assert_almost_equal( obj = "Input" assert_class_equal(left, right, obj=obj) _testing.assert_almost_equal( - left, - right, - check_dtype=check_dtype, - check_less_precise=check_less_precise, - **kwargs, + left, right, check_dtype=check_dtype, rtol=rtol, atol=atol, **kwargs ) @@ -596,9 +662,11 @@ def assert_index_equal( right: Index, exact: Union[bool, str] = "equiv", check_names: bool = True, - check_less_precise: Union[bool, int] = False, + check_less_precise: Union[bool, int] = no_default, check_exact: bool = True, check_categorical: bool = True, + rtol: float = 1.0e-5, + atol: float = 1.0e-8, obj: str = "Index", ) -> None: """ @@ -618,10 +686,22 @@ def assert_index_equal( Specify comparison precision. Only used when check_exact is False. 5 digits (False) or 3 digits (True) after decimal points are compared. If int, then specify the digits to compare. + + .. deprecated:: 1.1.0 + Use `rtol` and `atol` instead to define relative/absolute + tolerance, respectively. Similar to :func:`math.isclose`. check_exact : bool, default True Whether to compare number exactly. check_categorical : bool, default True Whether to compare internal Categorical exactly. + rtol : float, default 1e-5 + Relative tolerance. Only used when check_exact is False. + + .. versionadded:: 1.1.0 + atol : float, default 1e-8 + Absolute tolerance. Only used when check_exact is False. + + .. versionadded:: 1.1.0 obj : str, default 'Index' Specify object name being compared, internally used to show appropriate assertion message. @@ -650,6 +730,16 @@ def _get_ilevel_values(index, level): values = unique._shallow_copy(filled, name=index.names[level]) return values + if check_less_precise is not no_default: + warnings.warn( + "The 'check_less_precise' keyword in testing.assert_*_equal " + "is deprecated and will be removed in a future version. " + "You can stop passing 'check_less_precise' to silence this warning.", + FutureWarning, + stacklevel=2, + ) + rtol = atol = _get_tol_from_less_precise(check_less_precise) + # instance validation _check_isinstance(left, right, Index) @@ -686,8 +776,9 @@ def _get_ilevel_values(index, level): rlevel, exact=exact, check_names=check_names, - check_less_precise=check_less_precise, check_exact=check_exact, + rtol=rtol, + atol=atol, obj=lobj, ) # get_level_values may change dtype @@ -703,7 +794,8 @@ def _get_ilevel_values(index, level): _testing.assert_almost_equal( left.values, right.values, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, check_dtype=exact, obj=obj, lobj=left, @@ -1028,9 +1120,11 @@ def assert_extension_array_equal( left, right, check_dtype=True, - check_less_precise=False, - check_exact=False, index_values=None, + check_less_precise=no_default, + check_exact=False, + rtol: float = 1.0e-5, + atol: float = 1.0e-8, ): """ Check that left and right ExtensionArrays are equal. @@ -1041,14 +1135,26 @@ def assert_extension_array_equal( The two arrays to compare. check_dtype : bool, default True Whether to check if the ExtensionArray dtypes are identical. + index_values : numpy.ndarray, default None + Optional index (shared by both left and right), used in output. check_less_precise : bool or int, default False Specify comparison precision. Only used when check_exact is False. 5 digits (False) or 3 digits (True) after decimal points are compared. If int, then specify the digits to compare. + + .. deprecated:: 1.1.0 + Use `rtol` and `atol` instead to define relative/absolute + tolerance, respectively. Similar to :func:`math.isclose`. check_exact : bool, default False Whether to compare number exactly. - index_values : numpy.ndarray, default None - Optional index (shared by both left and right), used in output. + rtol : float, default 1e-5 + Relative tolerance. Only used when check_exact is False. + + .. versionadded:: 1.1.0 + atol : float, default 1e-8 + Absolute tolerance. Only used when check_exact is False. + + .. versionadded:: 1.1.0 Notes ----- @@ -1056,12 +1162,26 @@ def assert_extension_array_equal( A mask of missing values is computed for each and checked to match. The remaining all-valid values are cast to object dtype and checked. """ + if check_less_precise is not no_default: + warnings.warn( + "The 'check_less_precise' keyword in testing.assert_*_equal " + "is deprecated and will be removed in a future version. " + "You can stop passing 'check_less_precise' to silence this warning.", + FutureWarning, + stacklevel=2, + ) + rtol = atol = _get_tol_from_less_precise(check_less_precise) + assert isinstance(left, ExtensionArray), "left is not an ExtensionArray" assert isinstance(right, ExtensionArray), "right is not an ExtensionArray" if check_dtype: assert_attr_equal("dtype", left, right, obj="ExtensionArray") - if hasattr(left, "asi8") and type(right) == type(left): + if ( + isinstance(left, DatetimeLikeArrayMixin) + and isinstance(right, DatetimeLikeArrayMixin) + and type(right) == type(left) + ): # Avoid slow object-dtype comparisons # np.asarray for case where we have a np.MaskedArray assert_numpy_array_equal( @@ -1086,7 +1206,8 @@ def assert_extension_array_equal( left_valid, right_valid, check_dtype=check_dtype, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, obj="ExtensionArray", index_values=index_values, ) @@ -1099,13 +1220,15 @@ def assert_series_equal( check_dtype=True, check_index_type="equiv", check_series_type=True, - check_less_precise=False, + check_less_precise=no_default, check_names=True, check_exact=False, check_datetimelike_compat=False, check_categorical=True, check_category_order=True, check_freq=True, + rtol=1.0e-5, + atol=1.0e-8, obj="Series", ): """ @@ -1132,6 +1255,10 @@ def assert_series_equal( they are equivalent within the specified precision. Otherwise, we compare the **ratio** of the second number to the first number and check whether it is equivalent to 1 within the specified precision. + + .. deprecated:: 1.1.0 + Use `rtol` and `atol` instead to define relative/absolute + tolerance, respectively. Similar to :func:`math.isclose`. check_names : bool, default True Whether to check the Series and Index names attribute. check_exact : bool, default False @@ -1146,6 +1273,12 @@ def assert_series_equal( .. versionadded:: 1.0.2 check_freq : bool, default True Whether to check the `freq` attribute on a DatetimeIndex or TimedeltaIndex. + rtol : float, default 1e-5 + Relative tolerance. Only used when check_exact is False. + + .. versionadded:: 1.1.0 + atol : float, default 1e-8 + Absolute tolerance. Only used when check_exact is False. .. versionadded:: 1.1.0 obj : str, default 'Series' @@ -1154,6 +1287,16 @@ def assert_series_equal( """ __tracebackhide__ = True + if check_less_precise is not no_default: + warnings.warn( + "The 'check_less_precise' keyword in testing.assert_*_equal " + "is deprecated and will be removed in a future version. " + "You can stop passing 'check_less_precise' to silence this warning.", + FutureWarning, + stacklevel=2, + ) + rtol = atol = _get_tol_from_less_precise(check_less_precise) + # instance validation _check_isinstance(left, right, Series) @@ -1172,9 +1315,10 @@ def assert_series_equal( right.index, exact=check_index_type, check_names=check_names, - check_less_precise=check_less_precise, check_exact=check_exact, check_categorical=check_categorical, + rtol=rtol, + atol=atol, obj=f"{obj}.index", ) if check_freq and isinstance(left.index, (pd.DatetimeIndex, pd.TimedeltaIndex)): @@ -1227,7 +1371,8 @@ def assert_series_equal( _testing.assert_almost_equal( left._values, right._values, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, check_dtype=check_dtype, obj=str(obj), index_values=np.asarray(left.index), @@ -1245,7 +1390,8 @@ def assert_series_equal( _testing.assert_almost_equal( left._values, right._values, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, check_dtype=check_dtype, obj=str(obj), index_values=np.asarray(left.index), @@ -1273,7 +1419,7 @@ def assert_frame_equal( check_index_type="equiv", check_column_type="equiv", check_frame_type=True, - check_less_precise=False, + check_less_precise=no_default, check_names=True, by_blocks=False, check_exact=False, @@ -1281,6 +1427,8 @@ def assert_frame_equal( check_categorical=True, check_like=False, check_freq=True, + rtol=1.0e-5, + atol=1.0e-8, obj="DataFrame", ): """ @@ -1318,6 +1466,10 @@ def assert_frame_equal( they are equivalent within the specified precision. Otherwise, we compare the **ratio** of the second number to the first number and check whether it is equivalent to 1 within the specified precision. + + .. deprecated:: 1.1.0 + Use `rtol` and `atol` instead to define relative/absolute + tolerance, respectively. Similar to :func:`math.isclose`. check_names : bool, default True Whether to check that the `names` attribute for both the `index` and `column` attributes of the DataFrame is identical. @@ -1336,6 +1488,12 @@ def assert_frame_equal( (same as in columns) - same labels must be with the same data. check_freq : bool, default True Whether to check the `freq` attribute on a DatetimeIndex or TimedeltaIndex. + rtol : float, default 1e-5 + Relative tolerance. Only used when check_exact is False. + + .. versionadded:: 1.1.0 + atol : float, default 1e-8 + Absolute tolerance. Only used when check_exact is False. .. versionadded:: 1.1.0 obj : str, default 'DataFrame' @@ -1377,6 +1535,16 @@ def assert_frame_equal( """ __tracebackhide__ = True + if check_less_precise is not no_default: + warnings.warn( + "The 'check_less_precise' keyword in testing.assert_*_equal " + "is deprecated and will be removed in a future version. " + "You can stop passing 'check_less_precise' to silence this warning.", + FutureWarning, + stacklevel=2, + ) + rtol = atol = _get_tol_from_less_precise(check_less_precise) + # instance validation _check_isinstance(left, right, DataFrame) @@ -1399,9 +1567,10 @@ def assert_frame_equal( right.index, exact=check_index_type, check_names=check_names, - check_less_precise=check_less_precise, check_exact=check_exact, check_categorical=check_categorical, + rtol=rtol, + atol=atol, obj=f"{obj}.index", ) @@ -1411,9 +1580,10 @@ def assert_frame_equal( right.columns, exact=check_column_type, check_names=check_names, - check_less_precise=check_less_precise, check_exact=check_exact, check_categorical=check_categorical, + rtol=rtol, + atol=atol, obj=f"{obj}.columns", ) @@ -1439,13 +1609,14 @@ def assert_frame_equal( rcol, check_dtype=check_dtype, check_index_type=check_index_type, - check_less_precise=check_less_precise, check_exact=check_exact, check_names=check_names, check_datetimelike_compat=check_datetimelike_compat, check_categorical=check_categorical, check_freq=check_freq, obj=f'{obj}.iloc[:, {i}] (column name="{col}")', + rtol=rtol, + atol=atol, ) diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index db21161f84cf7..db8bb5ca3c437 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -32,7 +32,8 @@ def assert_stat_op_calc( has_skipna=True, check_dtype=True, check_dates=False, - check_less_precise=False, + rtol=1e-5, + atol=1e-8, skipna_alternative=None, ): """ @@ -54,9 +55,10 @@ def assert_stat_op_calc( "alternative(frame)" should be checked. check_dates : bool, default false Whether opname should be tested on a Datetime Series - check_less_precise : bool, default False - Whether results should only be compared approximately; - passed on to tm.assert_series_equal + rtol : float, default 1e-5 + Relative tolerance. + atol : float, default 1e-8 + Absolute tolerance. skipna_alternative : function, default None NaN-safe version of alternative """ @@ -87,14 +89,16 @@ def wrapper(x): result0, frame.apply(wrapper), check_dtype=check_dtype, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, ) # HACK: win32 tm.assert_series_equal( result1, frame.apply(wrapper, axis=1), check_dtype=False, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, ) else: skipna_wrapper = alternative @@ -105,13 +109,14 @@ def wrapper(x): result0, frame.apply(skipna_wrapper), check_dtype=check_dtype, - check_less_precise=check_less_precise, + rtol=rtol, + atol=atol, ) if opname in ["sum", "prod"]: expected = frame.apply(skipna_wrapper, axis=1) tm.assert_series_equal( - result1, expected, check_dtype=False, check_less_precise=check_less_precise + result1, expected, check_dtype=False, rtol=rtol, atol=atol, ) # check dtypes @@ -339,7 +344,7 @@ def kurt(x): np.sum, mixed_float_frame.astype("float32"), check_dtype=False, - check_less_precise=True, + rtol=1e-3, ) assert_stat_op_calc( diff --git a/pandas/tests/groupby/test_function.py b/pandas/tests/groupby/test_function.py index 9303a084f1e71..6f19ec40c2520 100644 --- a/pandas/tests/groupby/test_function.py +++ b/pandas/tests/groupby/test_function.py @@ -649,7 +649,7 @@ def test_nlargest_mi_grouper(): ] expected = Series(exp_values, index=exp_idx) - tm.assert_series_equal(result, expected, check_exact=False) + tm.assert_series_equal(result, expected, check_exact=False, rtol=1e-3) def test_nsmallest(): diff --git a/pandas/tests/io/json/test_ujson.py b/pandas/tests/io/json/test_ujson.py index 7dc73d5be1538..7b6acf7eed685 100644 --- a/pandas/tests/io/json/test_ujson.py +++ b/pandas/tests/io/json/test_ujson.py @@ -1081,9 +1081,7 @@ def test_decode_array_with_big_int(self): @pytest.mark.parametrize("sign", [-1, 1]) def test_decode_floating_point(self, sign, float_number): float_number *= sign - tm.assert_almost_equal( - float_number, ujson.loads(str(float_number)), check_less_precise=15 - ) + tm.assert_almost_equal(float_number, ujson.loads(str(float_number)), rtol=1e-15) def test_encode_big_set(self): s = set() diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 70713768c8d1e..a07e7a74b7573 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -2389,7 +2389,7 @@ def test_write_row_by_row(self): result = sql.read_sql("select * from test", con=self.conn) result.index = frame.index - tm.assert_frame_equal(result, frame, check_less_precise=True) + tm.assert_frame_equal(result, frame, rtol=1e-3) def test_execute(self): frame = tm.makeTimeDataFrame() @@ -2649,7 +2649,7 @@ def test_write_row_by_row(self): result = sql.read_sql("select * from test", con=self.conn) result.index = frame.index - tm.assert_frame_equal(result, frame, check_less_precise=True) + tm.assert_frame_equal(result, frame, rtol=1e-3) # GH#32571 result comes back rounded to 6 digits in some builds; # no obvious pattern diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index e54f4784e9c4f..df2c9ecbd7a0a 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -201,19 +201,19 @@ def test_conversion(self): assert rs[1] == xp def test_conversion_float(self): - decimals = 9 + rtol = 0.5 * 10 ** -9 rs = self.dtc.convert(Timestamp("2012-1-1 01:02:03", tz="UTC"), None, None) xp = converter.dates.date2num(Timestamp("2012-1-1 01:02:03", tz="UTC")) - tm.assert_almost_equal(rs, xp, decimals) + tm.assert_almost_equal(rs, xp, rtol=rtol) rs = self.dtc.convert( Timestamp("2012-1-1 09:02:03", tz="Asia/Hong_Kong"), None, None ) - tm.assert_almost_equal(rs, xp, decimals) + tm.assert_almost_equal(rs, xp, rtol=rtol) rs = self.dtc.convert(datetime(2012, 1, 1, 1, 2, 3), None, None) - tm.assert_almost_equal(rs, xp, decimals) + tm.assert_almost_equal(rs, xp, rtol=rtol) def test_conversion_outofbounds_datetime(self): # 2579 @@ -249,13 +249,13 @@ def test_time_formatter(self, time, format_expected): assert result == format_expected def test_dateindex_conversion(self): - decimals = 9 + rtol = 10 ** -9 for freq in ("B", "L", "S"): dateindex = tm.makeDateIndex(k=10, freq=freq) rs = self.dtc.convert(dateindex, None, None) xp = converter.dates.date2num(dateindex._mpl_repr()) - tm.assert_almost_equal(rs, xp, decimals) + tm.assert_almost_equal(rs, xp, rtol=rtol) def test_resolution(self): def _assert_less(ts1, ts2): diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py index 44a8452964f5a..a080bf0feaebc 100644 --- a/pandas/tests/test_algos.py +++ b/pandas/tests/test_algos.py @@ -1472,7 +1472,7 @@ def test_group_var_generic_2d_some_nan(self): expected_counts = counts + 2 self.algo(out, counts, values, labels) - tm.assert_almost_equal(out, expected_out, check_less_precise=6) + tm.assert_almost_equal(out, expected_out, rtol=0.5e-06) tm.assert_numpy_array_equal(counts, expected_counts) def test_group_var_constant(self): @@ -1510,7 +1510,7 @@ def test_group_var_large_inputs(self): self.algo(out, counts, values, labels) assert counts[0] == 10 ** 6 - tm.assert_almost_equal(out[0, 0], 1.0 / 12, check_less_precise=True) + tm.assert_almost_equal(out[0, 0], 1.0 / 12, rtol=0.5e-3) class TestGroupVarFloat32(GroupVarTestMixin): diff --git a/pandas/tests/test_nanops.py b/pandas/tests/test_nanops.py index cac6a59527a6e..0d60e6e8a978f 100644 --- a/pandas/tests/test_nanops.py +++ b/pandas/tests/test_nanops.py @@ -782,27 +782,27 @@ def setup_method(self, method): def test_nanvar_all_finite(self): samples = self.samples actual_variance = nanops.nanvar(samples) - tm.assert_almost_equal(actual_variance, self.variance, check_less_precise=2) + tm.assert_almost_equal(actual_variance, self.variance, rtol=1e-2) def test_nanvar_nans(self): samples = np.nan * np.ones(2 * self.samples.shape[0]) samples[::2] = self.samples actual_variance = nanops.nanvar(samples, skipna=True) - tm.assert_almost_equal(actual_variance, self.variance, check_less_precise=2) + tm.assert_almost_equal(actual_variance, self.variance, rtol=1e-2) actual_variance = nanops.nanvar(samples, skipna=False) - tm.assert_almost_equal(actual_variance, np.nan, check_less_precise=2) + tm.assert_almost_equal(actual_variance, np.nan, rtol=1e-2) def test_nanstd_nans(self): samples = np.nan * np.ones(2 * self.samples.shape[0]) samples[::2] = self.samples actual_std = nanops.nanstd(samples, skipna=True) - tm.assert_almost_equal(actual_std, self.variance ** 0.5, check_less_precise=2) + tm.assert_almost_equal(actual_std, self.variance ** 0.5, rtol=1e-2) actual_std = nanops.nanvar(samples, skipna=False) - tm.assert_almost_equal(actual_std, np.nan, check_less_precise=2) + tm.assert_almost_equal(actual_std, np.nan, rtol=1e-2) def test_nanvar_axis(self): # Generate some sample data. @@ -812,7 +812,7 @@ def test_nanvar_axis(self): actual_variance = nanops.nanvar(samples, axis=1) tm.assert_almost_equal( - actual_variance, np.array([self.variance, 1.0 / 12]), check_less_precise=2 + actual_variance, np.array([self.variance, 1.0 / 12]), rtol=1e-2 ) def test_nanvar_ddof(self): @@ -826,15 +826,13 @@ def test_nanvar_ddof(self): # The unbiased estimate. var = 1.0 / 12 - tm.assert_almost_equal(variance_1, var, check_less_precise=2) + tm.assert_almost_equal(variance_1, var, rtol=1e-2) # The underestimated variance. - tm.assert_almost_equal(variance_0, (n - 1.0) / n * var, check_less_precise=2) + tm.assert_almost_equal(variance_0, (n - 1.0) / n * var, rtol=1e-2) # The overestimated variance. - tm.assert_almost_equal( - variance_2, (n - 1.0) / (n - 2.0) * var, check_less_precise=2 - ) + tm.assert_almost_equal(variance_2, (n - 1.0) / (n - 2.0) * var, rtol=1e-2) def test_ground_truth(self): # Test against values that were precomputed with Numpy. diff --git a/pandas/tests/util/conftest.py b/pandas/tests/util/conftest.py index 5eff49ab774b5..b68bcc93431d0 100644 --- a/pandas/tests/util/conftest.py +++ b/pandas/tests/util/conftest.py @@ -16,8 +16,8 @@ def check_index_type(request): return request.param -@pytest.fixture(params=[True, False]) -def check_less_precise(request): +@pytest.fixture(params=[0.5e-3, 0.5e-5]) +def rtol(request): return request.param diff --git a/pandas/tests/util/test_assert_almost_equal.py b/pandas/tests/util/test_assert_almost_equal.py index b8048891e4876..c25668c33bfc4 100644 --- a/pandas/tests/util/test_assert_almost_equal.py +++ b/pandas/tests/util/test_assert_almost_equal.py @@ -17,7 +17,7 @@ def _assert_almost_equal_both(a, b, **kwargs): The first object to compare. b : object The second object to compare. - kwargs : dict + **kwargs The arguments passed to `tm.assert_almost_equal`. """ tm.assert_almost_equal(a, b, **kwargs) @@ -34,7 +34,7 @@ def _assert_not_almost_equal(a, b, **kwargs): The first object to compare. b : object The second object to compare. - kwargs : dict + **kwargs The arguments passed to `tm.assert_almost_equal`. """ try: @@ -57,13 +57,23 @@ def _assert_not_almost_equal_both(a, b, **kwargs): The first object to compare. b : object The second object to compare. - kwargs : dict + **kwargs The arguments passed to `tm.assert_almost_equal`. """ _assert_not_almost_equal(a, b, **kwargs) _assert_not_almost_equal(b, a, **kwargs) +@pytest.mark.parametrize( + "a,b,check_less_precise", + [(1.1, 1.1, False), (1.1, 1.100001, True), (1.1, 1.1001, 2)], +) +def test_assert_almost_equal_deprecated(a, b, check_less_precise): + # GH#30562 + with tm.assert_produces_warning(FutureWarning): + _assert_almost_equal_both(a, b, check_less_precise=check_less_precise) + + @pytest.mark.parametrize( "a,b", [ @@ -78,12 +88,65 @@ def test_assert_almost_equal_numbers(a, b): _assert_almost_equal_both(a, b) -@pytest.mark.parametrize("a,b", [(1.1, 1), (1.1, True), (1, 2), (1.0001, np.int16(1))]) +@pytest.mark.parametrize( + "a,b", + [ + (1.1, 1), + (1.1, True), + (1, 2), + (1.0001, np.int16(1)), + # The following two examples are not "almost equal" due to tol. + (0.1, 0.1001), + (0.0011, 0.0012), + ], +) def test_assert_not_almost_equal_numbers(a, b): _assert_not_almost_equal_both(a, b) -@pytest.mark.parametrize("a,b", [(0, 0), (0, 0.0), (0, np.float64(0)), (0.000001, 0)]) +@pytest.mark.parametrize( + "a,b", + [ + (1.1, 1.1), + (1.1, 1.100001), + (1.1, 1.1001), + (0.000001, 0.000005), + (1000.0, 1000.0005), + # Testing this example, as per #13357 + (0.000011, 0.000012), + ], +) +def test_assert_almost_equal_numbers_atol(a, b): + # Equivalent to the deprecated check_less_precise=True + _assert_almost_equal_both(a, b, rtol=0.5e-3, atol=0.5e-3) + + +@pytest.mark.parametrize("a,b", [(1.1, 1.11), (0.1, 0.101), (0.000011, 0.001012)]) +def test_assert_not_almost_equal_numbers_atol(a, b): + _assert_not_almost_equal_both(a, b, atol=1e-3) + + +@pytest.mark.parametrize( + "a,b", + [ + (1.1, 1.1), + (1.1, 1.100001), + (1.1, 1.1001), + (1000.0, 1000.0005), + (1.1, 1.11), + (0.1, 0.101), + ], +) +def test_assert_almost_equal_numbers_rtol(a, b): + _assert_almost_equal_both(a, b, rtol=0.05) + + +@pytest.mark.parametrize("a,b", [(0.000011, 0.000012), (0.000001, 0.000005)]) +def test_assert_not_almost_equal_numbers_rtol(a, b): + _assert_not_almost_equal_both(a, b, rtol=0.05) + + +@pytest.mark.parametrize("a,b", [(0, 0), (0, 0.0), (0, np.float64(0)), (0.00000001, 0)]) def test_assert_almost_equal_numbers_with_zeros(a, b): _assert_almost_equal_both(a, b) @@ -235,7 +298,7 @@ def test_assert_almost_equal_object(): def test_assert_almost_equal_value_mismatch(): - msg = "expected 2\\.00000 but got 1\\.00000, with decimal 5" + msg = "expected 2\\.00000 but got 1\\.00000, with rtol=1e-05, atol=1e-08" with pytest.raises(AssertionError, match=msg): tm.assert_almost_equal(1, 2) diff --git a/pandas/tests/util/test_assert_extension_array_equal.py b/pandas/tests/util/test_assert_extension_array_equal.py index 0547323b882f6..d9fdf1491c328 100644 --- a/pandas/tests/util/test_assert_extension_array_equal.py +++ b/pandas/tests/util/test_assert_extension_array_equal.py @@ -32,16 +32,13 @@ def test_assert_extension_array_equal_not_exact(kwargs): tm.assert_extension_array_equal(arr1, arr2, **kwargs) -@pytest.mark.parametrize( - "check_less_precise", [True, False, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -) -def test_assert_extension_array_equal_less_precise(check_less_precise): +@pytest.mark.parametrize("decimals", range(10)) +def test_assert_extension_array_equal_less_precise(decimals): + rtol = 0.5 * 10 ** -decimals arr1 = SparseArray([0.5, 0.123456]) arr2 = SparseArray([0.5, 0.123457]) - kwargs = dict(check_less_precise=check_less_precise) - - if check_less_precise is False or check_less_precise >= 5: + if decimals >= 5: msg = """\ ExtensionArray are different @@ -50,9 +47,9 @@ def test_assert_extension_array_equal_less_precise(check_less_precise): \\[right\\]: \\[0\\.5, 0\\.123457\\]""" with pytest.raises(AssertionError, match=msg): - tm.assert_extension_array_equal(arr1, arr2, **kwargs) + tm.assert_extension_array_equal(arr1, arr2, rtol=rtol) else: - tm.assert_extension_array_equal(arr1, arr2, **kwargs) + tm.assert_extension_array_equal(arr1, arr2, rtol=rtol) def test_assert_extension_array_equal_dtype_mismatch(check_dtype): diff --git a/pandas/tests/util/test_assert_index_equal.py b/pandas/tests/util/test_assert_index_equal.py index bbbeebcec2569..125af6ef78593 100644 --- a/pandas/tests/util/test_assert_index_equal.py +++ b/pandas/tests/util/test_assert_index_equal.py @@ -82,12 +82,12 @@ def test_index_equal_values_close(check_exact): tm.assert_index_equal(idx1, idx2, check_exact=check_exact) -def test_index_equal_values_less_close(check_exact, check_less_precise): +def test_index_equal_values_less_close(check_exact, rtol): idx1 = Index([1, 2, 3.0]) idx2 = Index([1, 2, 3.0001]) - kwargs = dict(check_exact=check_exact, check_less_precise=check_less_precise) + kwargs = dict(check_exact=check_exact, rtol=rtol) - if check_exact or not check_less_precise: + if check_exact or rtol < 0.5e-3: msg = """Index are different Index values are different \\(33\\.33333 %\\) @@ -100,10 +100,10 @@ def test_index_equal_values_less_close(check_exact, check_less_precise): tm.assert_index_equal(idx1, idx2, **kwargs) -def test_index_equal_values_too_far(check_exact, check_less_precise): +def test_index_equal_values_too_far(check_exact, rtol): idx1 = Index([1, 2, 3]) idx2 = Index([1, 2, 4]) - kwargs = dict(check_exact=check_exact, check_less_precise=check_less_precise) + kwargs = dict(check_exact=check_exact, rtol=rtol) msg = """Index are different @@ -115,10 +115,10 @@ def test_index_equal_values_too_far(check_exact, check_less_precise): tm.assert_index_equal(idx1, idx2, **kwargs) -def test_index_equal_level_values_mismatch(check_exact, check_less_precise): +def test_index_equal_level_values_mismatch(check_exact, rtol): idx1 = MultiIndex.from_tuples([("A", 2), ("A", 2), ("B", 3), ("B", 4)]) idx2 = MultiIndex.from_tuples([("A", 1), ("A", 2), ("B", 3), ("B", 4)]) - kwargs = dict(check_exact=check_exact, check_less_precise=check_less_precise) + kwargs = dict(check_exact=check_exact, rtol=rtol) msg = """MultiIndex level \\[1\\] are different diff --git a/pandas/tests/util/test_assert_series_equal.py b/pandas/tests/util/test_assert_series_equal.py index 337a06b91e443..859c8474562a3 100644 --- a/pandas/tests/util/test_assert_series_equal.py +++ b/pandas/tests/util/test_assert_series_equal.py @@ -102,22 +102,20 @@ def test_series_not_equal_metadata_mismatch(kwargs): @pytest.mark.parametrize("data1,data2", [(0.12345, 0.12346), (0.1235, 0.1236)]) @pytest.mark.parametrize("dtype", ["float32", "float64"]) -@pytest.mark.parametrize("check_less_precise", [False, True, 0, 1, 2, 3, 10]) -def test_less_precise(data1, data2, dtype, check_less_precise): +@pytest.mark.parametrize("decimals", [0, 1, 2, 3, 5, 10]) +def test_less_precise(data1, data2, dtype, decimals): + rtol = 10 ** -decimals s1 = Series([data1], dtype=dtype) s2 = Series([data2], dtype=dtype) - kwargs = dict(check_less_precise=check_less_precise) - - if (check_less_precise is False or check_less_precise == 10) or ( - (check_less_precise is True or check_less_precise >= 3) - and abs(data1 - data2) >= 0.0001 + if (decimals == 5 or decimals == 10) or ( + decimals >= 3 and abs(data1 - data2) >= 0.0005 ): msg = "Series values are different" with pytest.raises(AssertionError, match=msg): - tm.assert_series_equal(s1, s2, **kwargs) + tm.assert_series_equal(s1, s2, rtol=rtol) else: - _assert_series_equal_both(s1, s2, **kwargs) + _assert_series_equal_both(s1, s2, rtol=rtol) @pytest.mark.parametrize( @@ -151,7 +149,7 @@ def test_series_equal_index_dtype(s1, s2, msg, check_index_type): tm.assert_series_equal(s1, s2, **kwargs) -def test_series_equal_length_mismatch(check_less_precise): +def test_series_equal_length_mismatch(rtol): msg = """Series are different Series length are different @@ -162,10 +160,10 @@ def test_series_equal_length_mismatch(check_less_precise): s2 = Series([1, 2, 3, 4]) with pytest.raises(AssertionError, match=msg): - tm.assert_series_equal(s1, s2, check_less_precise=check_less_precise) + tm.assert_series_equal(s1, s2, rtol=rtol) -def test_series_equal_numeric_values_mismatch(check_less_precise): +def test_series_equal_numeric_values_mismatch(rtol): msg = """Series are different Series values are different \\(33\\.33333 %\\) @@ -177,10 +175,10 @@ def test_series_equal_numeric_values_mismatch(check_less_precise): s2 = Series([1, 2, 4]) with pytest.raises(AssertionError, match=msg): - tm.assert_series_equal(s1, s2, check_less_precise=check_less_precise) + tm.assert_series_equal(s1, s2, rtol=rtol) -def test_series_equal_categorical_values_mismatch(check_less_precise): +def test_series_equal_categorical_values_mismatch(rtol): msg = """Series are different Series values are different \\(66\\.66667 %\\) @@ -194,10 +192,10 @@ def test_series_equal_categorical_values_mismatch(check_less_precise): s2 = Series(Categorical(["a", "c", "b"])) with pytest.raises(AssertionError, match=msg): - tm.assert_series_equal(s1, s2, check_less_precise=check_less_precise) + tm.assert_series_equal(s1, s2, rtol=rtol) -def test_series_equal_datetime_values_mismatch(check_less_precise): +def test_series_equal_datetime_values_mismatch(rtol): msg = """numpy array are different numpy array values are different \\(100.0 %\\) @@ -209,7 +207,7 @@ def test_series_equal_datetime_values_mismatch(check_less_precise): s2 = Series(pd.date_range("2019-02-02", periods=3, freq="D")) with pytest.raises(AssertionError, match=msg): - tm.assert_series_equal(s1, s2, check_less_precise=check_less_precise) + tm.assert_series_equal(s1, s2, rtol=rtol) def test_series_equal_categorical_mismatch(check_categorical):