Pandas Неизменяемый DataFrame

Меня интересует неизменяемый DataFrame для использования в программе в качестве ссылочной таблицы с включенными свойствами read_only после того, как она была первоначально построена (что в моем случае находится во время метода класса def __init__()).

Я вижу, что объекты индекса заморожены.

Есть ли способ сделать целостным DataFrame неизменным?

Ответ 1

Попробуйте ввести что-то вроде этого

class Bla(object):
    def __init__(self):
        self._df = pd.DataFrame(index=[1,2,3])

    @property
    def df(self):
        return self._df.copy()

это позволит вам вернуть df обратно, используя b.df, но вы не сможете его назначить. Короче говоря, у вас есть класс df в классе, который ведет себя в "неизменяемом DataFrame", просто в том, что он блокирует изменения оригинала. однако возвращенный объект все же является изменяемым фреймом данных, поэтому он не будет вести себя как неизменяемый, другими способами. То есть вы не сможете использовать его в качестве ключа для словаря и т.д.

Ответ 2

Если вы действительно хотите, чтобы DataFrame вел себя как неизменяемый, вместо использования решения copy by @Joop (который я бы рекомендовал), вы могли бы построить следующую структуру.

Обратите внимание, что это всего лишь отправная точка.

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

Некоторые предупреждения:

  • В зависимости от того, как построено строковое представление прокси-объекта, два разных проксированных объекта могут получить один и тот же хэш, howerver реализация совместима с DataFrame среди другие объекты.

  • Изменения в исходном объекте будут влиять на прокси-объект.

  • Равномерность приведет к некоторым неприятным нерешенным рекурсиям, если другой объект возвращает вопрос равенства (вот почему list имеет специальный случай).

  • Помощник DataFrame proxy maker - это просто начало, проблема в том, что любой метод, который изменяет состояние исходного объекта, не может быть разрешен или должен быть вручную перезаписан помощником или полностью замаскирован extraFilter -параметр при создании экземпляра _ReadOnly. См. DataFrameProxy.sort.

  • Прокси не будут отображаться как полученные из проксированного типа.

Общий прокси файл для чтения

Это может быть использовано для любого объекта.

import md5                                                                                              
import warnings                                                                                         

class _ReadOnly(object):                                                                                

    def __init__(self, obj, extraFilter=tuple()):                                                       

        self.__dict__['_obj'] = obj                                                                     
        self.__dict__['_d'] = None                                                                      
        self.__dict__['_extraFilter'] = extraFilter                                                     
        self.__dict__['_hash'] = int(md5.md5(str(obj)).hexdigest(), 16)                                 

    @staticmethod                                                                                       
    def _cloak(obj):                                                                                    
        try:                                                                                            
            hash(obj)                                                                                   
            return obj                                                                                  
        except TypeError:                                                                               
            return _ReadOnly(obj)                                                                       

    def __getitem__(self, value):                                                                       

        return _ReadOnly._cloak(self._obj[value])                                                       

    def __setitem__(self, key, value):                                                                  

        raise TypeError(                                                                                
            "{0} has a _ReadOnly proxy around it".format(type(self._obj)))                              

    def __delitem__(self, key):                                                                         

        raise TypeError(                                                                                
            "{0} has a _ReadOnly proxy around it".format(type(self._obj)))                              

    def __getattr__(self, value):                                                                       

        if value in self.__dir__():                                                                     
            return _ReadOnly._cloak(getattr(self._obj, value))                                          
        elif value in dir(self._obj):                                                                   
            raise AttributeError("{0} attribute {1} is cloaked".format(                                 
                type(self._obj), value))                                                                
        else:                                                                                           
            raise AttributeError("{0} has no {1}".format(                                               
                type(self._obj), value))                                                                

    def __setattr__(self, key, value):                                                                  

        raise TypeError(                                                                                
            "{0} has a _ReadOnly proxy around it".format(type(self._obj)))                              

    def __delattr__(self, key):                                                                         

        raise TypeError(                                                                                
            "{0} has a _ReadOnly proxy around it".format(type(self._obj)))                              

    def __dir__(self):                                                                                  

        if self._d is None:                                                                             
            self.__dict__['_d'] = [                                                                     
                i for i in dir(self._obj) if not i.startswith('set')                                    
                and i not in self._extraFilter]                                                         
        return self._d                                                                                  

    def __repr__(self):                                                                                 

        return self._obj.__repr__()                                                                     

    def __call__(self, *args, **kwargs):                                                                

        if hasattr(self._obj, "__call__"):                                                              
            return self._obj(*args, **kwargs)                                                           
        else:                                                                                           
            raise TypeError("{0} not callable".format(type(self._obj)))                                 

    def __hash__(self):                                                                                 

        return self._hash                                                                               

    def __eq__(self, other):                                                                            

        try:                                                                                            
            return hash(self) == hash(other)                                                            
        except TypeError:                                                                               
            if isinstance(other, list):                                                                 
                try:                                                                                    
                    return all(zip(self, other))                                                        
                except:                                                                                 
                    return False                                                                        
            return other == self    

Прокси-сервер DataFrame

Должно быть расширено с помощью большего количества методов, таких как sort и фильтрации всех других изменяющих состояние методов, которые не представляют интереса.

Вы можете либо создать экземпляр DataFrame -instance в качестве единственного аргумента, либо дать ему аргументы, как вам нужно было бы создать DataFrame

import pandas as pd

class DataFrameProxy(_ReadOnly):                                                                        

    EXTRA_FILTER = ('drop', 'drop_duplicates', 'dropna')                                                

    def __init__(self, *args, **kwargs):                                                                

        if (len(args) == 1 and                                                                          
                not len(kwargs) and                                                                     
                isinstance(args, pd.DataFrame)):                                                        

            super(DataFrameProxy, self).__init__(args[0],                                               
                DataFrameProxy.EXTRA_FILTER)                                                            

        else:                                                                                           

            super(DataFrameProxy, self).__init__(pd.DataFrame(*args, **kwargs),                         
                DataFrameProxy.EXTRA_FILTER)                                                            



    def sort(self, inplace=False, *args, **kwargs):                                                     

        if inplace:                                                                                     
            warnings.warn("Inplace sorting overridden")                                                 

        return self._obj.sort(*args, **kwargs) 

Наконец:

Однако, несмотря на то, что забавное создание этого приспособления, почему бы просто не иметь DataFrame, который вы не изменяете? Если он доступен только вам, лучше просто убедитесь, что вы не измените его...

Ответ 3

Пакет StaticFrame (автором которого я являюсь) реализует интерфейс, подобный Pandas, и многие обычные операции Pandas, обеспечивая при этом неизменность в базовых массивах NumPy и неизменных контейнерах Series и Frame.

Вы можете сделать неизменным весь DataFrame Pandas, преобразовав его в Frame static_frame.Frame.from_pandas(df) с помощью static_frame.Frame.from_pandas(df). Затем вы можете использовать его в качестве таблицы только для чтения.

См. Документацию StaticFrame для этого метода: https://static-frame.readthedocs.io/en/latest/api_creation.html#static_frame.Series.from_pandas.