फ्लोट्स के संग्रह के लिए पायथन यूनिट-टेस्ट में assertAlmostEqual


81

AssertAlmostEqual (एक्स, वाई) में विधि पायथन के इकाई परीक्षण ढांचे परीक्षण है कि क्या xऔरy लगभग संभालने बराबर हैं वे तैरता है।

इसके साथ समस्या assertAlmostEqual()यह है कि यह केवल तैरने पर काम करता है। मैं एक ऐसी विधि की तलाश कर रहा हूं, assertAlmostEqual()जिस पर फ़्लोट्स की सूची, फ़्लोट्स के सेट, फ़्लोट्स के फ़्लोट्स, फ़्लोट्स के फ़्लोट्स, फ़्लोट्स की फ़्लोट्स की लिस्ट, फ़्लोट्स की सूचियों के सेट्स इत्यादि का काम हो।

उदाहरण के लिए, चलो x = 0.1234567890, y = 0.1234567891xऔर yलगभग बराबर हैं क्योंकि वे पिछले एक को छोड़कर प्रत्येक और हर अंक पर सहमत हैं। इसलिए self.assertAlmostEqual(x, y)है Trueक्योंकि assertAlmostEqual()तैरने के लिए काम करता है।

मैं एक और सामान्य की तलाश कर रहा हूं assertAlmostEquals()जो निम्नलिखित कॉल का मूल्यांकन करता है True:

  • self.assertAlmostEqual_generic([x, x, x], [y, y, y])
  • self.assertAlmostEqual_generic({1: x, 2: x, 3: x}, {1: y, 2: y, 3: y})
  • self.assertAlmostEqual_generic([(x,x)], [(y,y)])

क्या ऐसी कोई विधि है या क्या मुझे इसे स्वयं लागू करना होगा?

स्पष्टीकरण:

  • assertAlmostEquals()नाम का एक वैकल्पिक पैरामीटर है placesऔर संख्याओं की तुलना दशमलव की संख्या के अंतर को गणना करके की जाती है places। डिफ़ॉल्ट रूप से places=7, इसलिए self.assertAlmostEqual(0.5, 0.4)यह गलत है जबकि self.assertAlmostEqual(0.12345678, 0.12345679)सत्य है। मेरे सट्टे assertAlmostEqual_generic()में समान कार्यक्षमता होनी चाहिए।

  • दो सूचियों को लगभग समान माना जाता है यदि उनके पास समान क्रम में लगभग समान संख्याएं हैं। औपचारिक रूप से, for i in range(n): self.assertAlmostEqual(list1[i], list2[i])

  • इसी तरह, दो सेटों को लगभग समान माना जाता है यदि उन्हें लगभग समान सूचियों में परिवर्तित किया जा सकता है (प्रत्येक सेट को एक आदेश देकर)।

  • इसी तरह, दो शब्दकोशों को लगभग समान माना जाता है यदि प्रत्येक शब्दकोश का कुंजी सेट अन्य शब्दकोश के प्रमुख सेट के लगभग बराबर है, और ऐसे प्रत्येक के लिए लगभग समान कुंजी जोड़ी के लिए एक समान लगभग समान मूल्य है।

  • सामान्य तौर पर: मैं दो संग्रहों को लगभग समान मानता हूं यदि वे कुछ इसी फ़्लोट को छोड़कर समान हैं जो एक दूसरे के लगभग बराबर हैं। दूसरे शब्दों में, मैं वास्तव में वस्तुओं की तुलना करना चाहूंगा, लेकिन रास्ते में तैरते समय की तुलना में कम (अनुकूलित) परिशुद्धता के साथ।


floatशब्दकोश में कुंजियों का उपयोग करने की बात क्या है ? चूंकि आप ठीक उसी फ़्लोट को प्राप्त करना सुनिश्चित नहीं कर सकते हैं, आप कभी भी लुकअप का उपयोग करके अपने आइटम नहीं ढूंढ पाएंगे। और यदि आप लुकअप का उपयोग नहीं कर रहे हैं, तो केवल डिक्शनरी के बजाय टुपल्स की सूची का उपयोग क्यों न करें? एक ही तर्क सेट पर लागू होता है।
अधिकतम

सिर्फ स्रोत के लिए एक लिंकassertAlmostEqual
djvg

जवाबों:


71

यदि आप NumPy (जो आपके Python (x, y)) के साथ आता है) का उपयोग करने में कोई आपत्ति नहीं है, तो आप np.testingमॉड्यूल को देखना चाहते हैं , जो दूसरों के बीच में परिभाषित करता है, aassert_almost_equal फंक्शन ।

हस्ताक्षर है np.testing.assert_almost_equal(actual, desired, decimal=7, err_msg='', verbose=True)

>>> x = 1.000001
>>> y = 1.000002
>>> np.testing.assert_almost_equal(x, y)
AssertionError: 
Arrays are not almost equal to 7 decimals
ACTUAL: 1.000001
DESIRED: 1.000002
>>> np.testing.assert_almost_equal(x, y, 5)
>>> np.testing.assert_almost_equal([x, x, x], [y, y, y], 5)
>>> np.testing.assert_almost_equal((x, x, x), (y, y, y), 5)

4
यह करीब है, लेकिन numpy.testingलगभग-समान विधियां केवल संख्याओं, सरणियों, टुपल्स और सूचियों पर काम करती हैं। वे शब्दकोशों, सेटों और संग्रहों के संग्रह पर काम नहीं करते हैं।
sankile

वास्तव में, लेकिन यह एक शुरुआत है। इसके अलावा, आपके पास स्रोत कोड तक पहुंच है जिसे आप शब्दकोशों, संग्रह और उसके बाद की तुलना की अनुमति देने के लिए संशोधित कर सकते हैं। np.testing.assert_equalउदाहरणों को तर्क के रूप में पहचानता है, उदाहरण के लिए (यदि तुलना आपके द्वारा ==काम नहीं की जाएगी तो भी)।
पियरे जीएम

@BrenBarn के अनुसार, निश्चित रूप से, आप सेट की तुलना करते समय परेशानियों में दौड़ेंगे।
पियरे जीएम

ध्यान दें कि वर्तमान दस्तावेज़ का assert_array_almost_equalउपयोग करने की अनुशंसा करता है assert_allclose, assert_array_almost_equal_nulpया assert_array_max_ulpइसके बजाय।
फुन्हेहे

10

अजगर 3.5 के रूप में आप उपयोग की तुलना कर सकते हैं

math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)

जैसा कि pep-0485 में वर्णित है । कार्यान्वयन के बराबर होना चाहिए

abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )

7
यह फ्लोट्स के साथ कंटेनरों की तुलना करने में कैसे मदद करता है, जिसके बारे में सवाल पूछ रहा था?
अधिकतम

9

यहां बताया गया है कि मैंने एक सामान्य is_almost_equal(first, second)कार्य कैसे लागू किया है :

सबसे पहले, उन वस्तुओं की नकल करें जिनकी आपको तुलना करने की आवश्यकता है ( firstऔरsecond ) , लेकिन एक सटीक प्रतिलिपि न बनाएं: ऑब्जेक्ट के अंदर आपके द्वारा सामना की जाने वाली किसी भी फ्लोट के महत्वहीन दशमलव अंकों को काटें।

अब जब आपके पास प्रतियां हैं firstऔर secondजिसके लिए तुच्छ दशमलव अंक चले गए हैं, तो बस ऑपरेटर की तुलना firstऔर secondउपयोग करें ==

मान लें कि हमारे पास एक cut_insignificant_digits_recursively(obj, places)फ़ंक्शन है जो डुप्लिकेट करता है objलेकिन placesमूल में प्रत्येक फ्लोट के केवल सबसे महत्वपूर्ण दशमलव अंक छोड़ता है obj। यहाँ एक कार्यशील कार्यान्वयन है is_almost_equals(first, second, places):

from insignificant_digit_cutter import cut_insignificant_digits_recursively

def is_almost_equal(first, second, places):
    '''returns True if first and second equal. 
    returns true if first and second aren't equal but have exactly the same
    structure and values except for a bunch of floats which are just almost
    equal (floats are almost equal if they're equal when we consider only the
    [places] most significant digits of each).'''
    if first == second: return True
    cut_first = cut_insignificant_digits_recursively(first, places)
    cut_second = cut_insignificant_digits_recursively(second, places)
    return cut_first == cut_second

और यहाँ एक कार्यशील कार्यान्वयन है cut_insignificant_digits_recursively(obj, places):

def cut_insignificant_digits(number, places):
    '''cut the least significant decimal digits of a number, 
    leave only [places] decimal digits'''
    if  type(number) != float: return number
    number_as_str = str(number)
    end_of_number = number_as_str.find('.')+places+1
    if end_of_number > len(number_as_str): return number
    return float(number_as_str[:end_of_number])

def cut_insignificant_digits_lazy(iterable, places):
    for obj in iterable:
        yield cut_insignificant_digits_recursively(obj, places)

def cut_insignificant_digits_recursively(obj, places):
    '''return a copy of obj except that every float loses its least significant 
    decimal digits remaining only [places] decimal digits'''
    t = type(obj)
    if t == float: return cut_insignificant_digits(obj, places)
    if t in (list, tuple, set):
        return t(cut_insignificant_digits_lazy(obj, places))
    if t == dict:
        return {cut_insignificant_digits_recursively(key, places):
                cut_insignificant_digits_recursively(val, places)
                for key,val in obj.items()}
    return obj

कोड और इसकी इकाई परीक्षण यहां उपलब्ध हैं: https://github.com/snakile/approximate_comparator । मैं किसी भी सुधार और बग को ठीक करने का स्वागत करता हूं।


तैरने की तुलना करने के बजाय, आप तार की तुलना कर रहे हैं? ठीक है ... लेकिन फिर, क्या एक सामान्य प्रारूप सेट करना आसान नहीं होगा? जैसे fmt="{{0:{0}f}}".format(decimals), और इस fmtफ़ॉर्म्स का उपयोग अपने फ़्लोट्स को "स्ट्रिफ़ाइज़" करने के लिए करें?
पियरे जीएम

1
यह अच्छा लग रहा है, लेकिन एक छोटा बिंदु: placesदशमलव स्थानों की संख्या देता है, महत्वपूर्ण आंकड़ों की संख्या नहीं। उदाहरण के लिए, तुलना करना 1024.123और 1023.9993 महत्वपूर्ण समान वापस लौटना चाहिए, लेकिन 3 दशमलव स्थानों पर वे नहीं हैं।
रोडनी रिचर्डसन

1
@pir, लाइसेंस वास्तव में अपरिभाषित है। इस अंक में स्नैले का उत्तर देखें जिसमें वह कहता है कि उसके पास लाइसेंस चुनने / जोड़ने का समय नहीं है, लेकिन अनुदान / संशोधन अनुमति का उपयोग करता है। इसे साझा करने के लिए धन्यवाद, BTW।
Jérôme

1
@RodneyRichardson, हाँ यह दशमलव स्थानों की तरह है, asAlmostEqual में : "ध्यान दें कि ये विधियाँ दी गई संख्याओं को दशमलव स्थानों की संख्या (जैसे कि गोल () फ़ंक्शन) और महत्वपूर्ण अंक नहीं के रूप में गोल करती हैं।"
Jérôme

2
@ Jérôme, टिप्पणी के लिए धन्यवाद। मैंने अभी एक MIT लाइसेंस जोड़ा है।
सांकेतिक

5

आप उपयोग कर कोई आपत्ति नहीं है numpyपैकेज तो numpy.testingहै assert_array_almost_equalविधि।

यह array_likeवस्तुओं के लिए काम करता है , इसलिए यह सरणियों, सूचियों और फ़्लोट्स के ट्यूपल्स के लिए ठीक है, लेकिन क्या यह सेट और शब्दकोशों के लिए काम नहीं करता है।

प्रलेखन यहाँ है


4

ऐसी कोई विधि नहीं है, आपको इसे स्वयं करना होगा।

सूचियों के लिए और परिभाषा स्पष्ट है, लेकिन ध्यान दें कि आपके द्वारा उल्लेखित अन्य मामले स्पष्ट नहीं हैं, इसलिए यह कोई आश्चर्य नहीं है कि ऐसा फ़ंक्शन प्रदान नहीं किया गया है। उदाहरण के लिए, {1.00001: 1.00002}लगभग बराबर है {1.00002: 1.00001}? इस तरह के मामलों को संभालने के लिए यह विकल्प चुनने की आवश्यकता है कि निकटता चाबियों या मूल्यों पर निर्भर करती है या दोनों पर। सेट के लिए आपको एक सार्थक परिभाषा मिलने की संभावना नहीं है, क्योंकि सेट अनियंत्रित हैं, इसलिए "संगत" तत्वों की कोई धारणा नहीं है।


BrenBarn: मैंने प्रश्न में स्पष्टीकरण जोड़ा है। आपके प्रश्न का उत्तर यह है कि {1.00001: 1.00002}लगभग बराबर है {1.00002: 1.00001}अगर और केवल 1.00001 लगभग 1.00002 के बराबर है। डिफ़ॉल्ट रूप से वे लगभग बराबर नहीं होते हैं (क्योंकि डिफ़ॉल्ट परिशुद्धता 7 दशमलव स्थान है) लेकिन उनके लिए एक छोटे से पर्याप्त मूल्य के लिए placesलगभग बराबर है।
सांचिले

1
@BrenBarn: IMO, floatस्पष्ट कारणों में टाइप की कीज़ के उपयोग को हतोत्साहित (और शायद अस्वीकृत भी) किया जाना चाहिए। तानाशाह की अनुमानित समानता केवल मूल्यों पर आधारित होनी चाहिए; परीक्षण ढांचे floatको कुंजी के गलत उपयोग के बारे में चिंता करने की आवश्यकता नहीं है । सेट के लिए, उन्हें तुलना करने से पहले सॉर्ट किया जा सकता है, और सॉर्ट की गई सूची की तुलना की जा सकती है।
अधिकतम

2

आपको इसे स्वयं लागू करना पड़ सकता है, जबकि इसकी सही यह है कि सूची और सेट को एक ही तरह से पुनरावृत्त किया जा सकता है, शब्दकोश एक अलग कहानी है, आप उनकी कुंजियों को महत्व देते हैं, और तीसरा उदाहरण मुझे थोड़ा अस्पष्ट लगता है, क्या आपका मतलब है सेट के भीतर प्रत्येक मूल्य, या प्रत्येक सेट से प्रत्येक मूल्य की तुलना करें।

एक सरल कोड स्निपेट का उपयोग करता है।

def almost_equal(value_1, value_2, accuracy = 10**-8):
    return abs(value_1 - value_2) < accuracy

x = [1,2,3,4]
y = [1,2,4,5]
assert all(almost_equal(*values) for values in zip(x, y))

धन्यवाद, समाधान सूचियों और टुपल्स के लिए सही है, लेकिन अन्य प्रकार के संग्रह (या नेस्टेड संग्रह) के लिए नहीं। प्रश्न में मैंने जो स्पष्टीकरण दिए हैं, उन्हें देखें। मुझे उम्मीद है कि मेरा इरादा अब स्पष्ट है। दो सेट लगभग समान हैं यदि उन्हें एक ऐसी दुनिया में समान माना जाता है जहां संख्याओं को बहुत सटीक रूप से मापा नहीं जाता है।
सांकिल

1

इनमें से कोई भी उत्तर मेरे लिए काम नहीं करता है। निम्नलिखित कोड को अजगर संग्रह, कक्षाएं, डेटाक्लस, और नेमटुपल्स के लिए काम करना चाहिए। मैं शायद कुछ भूल गया हूं, लेकिन अभी तक यह मेरे लिए काम करता है।

import unittest
from collections import namedtuple, OrderedDict
from dataclasses import dataclass
from typing import Any


def are_almost_equal(o1: Any, o2: Any, max_abs_ratio_diff: float, max_abs_diff: float) -> bool:
    """
    Compares two objects by recursively walking them trough. Equality is as usual except for floats.
    Floats are compared according to the two measures defined below.

    :param o1: The first object.
    :param o2: The second object.
    :param max_abs_ratio_diff: The maximum allowed absolute value of the difference.
    `abs(1 - (o1 / o2)` and vice-versa if o2 == 0.0. Ignored if < 0.
    :param max_abs_diff: The maximum allowed absolute difference `abs(o1 - o2)`. Ignored if < 0.
    :return: Whether the two objects are almost equal.
    """
    if type(o1) != type(o2):
        return False

    composite_type_passed = False

    if hasattr(o1, '__slots__'):
        if len(o1.__slots__) != len(o2.__slots__):
            return False
        if any(not are_almost_equal(getattr(o1, s1), getattr(o2, s2),
                                    max_abs_ratio_diff, max_abs_diff)
            for s1, s2 in zip(sorted(o1.__slots__), sorted(o2.__slots__))):
            return False
        else:
            composite_type_passed = True

    if hasattr(o1, '__dict__'):
        if len(o1.__dict__) != len(o2.__dict__):
            return False
        if any(not are_almost_equal(k1, k2, max_abs_ratio_diff, max_abs_diff)
            or not are_almost_equal(v1, v2, max_abs_ratio_diff, max_abs_diff)
            for ((k1, v1), (k2, v2))
            in zip(sorted(o1.__dict__.items()), sorted(o2.__dict__.items()))
            if not k1.startswith('__')):  # avoid infinite loops
            return False
        else:
            composite_type_passed = True

    if isinstance(o1, dict):
        if len(o1) != len(o2):
            return False
        if any(not are_almost_equal(k1, k2, max_abs_ratio_diff, max_abs_diff)
            or not are_almost_equal(v1, v2, max_abs_ratio_diff, max_abs_diff)
            for ((k1, v1), (k2, v2)) in zip(sorted(o1.items()), sorted(o2.items()))):
            return False

    elif any(issubclass(o1.__class__, c) for c in (list, tuple, set)):
        if len(o1) != len(o2):
            return False
        if any(not are_almost_equal(v1, v2, max_abs_ratio_diff, max_abs_diff)
            for v1, v2 in zip(o1, o2)):
            return False

    elif isinstance(o1, float):
        if o1 == o2:
            return True
        else:
            if max_abs_ratio_diff > 0:  # if max_abs_ratio_diff < 0, max_abs_ratio_diff is ignored
                if o2 != 0:
                    if abs(1.0 - (o1 / o2)) > max_abs_ratio_diff:
                        return False
                else:  # if both == 0, we already returned True
                    if abs(1.0 - (o2 / o1)) > max_abs_ratio_diff:
                        return False
            if 0 < max_abs_diff < abs(o1 - o2):  # if max_abs_diff < 0, max_abs_diff is ignored
                return False
            return True

    else:
        if not composite_type_passed:
            return o1 == o2

    return True


class EqualityTest(unittest.TestCase):

    def test_floats(self) -> None:
        o1 = ('hi', 3, 3.4)
        o2 = ('hi', 3, 3.400001)
        self.assertTrue(are_almost_equal(o1, o2, 0.0001, 0.0001))
        self.assertFalse(are_almost_equal(o1, o2, 0.00000001, 0.00000001))

    def test_ratio_only(self):
        o1 = ['hey', 10000, 123.12]
        o2 = ['hey', 10000, 123.80]
        self.assertTrue(are_almost_equal(o1, o2, 0.01, -1))
        self.assertFalse(are_almost_equal(o1, o2, 0.001, -1))

    def test_diff_only(self):
        o1 = ['hey', 10000, 1234567890.12]
        o2 = ['hey', 10000, 1234567890.80]
        self.assertTrue(are_almost_equal(o1, o2, -1, 1))
        self.assertFalse(are_almost_equal(o1, o2, -1, 0.1))

    def test_both_ignored(self):
        o1 = ['hey', 10000, 1234567890.12]
        o2 = ['hey', 10000, 0.80]
        o3 = ['hi', 10000, 0.80]
        self.assertTrue(are_almost_equal(o1, o2, -1, -1))
        self.assertFalse(are_almost_equal(o1, o3, -1, -1))

    def test_different_lengths(self):
        o1 = ['hey', 1234567890.12, 10000]
        o2 = ['hey', 1234567890.80]
        self.assertFalse(are_almost_equal(o1, o2, 1, 1))

    def test_classes(self):
        class A:
            d = 12.3

            def __init__(self, a, b, c):
                self.a = a
                self.b = b
                self.c = c

        o1 = A(2.34, 'str', {1: 'hey', 345.23: [123, 'hi', 890.12]})
        o2 = A(2.34, 'str', {1: 'hey', 345.231: [123, 'hi', 890.121]})
        self.assertTrue(are_almost_equal(o1, o2, 0.1, 0.1))
        self.assertFalse(are_almost_equal(o1, o2, 0.0001, 0.0001))

        o2.hello = 'hello'
        self.assertFalse(are_almost_equal(o1, o2, -1, -1))

    def test_namedtuples(self):
        B = namedtuple('B', ['x', 'y'])
        o1 = B(3.3, 4.4)
        o2 = B(3.4, 4.5)
        self.assertTrue(are_almost_equal(o1, o2, 0.2, 0.2))
        self.assertFalse(are_almost_equal(o1, o2, 0.001, 0.001))

    def test_classes_with_slots(self):
        class C(object):
            __slots__ = ['a', 'b']

            def __init__(self, a, b):
                self.a = a
                self.b = b

        o1 = C(3.3, 4.4)
        o2 = C(3.4, 4.5)
        self.assertTrue(are_almost_equal(o1, o2, 0.3, 0.3))
        self.assertFalse(are_almost_equal(o1, o2, -1, 0.01))

    def test_dataclasses(self):
        @dataclass
        class D:
            s: str
            i: int
            f: float

        @dataclass
        class E:
            f2: float
            f4: str
            d: D

        o1 = E(12.3, 'hi', D('hello', 34, 20.01))
        o2 = E(12.1, 'hi', D('hello', 34, 20.0))
        self.assertTrue(are_almost_equal(o1, o2, -1, 0.4))
        self.assertFalse(are_almost_equal(o1, o2, -1, 0.001))

        o3 = E(12.1, 'hi', D('ciao', 34, 20.0))
        self.assertFalse(are_almost_equal(o2, o3, -1, -1))

    def test_ordereddict(self):
        o1 = OrderedDict({1: 'hey', 345.23: [123, 'hi', 890.12]})
        o2 = OrderedDict({1: 'hey', 345.23: [123, 'hi', 890.0]})
        self.assertTrue(are_almost_equal(o1, o2, 0.01, -1))
        self.assertFalse(are_almost_equal(o1, o2, 0.0001, -1))

0

मैं तब भी इसका उपयोग करता हूं self.assertEqual()जब प्रशंसक हिट करता है तो यह सबसे अधिक जानकारीपूर्ण रहता है। आप इसे गोल करके कर सकते हैं, जैसे।

self.assertEqual(round_tuple((13.949999999999999, 1.121212), 2), (13.95, 1.12))

कहाँ round_tupleहै

def round_tuple(t: tuple, ndigits: int) -> tuple:
    return tuple(round(e, ndigits=ndigits) for e in t)

def round_list(l: list, ndigits: int) -> list:
    return [round(e, ndigits=ndigits) for e in l]

अजगर डॉक्स ( https://stackoverflow.com/a/41407651/1031191 देखें ) के अनुसार, आप 13.94999999 जैसे गोलमोल मुद्दों से दूर हो सकते हैं, क्योंकि 13.94999999 == 13.95है True


-1

एक वैकल्पिक तरीका यह है कि अपने डेटा को एक स्थिर रूप में परिवर्तित करें, जैसे प्रत्येक फ्लोट को निश्चित परिशुद्धता के साथ एक स्ट्रिंग में बदलना।

def comparable(data):
    """Converts `data` to a comparable structure by converting any floats to a string with fixed precision."""
    if isinstance(data, (int, str)):
        return data
    if isinstance(data, float):
        return '{:.4f}'.format(data)
    if isinstance(data, list):
        return [comparable(el) for el in data]
    if isinstance(data, tuple):
        return tuple([comparable(el) for el in data])
    if isinstance(data, dict):
        return {k: comparable(v) for k, v in data.items()}

तब आप कर सकते हो:

self.assertEquals(comparable(value1), comparable(value2))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.