2025, Dec 28 03:07

Как в pandas.Series с _metadata не потерять name при индексации

В pandas 2.2.3 при наследовании Series с _metadata имя серии теряется при индексации списком. Объясняем причину и показываем фикс: добавьте '_name' в _metadata.

Наследование от pandas.Series — распространённый способ переносить предметно‑ориентированное поведение и атрибуты через преобразования данных. В pandas 2.2.3 есть тонкая ловушка: если добавить пользовательские метаданные в подкласс Series, имя серии может незаметно пропасть при некоторых видах индексации. Разберём, что происходит, почему так выходит и как сохранить имя.

Как воспроизвести проблему

Поведение зависит от того, задали ли _metadata. Сначала рассмотрим подкласс без пользовательских метаданных. Имя сохраняется и при срезах, и при индексации списком.

import pandas as pd


class UserSeries(pd.Series):

    @property
    def _constructor(self):
        return UserSeries


arr = UserSeries([*'abc'], name='data')

print(f'''No _metadata:
  {isinstance(arr[0:1], UserSeries) = }
  {isinstance(arr[[0, 1]], UserSeries) = }
  {arr[0:1].name = }  
  {arr[[0, 1]].name = }
''')

class UserSeries(pd.Series):

    _metadata = ['flag']

    @property
    def _constructor(self):
        return UserSeries


arr = UserSeries([*'abc'], name='data')
arr.flag = 'MyProperty'

print(f'''With _metadata:
  {isinstance(arr[0:1], UserSeries) = }
  {isinstance(arr[[0, 1]], UserSeries) = }
  {arr[0:1].name = }  
  {arr[[0, 1]].name = }
  {getattr(arr[0:1], 'flag', 'NA') = }  
  {getattr(arr[[0, 1]], 'flag', 'NA') = }  
''')

Ожидаемый вывод демонстрирует проблемную асимметрию. Когда _metadata задано, срезы сохраняют имя, а индексация списком его теряет.

No _metadata:
  isinstance(arr[0:1], UserSeries) = True
  isinstance(arr[[0, 1]], UserSeries) = True
  arr[0:1].name = 'data'  
  arr[[0, 1]].name = 'data'

With _metadata:
  isinstance(arr[0:1], UserSeries) = True
  isinstance(arr[[0, 1]], UserSeries) = True
  arr[0:1].name = 'data'  
  arr[[0, 1]].name = None         <<< Name is lost here
  getattr(arr[0:1], 'flag', 'NA') = 'MyProperty'  
  getattr(arr[[0, 1]], 'flag', 'NA') = 'MyProperty'  

Что на самом деле происходит

Атрибут name у Series — это дескриптор, работающий с внутренним строковым атрибутом '_name'. Если _metadata не переопределять, '_name' попадает туда по умолчанию, и pandas может передавать имя дальше при операциях. Как только вы переопределяете _metadata, контроль над тем, что переносится, переходит к вам, и '_name' перестаёт включаться неявно. В итоге некоторые способы индексации — например выборка по списку — могут «уронить» имя.

Как исправить

Явно добавьте '_name' в _metadata вместе с вашими полями. Это вернёт стандартное распространение имени даже при наличии собственных метаданных.

import pandas as pd


class UserSeries(pd.Series):

    _metadata = ['extra_flag', '_name']

    @property
    def _constructor(self):
        return UserSeries


arr = UserSeries([*'abc'], name='data')

print(f'{arr[0:1].name = }')
print(f'{arr[[0, 1]].name = }')

# Вывод:
# arr[0:1].name = 'data'
# arr[[0, 1]].name = 'data'

Почему это важно

Согласованность имени критична, когда код опирается на метки для последующих операций, логирования или взаимодействия с другими компонентами. Потеря имени лишь в отдельных путях индексации делает поведение хрупким и трудным для диагностики. Если '_name' входит в переносимые метаданные, поведение остаётся единым между срезами и индексацией списком — это помогает избежать неожиданных мелочей в пайплайнах и тестах.

Итоги

Определяя пользовательские метаданные в подклассе pandas.Series в pandas 2.2.3, добавляйте '_name' в _metadata — так имя серии сохранится при любых операциях индексации. Небольшое дополнение, которое делает расширенный Series предсказуемым и упрощает понимание ваших процессов обработки данных.