Расширение Pandas
Хотя pandas предоставляет богатый набор методов, контейнеров и типов данных, ваши потребности могут не быть полностью удовлетворены. Pandas предлагает несколько вариантов расширения pandas.
Регистрация пользовательских аксессоров
Библиотеки могут использовать декораторы pandas.api.extensions.register_dataframe_accessor(), pandas.api.extensions.register_series_accessor() и pandas.api.extensions.register_index_accessor(), чтобы добавить дополнительные «пространства имён» к объектам pandas. Все они следуют одному соглашению: вы декорируете класс, предоставляя имя добавляемого атрибута. Метод класса __init__ получает объект, который декорируется. Например:
@pd.api.extensions.register_dataframe_accessor("geo")
class GeoAccessor(object):
def __init__(self, pandas_obj):
self._obj = pandas_obj
@property
def center(self):
# return the geographic center point of this DataFrame
lat = self._obj.latitude
lon = self._obj.longitude
return (float(lon.mean()), float(lat.mean()))
def plot(self):
# plot this array's data on a map, e.g., using Cartopy
pass
Теперь пользователи могут получить доступ к вашим методам, используя пространство имён geo:
>>> ds = pd.DataFrame({'longitude': np.linspace(0, 10),
... 'latitude': np.linspace(0, 20)})
>>> ds.geo.center
(5.0, 10.0)
>>> ds.geo.plot()
# plots data on a map
Это может быть удобным способом расширения объектов pandas без их подклассирования. Если вы напишете пользовательский аксессор, отправьте запрос на добавление его на страницу нашего экосистемы pandas.
Типы расширения
Новое в версии 0.23.0.
Предупреждение
API pandas.api.extension.ExtensionDtype и pandas.api.extension.ExtensionArray новые и экспериментальные. Они могут изменяться между версиями без предупреждения.
Pandas определяет интерфейс для реализации типов данных и массивов, которые расширяют систему типов NumPy. Сам Pandas использует систему расширений для некоторых типов, которые не встроены в NumPy (категориальные, периодические, интервальные, datetime с часовым поясом).
Библиотеки могут определять пользовательский массив и тип данных. Когда pandas сталкивается с этими объектами, они обрабатываются должным образом (т. е. не преобразуются в массив ndarray объектов). Многие методы, такие как pandas.isna(), будут обращаться к реализации типа расширения.
Если вы разрабатываете библиотеку, которая реализует интерфейс, пожалуйста, опубликуйте её на странице типов расширенных данных.
Интерфейс состоит из двух классов.
ExtensionDtype
Тип pandas.api.extension.ExtensionDtype похож на объект numpy.dtype. Он описывает тип данных. Реализаторы несут ответственность за несколько уникальных элементов, таких как имя.
Одним из особенно важных элементов является свойство type. Это должен быть класс, который является скалярным типом для ваших данных. Например, если вы писали расширенный массив для данных IP-адреса, это может быть ipaddress.IPv4Address.
Обратитесь к исходному коду типа расширенного типа для определения интерфейса.
ExtensionArray
Этот класс предоставляет все функциональные возможности массивоподобных объектов. ExtensionArrays ограничены 1 измерением. ExtensionArray связан с ExtensionDtype через атрибут dtype.
Pandas не накладывает никаких ограничений на то, как создаётся массив расширения с помощью __new__ или __init__, и не накладывает никаких ограничений на то, как вы храните свои данные. Мы требуем, чтобы ваш массив можно было преобразовать в массив NumPy, даже если это относительно дорого (как в случае с Categorical).
Они могут быть основаны на нуле, одном или нескольких массивах NumPy. Например, pandas.Categorical — это расширенный массив, основанный на двух массивах, одном для кодов и одном для категорий. Массив IPv6-адресов может быть основан на структурированном массиве NumPy с двумя полями, одним для нижних 64 бит и одним для верхних 64 бит. Или они могут быть основаны на другом типе хранения, таком как списки Python.
Обратитесь к исходному коду расширенного массива для определения интерфейса. В строках документации и комментариях содержатся рекомендации по правильному внедрению интерфейса.
Мы предоставляем набор тестов, чтобы убедиться, что ваши расширенные массивы соответствуют ожидаемому поведению. Чтобы использовать набор тестов, вы должны предоставить несколько фикстур pytest и унаследовать от базового тестового класса. Необходимые фикстуры можно найти в https://github.com/pandas-dev/pandas/blob/master/pandas/tests/extension/conftest.py.
Чтобы использовать тест, подклассифицируйте его:
from pandas.tests.extension import base
class TestConstructors(base.BaseConstructorsTests):
pass
См. https://github.com/pandas-dev/pandas/blob/master/pandas/tests/extension/base/__init__.py для списка всех доступных тестов.
Подклассирование структур данных pandas
Предупреждение
Есть несколько более простых альтернатив, прежде чем рассматривать подклассирование pandas структур данных.
- Расширяемые цепочки методов с помощью pipe
- Используйте композицию. См. здесь.
- Расширение путём регистрации аксессора
- Расширение путём типа расширения
Этот раздел описывает, как подклассировать pandas структуры данных для удовлетворения более специфических потребностей. Есть два момента, которые требуют внимания:
- Переопределить свойства конструктора.
- Определить исходные свойства
Примечание
Вы можете найти хороший пример в проекте geopandas.
Переопределение свойств конструктора
Каждая структура данных имеет несколько свойств конструктора для возвращения новой структуры данных в результате операции. Переопределяя эти свойства, вы можете сохранить подклассы при манипулировании данными pandas.
Существует 3 свойства конструктора, которые необходимо определить:
-
_constructor: Используется, когда результат манипуляции имеет такие же размеры, как и оригинал. -
_constructor_sliced: Используется, когда результат манипуляции имеет на одну размерность (размеры) меньше, чем оригинал, например, при выделении одной колонки. -
_constructor_expanddim: Используется, когда результат манипуляции имеет на одну размерность больше, чем оригинал, например, приSeries.to_frame()иDataFrame.to_panel().
В следующей таблице показано, как pandas структуры данных определяют свойства конструктора по умолчанию.
| Атрибуты свойства | Series | DataFrame |
|---|---|---|
_constructor | Series | DataFrame |
_constructor_sliced | NotImplementedError | Series |
_constructor_expanddim | DataFrame | Panel |
Ниже приведен пример того, как определить SubclassedSeries и SubclassedDataFrame, переопределяя свойства конструктора.
class SubclassedSeries(Series):
@property
def _constructor(self):
return SubclassedSeries
@property
def _constructor_expanddim(self):
return SubclassedDataFrame
class SubclassedDataFrame(DataFrame):
@property
def _constructor(self):
return SubclassedDataFrame
@property
def _constructor_sliced(self):
return SubclassedSeries
>>> s = SubclassedSeries([1, 2, 3])
>>> type(s)
<class '__main__.SubclassedSeries'>
>>> to_framed = s.to_frame()
>>> type(to_framed)
<class '__main__.SubclassedDataFrame'>
>>> df = SubclassedDataFrame({'A', [1, 2, 3], 'B': [4, 5, 6], 'C': [7, 8, 9]})
>>> df
A B C
0 1 4 7
1 2 5 8
2 3 6 9
>>> type(df)
<class '__main__.SubclassedDataFrame'>
>>> sliced1 = df[['A', 'B']]
>>> sliced1
A B
0 1 4
1 2 5
2 3 6
>>> type(sliced1)
<class '__main__.SubclassedDataFrame'>
>>> sliced2 = df['A']
>>> sliced2
0 1
1 2
2 3
Name: A, dtype: int64
>>> type(sliced2)
<class '__main__.SubclassedSeries'>
Определение исходных свойств
Чтобы позволить исходным структурам данных иметь дополнительные свойства, вы должны сообщить pandas о добавленных свойствах. pandas сопоставляет неизвестные свойства с именами данных, переопределяя __getattribute__. Определение исходных свойств можно выполнить двумя способами:
- Определите
_internal_namesи_internal_names_setдля временных свойств, которые НЕ будут переданы в результаты манипуляций. - Определите
_metadataдля обычных свойств, которые будут переданы в результаты манипуляций.
Ниже приведен пример определения двух исходных свойств: «internal_cache» как временного свойства и «added_property» как обычного свойства
class SubclassedDataFrame2(DataFrame):
# temporary properties
_internal_names = pd.DataFrame._internal_names + ['internal_cache']
_internal_names_set = set(_internal_names)
# normal properties
_metadata = ['added_property']
@property
def _constructor(self):
return SubclassedDataFrame2
>>> df = SubclassedDataFrame2({'A': [1, 2, 3], 'B': [4, 5, 6], 'C': [7, 8, 9]})
>>> df
A B C
0 1 4 7
1 2 5 8
2 3 6 9
>>> df.internal_cache = 'cached'
>>> df.added_property = 'property'
>>> df.internal_cache
cached
>>> df.added_property
property
# properties defined in _internal_names is reset after manipulation
>>> df[['A', 'B']].internal_cache
AttributeError: 'SubclassedDataFrame2' object has no attribute 'internal_cache'
# properties defined in _metadata are retained
>>> df[['A', 'B']].added_property
property
© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.23.4/extending.html