Home > Software design >  How to introduce relative/absolute tolerance in assertDictEqual?
How to introduce relative/absolute tolerance in assertDictEqual?

Time:02-23

Is there a built-in method or, alternatively, a quick and efficient way for assertDictEqual() to have the same functionality as that of rtol and/or atol for asset_frame_equal() in pandas.testing?

I want to be able to compare two dictionaries for equality of values corresponding to the same key, where they pass as equal when the values are close to each other within a given tolerance limit. Similar to what atol/rtol does for frame_equal.

MRE:

import numpy as np
def assert_dicts_almost_equal(d1, d2, rtol=0.01, atol=0.1):
  assert len(d1) == len(d2), 'Unequal number of elements.'
  for key in d1:
    try:
      np.testing.assert_allclose(d1[key], d2[key], rtol=rtol, atol=atol)
    except AssertionError as msg:
      print('Assertion Error for {key}'.format(key=key))
      print(msg)

Data:

d = {'A': 0.49, 'B': 0.51}
d0 = {'A': 0.4999999999999991, 'B': 0.5000000000000007, 'C': np.nan}
d1 = {'A': 0.3105572709904508, 'B': 0.5030302993151613, 'C': np.nan}
d2 = {'A': 0.4813463081397519, 'B': 0.5104397084554964, 'C': np.nan}
d3 = {'A': 0.4740668937066489, 'B': 0.5144020381674881, 'C': np.nan}

Tests:

assert_dicts_almost_equal(d0, d)
assert_dicts_almost_equal(d0, d1)
assert_dicts_almost_equal(d0, d2)
assert_dicts_almost_equal(d0, d3)

Only the first two will raise Assertion Error, the rest will pass.

CodePudding user response:

Numpy's assert_allclose is similar. Here is toy example to demonstrate.

import numpy as np

dict_a = {'A': np.array([1,2,3]), 'B': np.array([4,5,6]), 'C': np.array([7,8,9])}

dict_b = {'A': np.array([1,2,3]), 'B': np.array([4,5,20]), 'C': np.array([7,8,9])}

for k, v in dict_a.items():
    try:
        np.testing.assert_allclose(dict_a[k], dict_b[k], atol=2)
        print('{k} is close'.format(k=k))
        
    except AssertionError as msg:
        print('Assertion Error for {k}'.format(k=k))
        print(msg)

Output:

A is close
Assertion Error for B

Not equal to tolerance rtol=1e-07, atol=2

Mismatched elements: 1 / 3 (33.3%)
Max absolute difference: 14
Max relative difference: 0.7
 x: array([4, 5, 6])
 y: array([ 4,  5, 20])
C is close

In this example the atol parameter is set higher to 25, for which the B values are within the absolute tolerance.

import numpy as np

dict_a = {'A': np.array([1,2,3]), 'B': np.array([4,5,6]), 'C': np.array([7,8,9])}

dict_b = {'A': np.array([1,2,3]), 'B': np.array([4,5,20]), 'C': np.array([7,8,9])}

for k, v in dict_a.items():
    try:
        np.testing.assert_allclose(dict_a[k], dict_b[k], atol=25)
        print('{k} is close'.format(k=k))
        
    except AssertionError as msg:
        print('Assertion Error for {k}'.format(k=k))
        print(msg)

Output:

A is close
B is close
C is close

Edit for comment:

I changed this up a bit based upon your comment. Now the original dicts are a mix of lists and single values and the array conversion happens in the loop. Numpy is more flexible since it can handle lists or single values compared to math.isclose() which can't handle lists.

import numpy as np
import math

dict_a = {'A': [1,2,3], 'B': [1,2,3], 'C': [1,2,3]}

dict_b = {'A': [1,2,3], 'B': 66, 'C': [1,2,3]}

for k, v in dict_a.items():
    try:
        np.testing.assert_allclose(np.array(dict_a[k]), np.array(dict_b[k]), atol=25)
        #math.isclose(dict_a[k], dict_b[k])
        print('{k} is close'.format(k=k))
        
    except AssertionError as msg:
        print('Assertion Error for {k}'.format(k=k))
        print(msg)

Output:

A is close
Assertion Error for B

Not equal to tolerance rtol=1e-07, atol=25

Mismatched elements: 3 / 3 (100%)
Max absolute difference: 65
Max relative difference: 0.98484848
 x: array([1, 2, 3])
 y: array(66)
C is close
  • Related