Почему логический оператор pandas не выравнивается по индексу так, как должен?

Рассмотрим эту простую настройку:

x = pd.Series([1, 2, 3], index=list('abc'))
y = pd.Series([2, 3, 3], index=list('bca'))

x

a    1
b    2
c    3
dtype: int64

y

b    2
c    3
a    3
dtype: int64

Как видите, индексы одинаковы, просто в другом порядке.

Теперь рассмотрим простое логическое сравнение с использованием оператора равенства (==):

x == y
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)

Это вызывает ValueError, скорее всего, потому что индексы не совпадают. С другой стороны, вызов эквивалентного оператора eq работает:

x.eq(y)

a    False
b     True
c     True
dtype: bool

OTOH, метод оператора работает, если y сначала переупорядочен...

x == y.reindex_like(x)

a    False
b     True
c     True
dtype: bool

Я понял, что сравнение функций и операторов должно делать то же самое, при прочих равных условиях. Что делает eq, а оператор сравнения - нет?

Ответы

Ответ 1

Просмотр всей трассировки для сравнения серий с несовпадающими индексами, особенно с учетом сообщения об исключении:

In [1]: import pandas as pd
In [2]: x = pd.Series([1, 2, 3], index=list('abc'))
In [3]: y = pd.Series([2, 3, 3], index=list('bca'))
In [4]: x == y
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-73b2790c1e5e> in <module>()
----> 1 x == y
/usr/lib/python3.7/site-packages/pandas/core/ops.py in wrapper(self, other, axis)
   1188 
   1189         elif isinstance(other, ABCSeries) and not self._indexed_same(othe
r):
-> 1190             raise ValueError("Can only compare identically-labeled "
   1191                              "Series objects")
   1192 
ValueError: Can only compare identically-labeled Series objects

мы видим, что это намеренное решение о реализации. Кроме того, это не уникально для объектов Series - DataFrames вызывают аналогичную ошибку.

Копание в Git обвинений в соответствующих строках в конечном итоге приводит к появлению некоторых соответствующих коммитов и выдаче потоков трекера. Например, Series.__eq__ использовался для полного игнорирования индекса RHS, и в комментарии к сообщению об ошибке такого поведения автор Pandas Уэс МакКинни говорит следующее:

Это на самом деле особенность/намеренный выбор, а не bug--, это связано с # 652. Еще в январе я изменил методы сравнения для автоматического выравнивания, но обнаружил, что это приводит к большому количеству ошибок/поломок для пользователей и, в частности, многих функций NumPy (которые регулярно выполняют такие функции, как arr[1:] == arr[:-1]; пример: np.unique) перестал работать.

Это возвращает нас к вопросу о том, что Series не достаточно похож на ndarray и, вероятно, не должен быть подклассом ndarray.

Итак, у меня нет хорошего ответа для вас, кроме этого; автоматическое выравнивание было бы идеальным, но я не думаю, что смогу сделать это, если я не сделаю Series не подклассом ndarray. Я думаю, что это, вероятно, хорошая идея, но вряд ли произойдет до 0,9 или 0,10 (несколько месяцев спустя).

Затем это было изменено на текущее поведение в пандах 0.19.0. Цитируя страницу "что нового":

Операторы следующих серий были изменены, чтобы сделать все операторы согласованными, включая DataFrame (GH1134, GH4581, GH13538)

  • Операторы сравнения серий теперь вызывают ValueError, когда индексы отличаются.
  • Последовательные логические операторы выравнивают как индекс левой, так и правой стороны.

Это привело к тому, что поведение Series соответствовало поведению DataFrame, которое уже отклоняло несоответствующие индексы в сравнениях.

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

Ответ 2

В Python мне нравится то, что вы можете добавить в исходный код практически все, что угодно. А из исходного кода pd.Series.eq он вызывает:

def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
    # other stuff
    # ...

    if isinstance(other, ABCSeries):
        return self._binop(other, op, level=level, fill_value=fill_value)

и перейдите к pd.Series._binop:

def _binop(self, other, func, level=None, fill_value=None):

    # other stuff
    # ...
    if not self.index.equals(other.index):
        this, other = self.align(other, level=level, join='outer',
                                 copy=False)
        new_index = this.index

Это означает, что оператор eq выравнивает две серии перед сравнением (что, по-видимому, нормальный оператор == не делает).

Ответ 3

Назад к 2012 году, когда у нас нет eq, ne и gt, у pandas есть проблема: Series disder возвращает неожиданный вывод с помощью логики (>,<,==,!=), Так что они делают с исправлением (новая функция добавил, gt, ge, ne..)

Ссылка на билет GitHub