Python: Pandas

Теория: Фильтрация значений и подготовка данных для анализа

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

Рекомендуемые программы