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.