- Использование булевых масок
- Поиск пропусков в данных
- Замена пропусков на определенные значения
- Замена значений в данных согласно условию
- Выводы
В библиотеке 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. Научились искать пропуски и избавляться от них. Узнали, как создавать сложные логические маски для поиска элементов, и заменять значения в найденных позициях. Эти инструменты и навыки работы с ними необходимы для аналитика любого уровня, поскольку применяются на всех этапах цикла обработки и анализа данных.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.