Сообщить об ошибке.

Логическая индексация в pandas

Материал объясняет на простом примере принцип работы логической индексации в pandas для отбора и фильтрации данных.

Для выполнения примеров загрузим данные:

>>> import pandas as pd

url = (
    "https://raw.githubusercontent.com/pandas-dev"
    "/pandas/main/pandas/tests/io/data/csv/tips.csv"
)

>>> tips = pd.read_csv(url)
>>> tips
#      total_bill   tip     sex smoker   day    time  size
# 0         16.99  1.01  Female     No   Sun  Dinner     2
# 1         10.34  1.66    Male     No   Sun  Dinner     3
# 2         21.01  3.50    Male     No   Sun  Dinner     3
# 3         23.68  3.31    Male     No   Sun  Dinner     2
# 4         24.59  3.61  Female     No   Sun  Dinner     4
# ..          ...   ...     ...    ...   ...     ...   ...
# 239       29.03  5.92    Male     No   Sat  Dinner     3
# 240       27.18  2.00  Female    Yes   Sat  Dinner     2
# 241       22.67  2.00    Male    Yes   Sat  Dinner     2
# 242       17.82  1.75    Male     No   Sat  Dinner     2
# 243       18.78  3.00  Female     No  Thur  Dinner     2
# 
# [244 rows x 7 columns]

Допустим, необходимо из загруженного DataFrame отобрать все строки, в которых столбец sex имеет значение "Male", т.е. соблюдается условие "sex" == "Male". Например:

>>> tips[tips["sex"] == "Male"].head()
#    total_bill   tip   sex smoker  day    time  size
# 1       10.34  1.66  Male     No  Sun  Dinner     3
# 2       21.01  3.50  Male     No  Sun  Dinner     3
# 3       23.68  3.31  Male     No  Sun  Dinner     2
# 5       25.29  4.71  Male     No  Sun  Dinner     4
# 6        8.77  2.00  Male     No  Sun  Dinner     2

Обратите внимание на индексные метки строк - строка 0 и 4 пропущены. В них столбец sex имеет значение Female

Приведенный выше оператор tips["sex"] == "Male" просто передает в DataFrame серию Series из логических объектов True/False, тем самым отбирая и возвращая все строки с True.

Докажем это:

# смотрим, что выводит выражение 
>>> tips["sex"] == "Male"
# 0      False
# 1       True
# 2       True
# 3       True
# 4      False
#        ...  
# 239     True
# 240    False
# 241     True
# 242     True
# 243    False
# Name: sex, Length: 244, dtype: bool

>>> type(tips["sex"] == "Male")
# <class 'pandas.core.series.Series'>

Можно с уверенностью сказать, что это Series со значениями bool, которая имеет те-же индексные метки, что и исходный DataFrame.

Сохраним полученную серию в переменную, а ее передадим в исходный DataFrame:

# присваиваем выражение переменной
>>> ser = tips["sex"] == "Male"
# убедимся, что это серия
>>> type(ser)
# <class 'pandas.core.series.Series'>

# смотрим еще раз, как она выглядит
# (первые 5 строк)
>>> ser.head()
# 0    False
# 1     True
# 2     True
# 3     True
# 4    False
# Name: sex, dtype: bool

Подсчитаем количество одинаковых значений True/False в полученной серии ser:

>>> ser.value_counts()
# sex
# True     157
# False     87
# Name: count, dtype: int64

Получили 157 строк со значениями True

Теперь передадим переменную ser в исходный DataFrame. В свою очередь DataFrame выведет те индексные метки строк, которые соответствуют True в серии ser. Обратите внимание на индексные метки строк полученного DataFrame и общее количество записей (157 rows):

>>> tips[ser]
#      total_bill   tip   sex smoker  day    time  size  new_bill
# 1          8.34  1.66  Male     No  Sun  Dinner     3     4.170
# 2         19.01  3.50  Male     No  Sun  Dinner     3     9.505
# 3         21.68  3.31  Male     No  Sun  Dinner     2    10.840
# 5         23.29  4.71  Male     No  Sun  Dinner     4    11.645
# 6          6.77  2.00  Male     No  Sun  Dinner     2     3.385
# ..          ...   ...   ...    ...  ...     ...   ...       ...
# 236       10.60  1.00  Male    Yes  Sat  Dinner     2     5.300
# 237       30.83  1.17  Male    Yes  Sat  Dinner     2    15.415
# 239       27.03  5.92  Male     No  Sat  Dinner     3    13.515
# 241       20.67  2.00  Male    Yes  Sat  Dinner     2    10.335
# 242       15.82  1.75  Male     No  Sat  Dinner     2     7.910

# [157 rows x 8 columns]

Что дает логическая индексация?

А дает она буквально следующие. Если передать оператор отрицания ~ с полученной серией ser, то получим противоположный результат. То есть массив данных, где столбец sex имеет значение Female:

>>> tips[~ser]
#      total_bill   tip     sex smoker   day    time  size
# 0         16.99  1.01  Female     No   Sun  Dinner     2
# 4         24.59  3.61  Female     No   Sun  Dinner     4
# 11        35.26  5.00  Female     No   Sun  Dinner     4
# 14        14.83  3.02  Female     No   Sun  Dinner     2
# 16        10.33  1.67  Female     No   Sun  Dinner     3
# ..          ...   ...     ...    ...   ...     ...   ...
# 226       10.09  2.00  Female    Yes   Fri   Lunch     2
# 229       22.12  2.88  Female    Yes   Sat  Dinner     2
# 238       35.83  4.67  Female     No   Sat  Dinner     3
# 240       27.18  2.00  Female    Yes   Sat  Dinner     2
# 243       18.78  3.00  Female     No  Thur  Dinner     2
# 
# [87 rows x 7 columns]

Обратите внимание на индексные метки строк полученного DataFrame и общее количество записей (87 rows).

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

Например в pandas есть метод DataFrame.duplicated(), который возвращает логическую индексацию найденных дубликатов и метод DataFrame.drop_duplicates(), возвращающий исходный DataFrame с уже удаленными дубликатами. В таких случаях начинающих пользователей pandas всегда возникает вопрос: - "А как же посмотреть/вывести на печать эти дубликаты?" На помощь приходит "логическая индексация"

Для примера возьмем следующий DataFrame:

>>> import pandas as pd

df = pd.DataFrame({
    'brand': ['Yum Yum', 'Yum Yum', 'Indomie', 'Indomie', 'Indomie'],
    'style': ['cup', 'cup', 'cup', 'pack', 'pack'],
    'rating': [4, 4, 3.5, 15, 5]
})

>>> df
#     brand style  rating
# 0  Yum Yum   cup     4.0
# 1  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0

Метод DataFrame.duplicated() возвратит Series со значениями bool:

>>> df.duplicated()
# 0    False
# 1     True
# 2    False
# 3    False
# 4    False
# dtype: bool

Что бы посмотреть, какие дублирующие строки отбросил метод df.duplicated() используем логическую индексацию:

>>> df[df.duplicated()]
#      brand style  rating
# 1  Yum Yum   cup     4.0

Теперь, что бы посмотреть как данные выглядят без дубликатов, необходимо в исходный DataFrame передать отрицание ~df.duplicated() или использовать метод DataFrame.drop_duplicates():

>>> df[~df.duplicated()]
#      brand style  rating
# 0  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0

# или
>>> df.drop_duplicates()
#      brand style  rating
# 0  Yum Yum   cup     4.0
# 2  Indomie   cup     3.5
# 3  Indomie  pack    15.0
# 4  Indomie  pack     5.0