Spec-Zone.ru › pandas 0.25

Расширение 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:
    def __init__(self, pandas_obj):
        self._validate(pandas_obj)
        self._obj = pandas_obj

    @staticmethod
    def _validate(obj):
        # verify there is a column latitude and a column longitude
        if 'latitude' not in obj.columns or 'longitude' not in obj.columns:
            raise AttributeError("Must have 'latitude' and 'longitude'.")

    @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.

Мы настоятельно рекомендуем проверять данные в аксессоре __init__. В нашем примере GeoAccessor мы проверяем, что данные содержат ожидаемые столбцы, генерируя AttributeError при обнаружении ошибки валидации. Для аксессора Series вы должны проверить dtype в случае, если аксессор применяется только к определённым типам данных.

Типы расширений

Новое в версии 0.23.0.

Предупреждение

API pandas.api.extensions.ExtensionDtype и pandas.api.extensions.ExtensionArray являются новыми и экспериментальными. Они могут меняться между версиями без предупреждения.

Pandas определяет интерфейс для реализации типов данных и массивов, которые *расширяют* систему типов NumPy. Сам Pandas использует систему расширений для некоторых типов, которые не встроенны в NumPy (категориальные, периодические, интервальные, datetime с часовым поясом).

Библиотеки могут определять пользовательский массив и тип данных. Когда pandas сталкивается с этими объектами, они будут обработаны должным образом (т.е. не будут преобразованы в ndarray объектов). Многие методы, такие как pandas.isna(), будут использовать реализацию типа расширения.

Если вы разрабатываете библиотеку, которая реализует интерфейс, опубликуйте её на странице Типы расширенных данных.

Интерфейс состоит из двух классов.

ExtensionDtype

Тип pandas.api.extensions.ExtensionDtype похож на объект numpy.dtype. Он описывает тип данных. Реализаторы отвечают за несколько уникальных элементов, таких как имя.

Один особенно важный элемент — свойство type. Это должен быть класс, являющийся скалярным типом для ваших данных. Например, если вы пишете массив расширения для данных IP-адреса, это может быть ipaddress.IPv4Address.

См. исходный код типа расширения для определения интерфейса.

Новое в версии 0.24.0.

pandas.api.extension.ExtensionDtype может быть зарегистрирован в pandas для создания через строковое имя типа. Это позволяет создать Series и .astype() с зарегистрированным строковым именем, например, 'category' является зарегистрированным строковым аксессором для CategoricalDtype.

См. типы данных расширений для получения более подробной информации о регистрации типов данных.

ExtensionArray

Этот класс предоставляет все функциональные возможности массивов. ExtensionArrays ограничены 1 измерением. ExtensionArray связан с ExtensionDtype через атрибут dtype.

Pandas не накладывает ограничений на то, как создаётся массив расширений с помощью его __new__ или __init__, и не накладывает ограничений на то, как вы храните ваши данные. Мы требуем, чтобы ваш массив был преобразуем в массив NumPy, даже если это относительно дорого (как это происходит с Categorical).

Они могут быть основаны ни на одном, на одном или на нескольких массивах NumPy. Например, pandas.Categorical является массивом расширения, основанным на двух массивах, один для кодов и один для категорий. Массив IPv6-адресов может быть основан на структурированном массиве NumPy с двумя полями, одно для нижних 64 бит и одно для верхних 64 бит. Или они могут быть основаны на другом типе хранения, например, на списках Python.

См. исходный код массива расширения для определения интерфейса. Комментарии и строки документации содержат рекомендации по правильной реализации интерфейса.

ExtensionArray Поддержка операторов

Новое в версии 0.24.0.

По умолчанию для класса ExtensionArray операторы не определены. Существует два подхода для обеспечения поддержки операторов для вашего ExtensionArray:

  1. Определите каждый из операторов в вашем подклассе ExtensionArray.
  2. Используйте реализацию оператора из pandas, которая зависит от операторов, уже определённых для базовых элементов (скаляров) ExtensionArray.

Примечание

Независимо от выбранного подхода, вы можете установить __array_priority__ если хотите, чтобы ваша реализация вызывалась при выполнении бинарных операций с массивами NumPy.

В первом случае вы определяете выбранные операторы, например, __add__, __le__, и т.д., которые вы хотите, чтобы поддерживал ваш подкласс ExtensionArray.

Второй подход предполагает, что у базовых элементов (т.е. скалярного типа) ExtensionArray уже определены отдельные операторы. Другими словами, если ваш ExtensionArray с именем MyExtensionArray реализован таким образом, что каждый элемент является экземпляром класса MyExtensionElement, а операторы определены для MyExtensionElement, то второй подход автоматически определит операторы для MyExtensionArray.

Mixin-класс ExtensionScalarOpsMixin поддерживает второй подход. Если вы разрабатываете подкласс ExtensionArray, например, MyExtensionArray, вы можете просто включить ExtensionScalarOpsMixin в качестве родительского класса MyExtensionArray, а затем вызвать методы _add_arithmetic_ops() и/или _add_comparison_ops() для подключения операторов к вашему классу MyExtensionArray, как показано ниже:

from pandas.api.extensions import ExtensionArray, ExtensionScalarOpsMixin

class MyExtensionArray(ExtensionArray, ExtensionScalarOpsMixin):
    pass


MyExtensionArray._add_arithmetic_ops()
MyExtensionArray._add_comparison_ops()

Примечание

Поскольку pandas автоматически вызывает базовый оператор для каждого элемента по отдельности, это может быть не так эффективно, как реализация собственной версии соответствующих операторов напрямую в ExtensionArray.

Для арифметических операций эта реализация попытается воссоздать новый ExtensionArray с результатом поэлементной операции. Успех зависит от того, возвращает ли операция результат, допустимый для ExtensionArray. Если ExtensionArray невозможно воссоздать, возвращается ndarray, содержащий скалярные значения вместо этого.

Для простоты реализации и согласованности с операциями между pandas и NumPy ndarray мы рекомендуем *не* обрабатывать Series и Indexes в ваших бинарных операциях. Вместо этого вы должны обнаруживать такие случаи и возвращать NotImplemented. Когда pandas сталкивается с операцией, такой как op(Series, ExtensionArray), pandas будет

  1. распаковывать массив из Series (Series.array)
  2. вызывать result = op(values, ExtensionArray)
  3. упаковывать результат в Series

Универсальные функции NumPy

Series реализует __array_ufunc__. В рамках реализации pandas распаковывает ExtensionArray из Series, применяет ufunc и, при необходимости, упаковывает результат заново.

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

В рамках вашей реализации мы требуем, чтобы вы обращались к pandas, когда в inputs обнаруживается контейнер pandas (Series, DataFrame, Index). В таких случаях вы должны возвращать NotImplemented. Pandas позаботится о распаковке массива из контейнера и повторном вызове ufunc с необработанным входным значением.

Тестирование массивов расширений

Мы предоставляем набор тестов, гарантирующий, что ваши массивы расширений соответствуют ожидаемому поведению. Для использования набора тестов необходимо предоставить несколько фикстур 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 структур данных.

  1. Расширяемые цепочки методов с pipe
  2. Использование композиции. См. здесь.
  3. Расширение путем регистрации аксессора
  4. Расширение путем типа расширения

Этот раздел описывает, как подклассировать pandas структуры данных для удовлетворения более специфических потребностей. Есть два момента, требующие внимания:

  1. Переопределение свойств конструктора.
  2. Определение исходных свойств

Примечание

Вы можете найти хороший пример в проекте geopandas.

Переопределение свойств конструктора

Каждая структура данных имеет несколько свойств конструктора для возвращения новой структуры данных в результате операции. Переопределяя эти свойства, вы можете сохранить подклассы при pandas манипуляциях с данными.

Есть 3 свойства конструктора, которые нужно определить:

  • _constructor: Используется, когда результат манипуляции имеет те же размеры, что и оригинал.
  • _constructor_sliced: Используется, когда результат манипуляции имеет одну или несколько меньших размерностей, чем оригинал, например, при взятии среза DataFrame одиночных столбцов.
  • _constructor_expanddim: Используется, когда результат манипуляции имеет одну или несколько больших размерностей, чем оригинал, например, Series.to_frame().

В следующей таблице показано, как pandas структуры данных определяют свойства конструктора по умолчанию.

Атрибуты свойства Series DataFrame
_constructor Series DataFrame
_constructor_sliced NotImplementedError Series
_constructor_expanddim DataFrame NotImplementedError

Ниже приведен пример того, как определить SubclassedSeries и SubclassedDataFrame переопределяя свойства конструктора.

class SubclassedSeries(pd.Series):

    @property
    def _constructor(self):
        return SubclassedSeries

    @property
    def _constructor_expanddim(self):
        return SubclassedDataFrame


class SubclassedDataFrame(pd.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__. Определение исходных свойств можно выполнить двумя способами:

  1. Определите _internal_names и _internal_names_set для временных свойств, которые НЕ будут передаваться результатам манипуляции.
  2. Определите _metadata для обычных свойств, которые будут передаваться результатам манипуляции.

Ниже приведен пример определения двух исходных свойств: «internal_cache» как временное свойство и «added_property» как обычное свойство.

class SubclassedDataFrame2(pd.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

Расширение бэкэндов построения графиков

Начиная с версии 0.25, pandas можно расширить с помощью сторонних бэкэндов построения графиков. Основная идея заключается в том, чтобы позволить пользователям выбирать бэкэнд построения графиков, отличающийся от предоставленного на основе Matplotlib. Например:

>>> pd.set_option('plotting.backend', 'backend.module')
>>> pd.Series([1, 2, 3]).plot()

Это в большей или меньшей степени эквивалентно:

>>> import backend.module
>>> backend.module.plot(pd.Series([1, 2, 3]))

Затем модуль бэкэнда может использовать другие инструменты визуализации (Bokeh, Altair и т.д.) для генерации графиков.

Дополнительную информацию о том, как реализовать сторонний бэкэнд построения графиков, можно найти по адресу https://github.com/pandas-dev/pandas/blob/master/pandas/plotting/__init__.py#L1.

© 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.25.0/development/extending.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API