- Использование булевых масок
- Поиск пропусков в данных
- Замена пропусков на определенные значения
- Замена значений в данных согласно условию
- Выводы
В библиотеке Pandas реализованы различные подходы, чтобы индексировать элементы. Можно обращаться к ним по порядковому номеру или по метке. При этом есть задачи, в которых нужно отсеивать элементы по условию с использованием специальных масок. В данной задаче указание конкретных интервалов для индексов не всегда подходит.
Для таких задач в Pandas реализован гибкий алгоритм фильтрации. В нем сложность масок определяется только фантазией аналитика и правилами логической арифметики. В этом уроке мы познакомимся с инструментами Pandas для подготовки и первичного анализа данных.
Использование булевых масок
Для работы нам понадобится датасет с недельными показателями кликов на сайтах четырех магазинов:
import pandas as pd
df_clicks = pd.read_csv('./data/Cite_clicks_week.csv', index_col=0)
print(df_clicks)
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 1319.0 -265.0 319.0 NaN
# 2 NaN 267.0 333.0 344.0
# 3 283.0 NaN 274.0 283.0
# 4 328.0 364.0 328.0 NaN
# 5 391.0 355.0 373.0 337.0
# 6 445.0 -418.0 1409.0 445.0
# 7 481.0 NaN 481.0 409.0
Среди показателей есть NaN
-значения, которые указывают на пропуски в данных. Также есть отрицательные значения и показатели, которые существенно выше всех остальных. Это вполне обычная ситуация.
Такие значения могут влиять на точность анализа данных и даже на возможность его проведения. Поэтому аналитику приходится находить их и исправлять.
Структура DataFrame
поддерживает операции среза последовательных элементов в данных по аналогии со стандартными списками:
print(df_clicks[1:5])
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 2 NaN 267.0 333.0 344.0
# 3 283.0 NaN 274.0 283.0
# 4 328.0 364.0 328.0 NaN
# 5 391.0 355.0 373.0 337.0
Чтобы извлечь конкретные строки, можно использовать булевы маски. Это массивы значений True
и False
. Строка берется, если по ее порядковому номеру в булевой маске стоит значение True
:
print(df_clicks[[True, True, False, True, False, True, False]])
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 1319.0 -265.0 319.0 NaN
# 2 NaN 267.0 333.0 344.0
# 4 328.0 364.0 328.0 NaN
# 6 445.0 -418.0 1409.0 445.0
Чтобы извлечь конкретные столбцы в срезе, используется метод loc()
, в параметры которого передается срез и массив меток столбцов:
print(df_clicks.loc[1:5, ['SHOP1']])
# => SHOP1
# day
# 1 1319.0
# 2 NaN
# 3 283.0
# 4 328.0
# 5 391.0
print(df_clicks.loc[1:5, ['SHOP1', 'SHOP3']])
# => SHOP1 SHOP3
# day
# 1 1319.0 319.0
# 2 NaN 333.0
# 3 283.0 274.0
# 4 328.0 328.0
# 5 391.0 373.0
Для больших таблиц будет неудобно вручную задавать булевы маски, как мы это делали в примерах выше. В Pandas получить маску можно с помощью логических операторов, примененных к данным:
print(df_clicks['SHOP1'] < 300)
# => day
# 1 False
# 2 False
# 3 True
# 4 False
# 5 False
# 6 False
# 7 False
# Name: SHOP1, dtype: bool
Чтобы получить нужные строки согласно булевой маске, достаточно передать ее в качестве индекса в DataFrame
:
print(df_clicks[df_clicks['SHOP1'] < 300])
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 3 283.0 NaN 274.0 283.0
Выбор нужных столбцов в таблице осуществляется по аналогии с примером выше:
print(df_clicks.loc[df_clicks['SHOP1'] < 300, ['SHOP1', 'SHOP3']])
# => SHOP1 SHOP3
# day
# 3 283.0 274.0
Поиск пропусков в данных
Одной из существенных проблем в данных являются пропуски. Многие функции агрегации, обработки и даже простые арифметические операции не могут быть выполнены при их наличии. Чтобы обнаружить пропуски, в Pandas используют метод isna()
:
print(df_clicks[df_clicks['SHOP1'].isna()])
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 2 NaN 267.0 333.0 344.0
Для поиска строк, которые не содержат пропуски, можно использовать оператор тильда для логического отрицания исходной булевой маски:
print(df_clicks[~df_clicks['SHOP1'].isna()])
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 1319.0 -265.0 319.0 NaN
# 3 283.0 NaN 274.0 283.0
# 4 328.0 364.0 328.0 NaN
# 5 391.0 355.0 373.0 337.0
# 6 445.0 -418.0 1409.0 445.0
# 7 481.0 NaN 481.0 409.0
Данный метод применим не только к столбцам, но и ко всей таблице:
print(df_clicks.isna())
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 False False False True
# 2 True False False False
# 3 False True False False
# 4 False False False True
# 5 False False False False
# 6 False False False False
# 7 False True False False
Замена пропусков на определенные значения
Когда мы находим пропуски в данных, это помогает контролировать их качество. Но часто приходится не только их находить, но и исправлять.
В качестве значения, на которое заменяется пропуск, можно взять среднее по всем данным. Чтобы найти среднее, нужно дважды применить метод mean()
, поскольку после первого применения получается массив средних для каждого магазина по отдельности:
df_clicks_mean = df_clicks.mean().mean()
print(df_clicks_mean)
# => 366.94
Чтобы заполнить пропуски, используется метод fillna()
с параметром, на который происходит замена пропущенных значений:
print(df_clicks.fillna(df_clicks_mean))
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 1319.00000 -265.00000 319.0 366.94881
# 2 366.94881 267.00000 333.0 344.00000
# 3 283.00000 366.94881 274.0 283.00000
# 4 328.00000 364.00000 328.0 366.94881
# 5 391.00000 355.00000 373.0 337.00000
# 6 445.00000 -418.00000 1409.0 445.00000
# 7 481.00000 366.94881 481.0 409.00000
Замена значений в данных согласно условию
Помимо пропусков в данных могут быть значения, которые попали туда по ошибке, были некорректно введены или искажены при записи. Такие случаи называют выбросами.
Анализ данных и поиск выбросов приводит к формированию некоторого условия, которому должны удовлетворять элементы. Если элементы ему не удовлетворяют, то данные значения можно заменить на среднее. Для этого используют метод where()
:
print(df_clicks.where(df_clicks < 1000, df_clicks_mean))
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 366.94881 -265.00000 319.00000 366.94881
# 2 366.94881 267.00000 333.00000 344.00000
# 3 283.00000 366.94881 274.00000 283.00000
# 4 328.00000 364.00000 328.00000 366.94881
# 5 391.00000 355.00000 373.00000 337.00000
# 6 445.00000 -418.00000 366.94881 445.00000
# 7 481.00000 366.94881 481.00000 409.00000
При работе с Pandas это условие формируется в виде булевой маски. Для удобства ее выносят в отдельную переменную:
mask = (0 < df_clicks) & (df_clicks < 1000)
print(mask)
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 False False True False
# 2 False True True True
# 3 True False True True
# 4 True True True False
# 5 True True True True
# 6 True False False True
# 7 True False True True
print(df_clicks.where(
mask,
df_clicks_mean,
)
)
# => SHOP1 SHOP2 SHOP3 SHOP4
# day
# 1 366.94881 366.94881 319.00000 366.94881
# 2 366.94881 267.00000 333.00000 344.00000
# 3 283.00000 366.94881 274.00000 283.00000
# 4 328.00000 364.00000 328.00000 366.94881
# 5 391.00000 355.00000 373.00000 337.00000
# 6 445.00000 366.94881 366.94881 445.00000
# 7 481.00000 366.94881 481.00000 409.00000
Выводы
В этом уроке мы познакомились с подходами к фильтрации элементов DataFrame
. Научились искать пропуски и избавляться от них. Узнали, как создавать сложные логические маски для поиска элементов, и заменять значения в найденных позициях. Эти инструменты и навыки работы с ними необходимы для аналитика любого уровня, поскольку применяются на всех этапах цикла обработки и анализа данных.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.