pypet/utils/comparisons.py
def nested_equal(a, b):
    """Compares two objects recursively by their elements.

    Also handles numpy arrays, pandas data and sparse matrices.

    First checks if the data falls into the above categories.
    If not, it is checked if a or b are some type of sequence or mapping and
    the contained elements are compared.
    If this is not the case, it is checked if a or b do provide a custom `__eq__` that
    evaluates to a single boolean value.
    If this is not the case, the attributes of a and b are compared.
    If this does not help either, normal `==` is used.

    Assumes hashable items are not mutable in a way that affects equality.
    Based on the suggestion from HERE_, thanks again Lauritz V. Thaulow :-)

    .. _HERE: http://stackoverflow.com/questions/18376935/best-practice-for-equality-in-python

    """
    if a is b:
        return True

    if a is None or b is None:
        return False

    a_sparse = spsp.isspmatrix(a)
    b_sparse = spsp.isspmatrix(b)
    if a_sparse != b_sparse:
        return False
    if a_sparse:
        if a.nnz == 0:
            return b.nnz == 0
        else:
            return not np.any((a != b).data)

    a_series = isinstance(a, pd.Series)
    b_series = isinstance(b, pd.Series)
    if a_series != b_series:
        return False
    if a_series:
        try:
            eq = (a == b).all()
            return eq
        except (TypeError, ValueError):
            # If Sequence itself contains numpy arrays we get here
            if not len(a) == len(b):
                return False
            for idx, itema in enumerate(a):
                itemb = b[idx]
                if not nested_equal(itema, itemb):
                    return False
            return True

    a_frame = isinstance(a, pd.DataFrame)
    b_frame = isinstance(b, pd.DataFrame)
    if a_frame != b_frame:
        return False
    if a_frame:
        try:
            if a.empty and b.empty:
                return True
            new_frame = a == b
            new_frame = new_frame | (pd.isnull(a) & pd.isnull(b))
            if isinstance(new_frame, pd.DataFrame):
                return np.all(new_frame.as_matrix())
        except (ValueError, TypeError):
            # The Value Error can happen if the data frame is of dtype=object and contains
            # numpy arrays. Numpy array comparisons do not evaluate to a single truth value
            for name in a:
                cola = a[name]
                if not name in b:
                    return False
                colb = b[name]
                if not len(cola) == len(colb):
                    return False
                for idx, itema in enumerate(cola):
                    itemb = colb[idx]
                    if not nested_equal(itema, itemb):
                        return False
            return True

    a_array = isinstance(a, np.ndarray)
    b_array = isinstance(b, np.ndarray)
    if a_array != b_array:
        return False
    if a_array:
        if a.shape != b.shape:
            return False
        return np.all(a == b)

    a_list = isinstance(a, (Sequence, list, tuple))
    b_list = isinstance(b, (Sequence, list, tuple))
    if a_list != b_list:
        return False
    if a_list:
        return all(nested_equal(x, y) for x, y in zip(a, b))

    a_mapping = isinstance(a, (Mapping, dict))
    b_mapping = isinstance(b, (Mapping, dict))
    if a_mapping != b_mapping:
        return False
    if a_mapping:
        keys_a = a.keys()
        if set(keys_a) != set(b.keys()):
            return False
        return all(nested_equal(a[k], b[k]) for k in keys_a)

    # Equality for general objects
    # for types that support __eq__ or __cmp__
    equality = NotImplemented
    try:
        equality = a.__eq__(b)
    except (AttributeError, NotImplementedError, TypeError, ValueError):
        pass
    if equality is NotImplemented:
        try:
            equality = b.__eq__(a)
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass
    if equality is NotImplemented:
        try:
            cmp = a.__cmp__(b)
            if cmp is not NotImplemented:
                equality = cmp == 0
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass
    if equality is NotImplemented:
        try:
            cmp = b.__cmp__(a)
            if cmp is not NotImplemented:
                equality = cmp == 0
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass
    if equality is not NotImplemented:
        try:
            return bool(equality)
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass

    # Compare objects based on their attributes
    attributes_a = get_all_attributes(a)
    attributes_b = get_all_attributes(b)
    if len(attributes_a) != len(attributes_b):
        return False
    if len(attributes_a) > 0:
        keys_a = list(attributes_a.keys())
        if set(keys_a) != set(attributes_b.keys()):
            return False

        return all(nested_equal(attributes_a[k], attributes_b[k]) for k in keys_a)

    # Ok they are really not equal
    return False
def nested_equal(a, b):
    """Compares two objects recursively by their elements.

    Also handles numpy arrays, pandas data and sparse matrices.

    First checks if the data falls into the above categories.
    If not, it is checked if a or b are some type of sequence or mapping and
    the contained elements are compared.
    If this is not the case, it is checked if a or b do provide a custom `__eq__` that
    evaluates to a single boolean value.
    If this is not the case, the attributes of a and b are compared.
    If this does not help either, normal `==` is used.

    Assumes hashable items are not mutable in a way that affects equality.
    Based on the suggestion from HERE_, thanks again Lauritz V. Thaulow :-)

    .. _HERE: http://stackoverflow.com/questions/18376935/best-practice-for-equality-in-python

    """
    if a is b:
        return True

    if a is None or b is None:
        return False

    a_sparse = spsp.isspmatrix(a)
    b_sparse = spsp.isspmatrix(b)
    if a_sparse != b_sparse:
        return False
    if a_sparse:
        if a.nnz == 0:
            return b.nnz == 0
        else:
            return not np.any((a != b).data)

    a_series = isinstance(a, pd.Series)
    b_series = isinstance(b, pd.Series)
    if a_series != b_series:
        return False
    if a_series:
        try:
            eq = (a == b).all()
            return eq
        except (TypeError, ValueError):
            # If Sequence itself contains numpy arrays we get here
            if not len(a) == len(b):
                return False
            for idx, itema in enumerate(a):
                itemb = b[idx]
                if not nested_equal(itema, itemb):
                    return False
            return True

    a_frame = isinstance(a, pd.DataFrame)
    b_frame = isinstance(b, pd.DataFrame)
    if a_frame != b_frame:
        return False
    if a_frame:
        try:
            if a.empty and b.empty:
                return True
            new_frame = a == b
            new_frame = new_frame | (pd.isnull(a) & pd.isnull(b))
            if isinstance(new_frame, pd.DataFrame):
                return np.all(new_frame.as_matrix())
        except (ValueError, TypeError):
            # The Value Error can happen if the data frame is of dtype=object and contains
            # numpy arrays. Numpy array comparisons do not evaluate to a single truth value
            for name in a:
                cola = a[name]
                if not name in b:
                    return False
                colb = b[name]
                if not len(cola) == len(colb):
                    return False
                for idx, itema in enumerate(cola):
                    itemb = colb[idx]
                    if not nested_equal(itema, itemb):
                        return False
            return True

    a_array = isinstance(a, np.ndarray)
    b_array = isinstance(b, np.ndarray)
    if a_array != b_array:
        return False
    if a_array:
        if a.shape != b.shape:
            return False
        return np.all(a == b)

    a_list = isinstance(a, (Sequence, list, tuple))
    b_list = isinstance(b, (Sequence, list, tuple))
    if a_list != b_list:
        return False
    if a_list:
        return all(nested_equal(x, y) for x, y in zip(a, b))

    a_mapping = isinstance(a, (Mapping, dict))
    b_mapping = isinstance(b, (Mapping, dict))
    if a_mapping != b_mapping:
        return False
    if a_mapping:
        keys_a = a.keys()
        if set(keys_a) != set(b.keys()):
            return False
        return all(nested_equal(a[k], b[k]) for k in keys_a)

    # Equality for general objects
    # for types that support __eq__ or __cmp__
    equality = NotImplemented
    try:
        equality = a.__eq__(b)
    except (AttributeError, NotImplementedError, TypeError, ValueError):
        pass
    if equality is NotImplemented:
        try:
            equality = b.__eq__(a)
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass
    if equality is NotImplemented:
        try:
            cmp = a.__cmp__(b)
            if cmp is not NotImplemented:
                equality = cmp == 0
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass
    if equality is NotImplemented:
        try:
            cmp = b.__cmp__(a)
            if cmp is not NotImplemented:
                equality = cmp == 0
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass
    if equality is not NotImplemented:
        try:
            return bool(equality)
        except (AttributeError, NotImplementedError, TypeError, ValueError):
            pass

    # Compare objects based on their attributes
    attributes_a = get_all_attributes(a)
    attributes_b = get_all_attributes(b)
    if len(attributes_a) != len(attributes_b):
        return False
    if len(attributes_a) > 0:
        keys_a = list(attributes_a.keys())
        if set(keys_a) != set(attributes_b.keys()):
            return False

        return all(nested_equal(attributes_a[k], attributes_b[k]) for k in keys_a)

    # Ok they are really not equal
    return False
Compares two objects recursively by their elements.

    Also handles numpy arrays, pandas data and sparse matrices.

    First checks if the data falls into the above categories.
    If not, it is checked if a or b are some type of sequence or mapping and
    the contained elements are compared.
    If this is not the case, it is checked if a or b do provide a custom `__eq__` that
    evaluates to a single boolean value.
    If this is not the case, the attributes of a and b are compared.
    If this does not help either, normal `==` is used.

    Assumes hashable items are not mutable in a way that affects equality.
    Based on the suggestion from HERE_, thanks again Lauritz V. Thaulow :-)

    .. _HERE: http://stackoverflow.com/questions/18376935/best-practice-for-equality-in-python