2015-09-24 2 views
1

Для ясности я извлечу отрывок из моего кода и использую общие имена. У меня есть класс Foo(), который хранит DataFrame для атрибута.Как перегружать `__eq__` для сравнения pandas DataFrames и Series?

import pandas as pd 
import pandas.util.testing as pdt 

class Foo(): 

    def __init__(self, bar): 
     self.bar = bar          # dict of dicts 
     self.df = pd.DataFrame(bar)      # pandas object  

    def __eq__(self, other): 
     if isinstance(other, self.__class__): 
      return self.__dict__ == other.__dict__ 
     return NotImplemented 

    def __ne__(self, other): 
     result = self.__eq__(other) 
     if result is NotImplemented: 
      return result 
     return not result 

Однако, когда я пытаюсь сравнить два экземпляра из Foo, я получаю excepetion, связанные с неоднозначностью сравнения двух DataFrames (сравнение должно работать нормально без ключа «» в ф.р. Foo.__dict__).

d1 = {'A' : pd.Series([1, 2], index=['a', 'b']), 
     'B' : pd.Series([1, 2], index=['a', 'b'])} 
d2 = d1.copy() 

foo1 = Foo(d1) 
foo2 = Foo(d2) 

foo1.bar             # dict 
foo1.df             # pandas DataFrame 

foo1 == foo2            # ValueError 

[Out] ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all(). 

К счастью, у pandas есть служебные функции для утверждения, являются ли два DataFrames или Series истинными. Я хотел бы использовать операцию сравнения этой функции, если это возможно.

pdt.assert_frame_equal(pd.DataFrame(d1), pd.DataFrame(d2)) # no raises 

Есть несколько вариантов, чтобы решить сравнение двух Foo экземпляров:

  1. сравнить копию __dict__, где new_dict не хватает ФР ключа
  2. удалить ключ ф.р. из __dict__ (не идеально)
  3. не сравнить __dict__, но только его части, содержащиеся в кортеже
  4. перегружать __eq__ для облегчения панды DataFrame сравнения

Последний вариант кажется самым надежным в долгосрочной перспективе, но я не уверен, что лучший подход. В конце концов, Я хотел был бы refactor __eq__, чтобы сравнить все элементы от Foo.__dict__, включая DataFrames (и Series). Любые идеи о том, как это сделать?

+0

Почему вы просто не делаете подклассы для dataframe и series и не создаете собственную функцию '__eq__'? Вы можете супер оригинал для других случаев. – postelrich

ответ

2

Решение этих нитей

Comparing two pandas dataframes for differences

Pandas DataFrames with NaNs equality comparison

def df_equal(self): 
    try: 
     assert_frame_equal(csvdata, csvdata_old) 
     return True 
    except: 
     return False 

Для словаря dataframes:

def df_equal(df1, df2): 
    try: 
     assert_frame_equal(df1, df2) 
     return True 
    except: 
     return False 

def __eq__(self, other): 
    if self.df.keys() != other.keys(): 
     return False 
    for k in self.df.keys(): 
     if not df_equal(self.df[k], other[k]): 
      return False 
    return True 
+0

Я ценю ссылки. Похоже на то, что мне нужно '__eq__' обрабатывать все атрибуты, содержащиеся в' foo .__ dict__', т. Е. 'Df' и' bar'. Похоже, что он будет обрабатывать только DataFrames. – pylang

+1

gotcha. вам, возможно, придется просто реализовать индивидуальные сравнения самостоятельно. Я обновил ответ с идеей, но вам нужно будет точно определить, что означает равенство с точки зрения ваших объектов. – ate50eggs

+0

Не забудьте поднять полезные ответы! Как правило, для ясности на клавиши ссылаются как .columns. Я не уверен, что вышеупомянутое будет работать, как обычно. Df [k] - это серия ('assert_frame_equal' работает только с DataFrames). –

0

Следующий код кажется полностью удовлетворить мой первоначальный вопрос. Он обрабатывает как pandas DataFrames, так и Series. Упрощение приветствуется.

Фокус в том, что __eq__ был реализован для сравнения __dict__ и объектов панды отдельно. Правдоподобность каждого окончательно сравнивается и результат возвращается. Что-то интересное и эксплуатируемое здесь, and возвращает второе значение, если первое значение равно True.

Идея использования обработки ошибок и внешней функции сравнения была основана на ответе, представленном @ ate50eggs. Большое спасибо.

import pandas as pd 
import pandas.util.testing as pdt 

def ndframe_equal(ndf1, ndf2): 
    try: 
     if isinstance(ndf1, pd.DataFrame) and isinstance(ndf2, pd.DataFrame): 
      pdt.assert_frame_equal(ndf1, ndf2) 
      #print('DataFrame check:', type(ndf1), type(ndf2)) 
     elif isinstance(ndf1, pd.Series) and isinstance(ndf2, pd.Series): 
      pdt.assert_series_equal(ndf1, ndf2) 
      #print('Series check:', type(ndf1), type(ndf2)) 
     return True 
    except (ValueError, AssertionError, AttributeError):    
     return False 


class Foo(object): 

    def __init__(self, bar): 
     self.bar = bar          
     try: 
      self.ndf = pd.DataFrame(bar) 
     except(ValueError): 
      self.ndf = pd.Series(bar) 

    def __eq__(self, other): 
     if isinstance(other, self.__class__): 
      # Auto check attrs if assigned to DataFrames/Series, then add to list 
      blacklisted = [attr for attr in self.__dict__ if 
           isinstance(getattr(self, attr), pd.DataFrame) 
           or isinstance(getattr(self, attr), pd.Series)] 

      # Check DataFrames and Series 
      for attr in blacklisted: 
       ndf_eq = ndframe_equal(getattr(self, attr), 
              getattr(other, attr)) 

      # Ignore pandas objects; check rest of __dict__ and build new dicts 
      self._dict = { 
       key: value 
       for key, value in self.__dict__.items() 
       if key not in blacklisted} 
      other._dict = { 
       key: value 
       for key, value in other.__dict__.items() 
       if key not in blacklisted} 
      return ndf_eq and self._dict == other._dict # order is important 
     return NotImplemented    

    def __ne__(self, other): 
     result = self.__eq__(other) 
     if result is NotImplemented: 
      return result 
     return not result 

Тестирование последнего кода на DataFrames.

# Data for DataFrames 
d1 = {'A' : pd.Series([1, 2], index=['a', 'b']), 
     'B' : pd.Series([1, 2], index=['a', 'b'])} 
d2 = d1.copy() 
d3 = {'A' : pd.Series([1, 2], index=['abc', 'b']), 
     'B' : pd.Series([9, 0], index=['abc', 'b'])} 

# Test DataFrames 
foo1 = Foo(d1) 
foo2 = Foo(d2) 

foo1.bar           # dict of Series 
foo1.ndf           # pandas DataFrame 

foo1 == foo2          # triggers _dict 
#foo1.__dict__['_dict'] 
#foo1._dict 

foo1 == foo2          # True     
foo1 != foo2          # False 
not foo1 == foo2         # False    
not foo1 != foo2         # True 
foo2 = Foo(d3)              

foo1 == foo2          # False 
foo1 != foo2          # True 
not foo1 == foo2         # True 
not foo1 != foo2         # False 

Наконец тестирование на другом общем объекте панд, в Series.

# Data for Series 
s1 = {'a' : 0., 'b' : 1., 'c' : 2.} 
s2 = s1.copy() 
s3 = {'a' : 0., 'b' : 4, 'c' : 5} 

# Test Series 
foo3 = Foo(s1) 
foo4 = Foo(s2) 

foo3.bar           # dict 
foo4.ndf           # pandas Series 

foo3 == foo4          # True 
foo3 != foo4          # False 
not foo3 == foo4         # False 
not foo3 != foo4         # True 

foo4 = Foo(s3) 
foo3 == foo4          # False  
foo3 != foo4          # True 
not foo3 == foo4         # True  
not foo3 != foo4         # False