Skip to content

Commit 0910110

Browse files
author
Joao Veiga
committed
ENH: Allow absolute precision in assert_almost_equal (pandas-dev#13357)
This commit adds a new keyword argument `check_low_values`, that will allow the approximate comparison of numerics based on literal decimal places. This is particularly useful when comparing low values: # This fails because it's doing (1 - .1 / .1001) assert_almost_equal(0.1, 0.1001, check_less_precise=True) # This will work as intuitively expected assert_almost_equal( 0.1, 0.1001, check_less_precise=True, check_low_values=True )
1 parent db062da commit 0910110

File tree

3 files changed

+80
-9
lines changed

3 files changed

+80
-9
lines changed

pandas/_libs/testing.pyx

+14-8
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ cpdef assert_dict_equal(a, b, bint compare_keys=True):
6464

6565
cpdef assert_almost_equal(a, b,
6666
check_less_precise=False,
67+
check_low_values=False,
6768
bint check_dtype=True,
6869
obj=None, lobj=None, robj=None):
6970
"""
@@ -77,18 +78,21 @@ cpdef assert_almost_equal(a, b,
7778
Specify comparison precision.
7879
5 digits (False) or 3 digits (True) after decimal points are
7980
compared. If an integer, then this will be the number of decimal
80-
points to compare
81+
points to compare.
82+
check_low_values : bool, default False
83+
Use absolute comparison precision, instead of relative. This is
84+
particularly useful when comparing values between -1 and 1.
8185
check_dtype: bool, default True
82-
check dtype if both a and b are np.ndarray
86+
check dtype if both a and b are np.ndarray.
8387
obj : str, default None
8488
Specify object name being compared, internally used to show
85-
appropriate assertion message
89+
appropriate assertion message.
8690
lobj : str, default None
8791
Specify left object name being compared, internally used to show
88-
appropriate assertion message
92+
appropriate assertion message.
8993
robj : str, default None
9094
Specify right object name being compared, internally used to show
91-
appropriate assertion message
95+
appropriate assertion message.
9296
"""
9397
cdef:
9498
int decimal
@@ -103,6 +107,7 @@ cpdef assert_almost_equal(a, b,
103107
robj = b
104108

105109
assert isinstance(check_less_precise, (int, bool))
110+
assert isinstance(check_low_values, bool)
106111

107112
if isinstance(a, dict) or isinstance(b, dict):
108113
return assert_dict_equal(a, b)
@@ -162,7 +167,8 @@ cpdef assert_almost_equal(a, b,
162167
for i in range(len(a)):
163168
try:
164169
assert_almost_equal(a[i], b[i],
165-
check_less_precise=check_less_precise)
170+
check_less_precise=check_less_precise,
171+
check_low_values=check_low_values)
166172
except AssertionError:
167173
is_unequal = True
168174
diff += 1
@@ -203,8 +209,8 @@ cpdef assert_almost_equal(a, b,
203209

204210
fa, fb = a, b
205211

206-
# case for zero
207-
if abs(fa) < 1e-5:
212+
# case for zero, or explicit low value comparison
213+
if abs(fa) < 1e-5 or check_low_values:
208214
if not decimal_almost_equal(fa, fb, decimal):
209215
assert False, (f'(very low values) expected {fb:.5f} '
210216
f'but got {fa:.5f}, with decimal {decimal}')

pandas/tests/util/test_assert_almost_equal.py

+29
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,35 @@ def test_assert_almost_equal_numbers(a, b):
8080
_assert_almost_equal_both(a, b)
8181

8282

83+
@pytest.mark.parametrize(
84+
"a,b",
85+
[
86+
(1.1, 1.1),
87+
(1.1, 1.100001),
88+
# check_less_precise=True allows 1.1 vs 1.1001
89+
(1.1, 1.1001),
90+
],
91+
)
92+
def test_assert_almost_equal_numbers_less_precise(a, b):
93+
_assert_almost_equal_both(a, b, check_less_precise=True)
94+
95+
96+
@pytest.mark.parametrize(
97+
"a,b",
98+
[
99+
(1.1, 1.1),
100+
(1.1, 1.100001),
101+
(1.1, 1.1001),
102+
# check_low_values allows for 0.1 vs 0.1001
103+
(0.1, 0.1001),
104+
# Testing this example, as per #13357
105+
(0.000011, 0.000012),
106+
],
107+
)
108+
def test_assert_almost_equal_numbers_low_values(a, b):
109+
_assert_almost_equal_both(a, b, check_less_precise=True, check_low_values=True)
110+
111+
83112
@pytest.mark.parametrize("a,b", [(1.1, 1), (1.1, True), (1, 2), (1.0001, np.int16(1))])
84113
def test_assert_not_almost_equal_numbers(a, b):
85114
_assert_not_almost_equal_both(a, b)

pandas/util/testing.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ def assert_almost_equal(
279279
right,
280280
check_dtype: Union[bool, str] = "equiv",
281281
check_less_precise: Union[bool, int] = False,
282+
check_low_values: bool = False,
282283
**kwargs,
283284
):
284285
"""
@@ -305,6 +306,9 @@ def assert_almost_equal(
305306
they are equivalent within the specified precision. Otherwise, we
306307
compare the **ratio** of the second number to the first number and
307308
check whether it is equivalent to 1 within the specified precision.
309+
check_low_values : bool, default False
310+
Use absolute comparison precision, instead of relative. This is
311+
particularly useful when comparing values between -1 and 1.
308312
"""
309313
if isinstance(left, pd.Index):
310314
assert_index_equal(
@@ -313,6 +317,7 @@ def assert_almost_equal(
313317
check_exact=False,
314318
exact=check_dtype,
315319
check_less_precise=check_less_precise,
320+
check_low_values=check_low_values,
316321
**kwargs,
317322
)
318323

@@ -323,6 +328,7 @@ def assert_almost_equal(
323328
check_exact=False,
324329
check_dtype=check_dtype,
325330
check_less_precise=check_less_precise,
331+
check_low_values=check_low_values,
326332
**kwargs,
327333
)
328334

@@ -333,6 +339,7 @@ def assert_almost_equal(
333339
check_exact=False,
334340
check_dtype=check_dtype,
335341
check_less_precise=check_less_precise,
342+
check_low_values=check_low_values,
336343
**kwargs,
337344
)
338345

@@ -356,6 +363,7 @@ def assert_almost_equal(
356363
right,
357364
check_dtype=check_dtype,
358365
check_less_precise=check_less_precise,
366+
check_low_values=check_low_values,
359367
**kwargs,
360368
)
361369

@@ -570,6 +578,7 @@ def assert_index_equal(
570578
exact: Union[bool, str] = "equiv",
571579
check_names: bool = True,
572580
check_less_precise: Union[bool, int] = False,
581+
check_low_values: bool = False,
573582
check_exact: bool = True,
574583
check_categorical: bool = True,
575584
obj: str = "Index",
@@ -591,6 +600,9 @@ def assert_index_equal(
591600
Specify comparison precision. Only used when check_exact is False.
592601
5 digits (False) or 3 digits (True) after decimal points are compared.
593602
If int, then specify the digits to compare.
603+
check_low_values : bool, default False
604+
Use absolute comparison precision, instead of relative. This is
605+
particularly useful when comparing values between -1 and 1.
594606
check_exact : bool, default True
595607
Whether to compare number exactly.
596608
check_categorical : bool, default True
@@ -660,6 +672,7 @@ def _get_ilevel_values(index, level):
660672
exact=exact,
661673
check_names=check_names,
662674
check_less_precise=check_less_precise,
675+
check_low_values=check_low_values,
663676
check_exact=check_exact,
664677
obj=lobj,
665678
)
@@ -677,6 +690,7 @@ def _get_ilevel_values(index, level):
677690
left.values,
678691
right.values,
679692
check_less_precise=check_less_precise,
693+
check_low_values=check_low_values,
680694
check_dtype=exact,
681695
obj=obj,
682696
lobj=left,
@@ -989,7 +1003,12 @@ def _raise(left, right, err_msg):
9891003

9901004

9911005
def assert_extension_array_equal(
992-
left, right, check_dtype=True, check_less_precise=False, check_exact=False
1006+
left,
1007+
right,
1008+
check_dtype=True,
1009+
check_less_precise=False,
1010+
check_low_values=False,
1011+
check_exact=False,
9931012
):
9941013
"""Check that left and right ExtensionArrays are equal.
9951014
@@ -1003,6 +1022,9 @@ def assert_extension_array_equal(
10031022
Specify comparison precision. Only used when check_exact is False.
10041023
5 digits (False) or 3 digits (True) after decimal points are compared.
10051024
If int, then specify the digits to compare.
1025+
check_low_values : bool, default False
1026+
Use absolute comparison precision, instead of relative. This is
1027+
particularly useful when comparing values between -1 and 1.
10061028
check_exact : bool, default False
10071029
Whether to compare number exactly.
10081030
@@ -1036,6 +1058,7 @@ def assert_extension_array_equal(
10361058
right_valid,
10371059
check_dtype=check_dtype,
10381060
check_less_precise=check_less_precise,
1061+
check_low_values=check_low_values,
10391062
obj="ExtensionArray",
10401063
)
10411064

@@ -1048,6 +1071,7 @@ def assert_series_equal(
10481071
check_index_type="equiv",
10491072
check_series_type=True,
10501073
check_less_precise=False,
1074+
check_low_values=False,
10511075
check_names=True,
10521076
check_exact=False,
10531077
check_datetimelike_compat=False,
@@ -1078,6 +1102,9 @@ def assert_series_equal(
10781102
they are equivalent within the specified precision. Otherwise, we
10791103
compare the **ratio** of the second number to the first number and
10801104
check whether it is equivalent to 1 within the specified precision.
1105+
check_low_values : bool, default False
1106+
Use absolute comparison precision, instead of relative. This is
1107+
particularly useful when comparing values between -1 and 1.
10811108
check_names : bool, default True
10821109
Whether to check the Series and Index names attribute.
10831110
check_exact : bool, default False
@@ -1114,6 +1141,7 @@ def assert_series_equal(
11141141
exact=check_index_type,
11151142
check_names=check_names,
11161143
check_less_precise=check_less_precise,
1144+
check_low_values=check_low_values,
11171145
check_exact=check_exact,
11181146
check_categorical=check_categorical,
11191147
obj=f"{obj}.index",
@@ -1178,6 +1206,7 @@ def assert_series_equal(
11781206
left._internal_get_values(),
11791207
right._internal_get_values(),
11801208
check_less_precise=check_less_precise,
1209+
check_low_values=check_low_values,
11811210
check_dtype=check_dtype,
11821211
obj=str(obj),
11831212
)
@@ -1200,6 +1229,7 @@ def assert_frame_equal(
12001229
check_column_type="equiv",
12011230
check_frame_type=True,
12021231
check_less_precise=False,
1232+
check_low_values=False,
12031233
check_names=True,
12041234
by_blocks=False,
12051235
check_exact=False,
@@ -1243,6 +1273,9 @@ def assert_frame_equal(
12431273
they are equivalent within the specified precision. Otherwise, we
12441274
compare the **ratio** of the second number to the first number and
12451275
check whether it is equivalent to 1 within the specified precision.
1276+
check_low_values : bool, default False
1277+
Use absolute comparison precision, instead of relative. This is
1278+
particularly useful when comparing values between -1 and 1.
12461279
check_names : bool, default True
12471280
Whether to check that the `names` attribute for both the `index`
12481281
and `column` attributes of the DataFrame is identical.
@@ -1321,6 +1354,7 @@ def assert_frame_equal(
13211354
exact=check_index_type,
13221355
check_names=check_names,
13231356
check_less_precise=check_less_precise,
1357+
check_low_values=check_low_values,
13241358
check_exact=check_exact,
13251359
check_categorical=check_categorical,
13261360
obj=f"{obj}.index",
@@ -1333,6 +1367,7 @@ def assert_frame_equal(
13331367
exact=check_column_type,
13341368
check_names=check_names,
13351369
check_less_precise=check_less_precise,
1370+
check_low_values=check_low_values,
13361371
check_exact=check_exact,
13371372
check_categorical=check_categorical,
13381373
obj=f"{obj}.columns",
@@ -1361,6 +1396,7 @@ def assert_frame_equal(
13611396
check_dtype=check_dtype,
13621397
check_index_type=check_index_type,
13631398
check_less_precise=check_less_precise,
1399+
check_low_values=check_low_values,
13641400
check_exact=check_exact,
13651401
check_names=check_names,
13661402
check_datetimelike_compat=check_datetimelike_compat,

0 commit comments

Comments
 (0)