Следующая тема: ПД. Системное и критическое мышление в работе аналитика
Вернуться к разделу: "Предобработка данных"
Вернуться в оглавление: Я.Практикум
1.Введение
В огромном датафрейме вам может понадобиться определённый сегмент. Допустим, интересуют данные по узкой возрастной категории или одному городу. Чтобы выделить их, прибегают к категоризации — объединению избранных данных в произвольные группы по заданному критерию. Аналитики пишут функции на Python, чтобы выделить такие группы. Вспомним, как это делать, и применим к реальному датасету.
Чему вы научитесь
- Выделять из данных словарь категорий;
- Разделять данные на категории по числовому признаку;
- Писать функции для обработки сразу нескольких ячеек в строке.
Сколько времени это займет:
4 урока от 10 до 25 минут
Постановка задачи:
Готовы ли мы к большему потоку клиентов? Пользователям важно быть услышанными, поэтому вас просят помочь с оптимизацией работы службы поддержки. Там работают очень организованные менеджеры, но даже они устают от того, что для поступающих проблем нет прозрачной системы приоритетов.
2.Знакомство с данными
Важно уметь не только привлекать клиентов, но и удерживать их.
Вы уже анализировали поисковую выдачу товаров Яндекс.Маркета, сравнивали источники трафика и избавлялись от дубликатов в ассортименте — всё это направлено на улучшение маркетинга. Однако не менее важны усилия по поддержке пользователей.
Вам передали статистику обращений в службу поддержки Яндекс.Маркета: id каждого пользователя с темой и временем обращения.
Ваша задача — настроить приоритизацию задач по каждому виду обращений. Сейчас непонятно, за решение каких проблем браться в первую очередь, а какие могут подождать. Такой формат загрузки службы поддержки не повышает качество сервиса.
Задача №1
Прочитайте файл с отзывами, размещённый по адресу: /datasets/support.csv
Сохраните таблицу в переменной support. Выведите первые 10 строк.
Путь к файлу: /datasets/support.csv
import pandas as pd support = pd.read_csv('/datasets/support.csv') print (support.head(10))
user_id Тип обращения type_id Время обращения
0 DNcd8dnS Жалоба на товар в магазине 3 2019-03-28 13:58:24
1 0e9MvwGs Мошенничество 5 2019-03-08 17:11:59
2 boyDUG4C Мошенничество 5 2019-03-03 17:52:34
3 5jMA27s1 Жалоба на товар в магазине 3 2019-03-16 15:18:21
4 wvtyctOK Накрутка отзывов 2 2019-03-13 14:43:14
5 zvN8W1tc Жалоба на видеообзор 8 2019-03-29 20:32:17
6 bBmybSbr Не работает сайт 1 2019-04-08 21:37:15
7 5JX1P5G8 Продажа запрещенных товаров 6 2019-04-06 12:15:20
8 pbbG3xWx Не работает сайт 1 2019-04-09 17:30:43
9 vZWtTovT Жалоба на товар в магазине 3 2019-04-16 12:26:16
Всё не то и всё не так, когда изучаешь список обращений в службу поддержки.
Задача №2
Просмотрите общую информацию о наборе данных.
import pandas as pd
support = pd.read_csv('/datasets/support.csv')
print (support.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3000 entries, 0 to 2999
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 3000 non-null object
1 Тип обращения 3000 non-null object
2 type_id 3000 non-null int64
3 Время обращения 3000 non-null object
dtypes: int64(1), object(3)
memory usage: 93.9+ KB
None
Ладно хоть с типами данных всё хорошо, а вот имена столбцов записаны некорректно. Разберитесь с этим.
Задача №3
Замените названия столбцов методом rename()
:
- Тип обращения поменяйте на
type_message
;
- Время обращения — на
timestamp
.
Столбцы user_id
и type_id
оставьте без изменений.
Проверьте успешность замены методом info().
import pandas as pd
support = pd.read_csv('/datasets/support.csv')
support = support.rename(columns={'Тип обращения':'type_message'})
support = support.rename(columns={'Время обращения':'timestamp'})
print (support.info())
Результат
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3000 entries, 0 to 2999
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 3000 non-null object
1 type_message 3000 non-null object
2 type_id 3000 non-null int64
3 timestamp 3000 non-null object
dtypes: int64(1), object(3)
memory usage: 93.9+ KB
None
Столбец с именем на type и столбец 'timestamp' отныне ваши постоянные спутники. Вы с ними свыкнетесь так, что вам будет их не хватать даже в отпуске.
3.Декомпозиция таблиц
Типы обращений в поддержку хранятся в виде строк разной длины:
0 DNcd8dnS Жалоба на товар в магазине 3 2019-03-28 13:58:24
1 0e9MvwGs Мошенничество 5 2019-03-08 17:11:59
2 boyDUG4C Мошенничество 5 2019-03-03 17:52:34
3 5jMA27s1 Жалоба на товар в магазине 3 2019-03-16 15:18:21
4 wvtyctOK Накрутка отзывов 2 2019-03-13 14:43:14
5 zvN8W1tc Жалоба на видеообзор 8 2019-03-29 20:32:17
6 bBmybSbr Не работает сайт 1 2019-04-08 21:37:15
7 5JX1P5G8 Продажа запрещенных товаров 6 2019-04-06 12:15:20
8 pbbG3xWx Не работает сайт 1 2019-04-09 17:30:43
9 vZWtTovT Жалоба на товар в магазине 3 2019-04-16 12:26:16
К чему приводит такой способ хранения?
- Усложняется визуальная работа с таблицей.
- Увеличивается размер файла и время обработки данных.
- Чтобы отфильтровать данные по типу обращения, приходится набирать его полное название. А в нём можно ошибиться.
- Создание новых категорий и изменение старых отнимает много времени. Предупредить появление этих проблем можно. Создадим отдельный «справочник», где названию категории будет соответствовать номер. И в будущих таблицах будем обращаться уже не к длинной строке, а к её числовому обозначению.
Рассмотрим на примере. Владельцы сети ресторанов хотят оценить текущую ситуацию на рынке, чтобы понять, какой ресторан открыть следующим.
import pandas as pd
data = pd.read_csv('food_market_stats.csv')
print(data.head(10))
id cuisine rating name
0 1 Ближневосточная 5.0 The Grand Barbecue
1 1 Ближневосточная 5.0 The Grand Barbecue
2 1 Ближневосточная 5.0 The Grand Barbecue
3 1 Ближневосточная 4.0 The Grand Barbecue
4 1 Ближневосточная 4.0 The Grand Barbecue
5 1 Ближневосточная 5.0 The Grand Barbecue
6 1 Ближневосточная 5.0 The Grand Barbecue
7 1 Ближневосточная 3.0 The Grand Barbecue
8 1 Ближневосточная 1.0 The Grand Barbecue
9 1 Ближневосточная 5.0 The Grand Barbecue
В таблице уникальный идентификатор id
, кухня ресторана cuisine
, его оценка от посетителя rating
и название name
. Задача — найти средний рейтинг для каждого ресторана.
Уточним, какие виды кухонь подают в ресторанах:
print(data['cuisine'].value_counts().head(15))
Кавказская 3289
Средиземноморская 2678
Японская 2670
Русская 2521
Ближневосточная 2343
Азиатская 1977
Русская|Азиатская 1695
Кавказская|Русская 1586
Средиземноморская|Русская|Японская 1473
Ближневосточная|Японская 1137
Русская|Кавказская 1111
Японская|Азиатская 1089
Русская|Японская|Кавказская 984
Ближневосточная|Японская|Кавказская 886
Кавказская|Азиатская 799
Name: kitchen, dtype: int64
Обратите внимание, что в одном ресторане могут подавать блюда сразу нескольких кухонь. При просмотре таблицы этот факт не бросается в глаза, а набирать такие сочетания для поиска неудобно.
Узнаем, сколько ресторанов всего:
print(len(data['id'].unique()))
>> 273
Получается, что и название, и тип кухни многократно повторяются в данных, а строки с ними занимают много места. Создадим “справочник” и оптимизируем данные.
Разделим таблицу data
на две части:
В первой оставим столбцы id
и rating
(оценки, поставленные ресторану посетителями).
rest_log = data[['id','rating']]
print(rest_log.head(10))
id rating
0 1 5.0
1 1 5.0
2 1 5.0
3 1 4.0
4 1 4.0
5 1 5.0
6 1 5.0
7 1 3.0
8 1 1.0
9 1 5.0
Вторая станет «справочником»: каждому из 273 ресторанов запишем id
, название name
и тип кухни cuisine
.
rest_dict = data[['id', 'cuisine', 'name']]
print(rest_dict.head(10))
id cuisine name
0 1 Ближневосточная The Grand Barbecue
1 1 Ближневосточная The Grand Barbecue
2 1 Ближневосточная The Grand Barbecue
3 1 Ближневосточная The Grand Barbecue
4 1 Ближневосточная The Grand Barbecue
5 1 Ближневосточная The Grand Barbecue
6 1 Ближневосточная The Grand Barbecue
7 1 Ближневосточная The Grand Barbecue
8 1 Ближневосточная The Grand Barbecue
9 1 Ближневосточная The Grand Barbecue
В «справочнике» вы заметили большое количество дубликатов. Их нужно удалить. Применим знакомую цепочку методов: drop_duplicates()
и reset_index()
.
rest_dict = rest_dict.drop_duplicates().reset_index(drop=True)
print(rest_dict.head())
id cuisine name
0 1 Ближневосточная The Grand Barbecue
1 2 Японская The Bitter Bite
2 3 Кавказская The Curry Apple
3 4 Японская The Caviar Courtyard
4 5 Кавказская|Ближневосточная|Русская The Little Brothers
После удаления дубликатов представление данных с оценками посетителей стало компактнее. Таблицу легко группировать по id
ресторана и находить средний рейтинг.
print(rest_log.groupby('id').mean().sort_values('rating',ascending=False).head(10))
rating
id
134 4.159420
111 4.133333
167 4.131250
113 4.113208
162 4.099010
219 4.095238
20 4.090909
264 4.031746
47 4.029167
77 4.024490
Надо сказать, что на практике декомпозировать таблицы приходится не так часто, чаще требуется их объединить по какому-то столбцу.
Вы научитесь это делать в следующем спринте. Однако сам навык создания подобных таблиц-«справочников» очень полезен.
Проделаем то же самое с обращениями в службу поддержки.
Задача №1
Уточните, сколько раз встречается каждый тип обращений. Результат выведите на экран.
import pandas as pd
support = pd.read_csv('/datasets/support_upd.csv')
print (support['type_message'].value_counts().head(10))
Жалоба на товар в магазине 606
Мошенничество 586
Продажа поддельной продукции 312
Не работает сайт 311
Продажа запрещенных товаров 303
Накрутка отзывов 302
Жалоба на видеообзор 297
Отзыв удалён 283
Name: type_message, dtype: int64
И такая дребедень целый день.
Задача №2
Создайте новую таблицу support_log. Из таблицы support перенесите следующие столбцы: 'user_id', 'type_id', 'timestamp'. Первые 10 строк support_log напечатайте на экране.
import pandas as pd
support = pd.read_csv('/datasets/support_upd.csv')
support_log = support[['user_id','type_id','timestamp']]
print (support_log.head(10))
Результат
user_id type_id timestamp
0 DNcd8dnS 3 2019-03-28 13:58:24
1 0e9MvwGs 5 2019-03-08 17:11:59
2 boyDUG4C 5 2019-03-03 17:52:34
3 5jMA27s1 3 2019-03-16 15:18:21
4 wvtyctOK 2 2019-03-13 14:43:14
5 zvN8W1tc 8 2019-03-29 20:32:17
6 bBmybSbr 1 2019-04-08 21:37:15
7 5JX1P5G8 6 2019-04-06 12:15:20
8 pbbG3xWx 1 2019-04-09 17:30:43
9 vZWtTovT 3 2019-04-16 12:26:16
Теперь неизвестно кто жалуется непонятно на что. Зато знаем точно, когда.
Задача №3
Создайте новую таблицу support_dict
. Из таблицы support
перенесите следующие столбцы: 'type_message', 'type_id'
. Первые 10 строк support_dict выведите на экран.
import pandas as pd
support = pd.read_csv('/datasets/support_upd.csv')
support_dict = support[['type_message', 'type_id']]
print (support_dict.head(10))
Результат
type_message type_id
0 Жалоба на товар в магазине 3
1 Мошенничество 5
2 Мошенничество 5
3 Жалоба на товар в магазине 3
4 Накрутка отзывов 2
5 Жалоба на видеообзор 8
6 Не работает сайт 1
7 Продажа запрещенных товаров 6
8 Не работает сайт 1
9 Жалоба на товар в магазине 3
Вы создали таблицу, где каждому типу жалобы сопоставлен идентификатор.
Задача №4
Удалите дубликаты в таблице support_dict
и распечатайте её в порядке возрастания значений столбца type_id
.
import pandas as pd
support = pd.read_csv('/datasets/support_upd.csv')
support_dict=support[['type_message','type_id']]
support_dict = support_dict.drop_duplicates().reset_index(drop=True)
print (support_dict.sort_values('type_id',ascending=True))
Результат
type_message type_id
4 Не работает сайт 1
2 Накрутка отзывов 2
0 Жалоба на товар в магазине 3
6 Продажа поддельной продукции 4
1 Мошенничество 5
5 Продажа запрещенных товаров 6
7 Отзыв удалён 7
3 Жалоба на видеообзор 8
Грустит самурай Отзывы накрутили
Поддержка, как быть?
4.Категоризация по числовым диапазонам
Вам как аналитику задали вопрос: клиентов какого возраста больше всего в нашей базе? Ознакомимся с данными:
import pandas as pd
clients = pd.read_csv('stats_by_age.csv')
print(clients.head())
\ |
id |
first_name |
last_name |
age |
0 |
1 |
Alikee |
O'Mullally |
24 |
1 |
2 |
Rosella |
Winnard |
29 |
2 |
3 |
Peri |
Talmadge |
37 |
3 |
4 |
Brose |
Attwooll |
21 |
4 |
5 |
Rycca |
Caunter |
29 |
В столбце FIRST_NAME
значатся имена клиентов, в столбце LAST_NAME
— их фамилии, а возраст — целые числа в столбце AGE
. Количество клиентов каждого возраста можно посчитать методом value_counts()
:
print(clients['age'].value_counts())
17 13
20 10
37 9
....
72 1
73 1
16 1
Name: age, dtype: int64
Какие выводы можно сделать из этих данных?
Неужели компании необходимо сосредоточиться на двадцати-, семнадцати- и тридцатисемилетних, а всех, кому 16 или 38, оставить без внимания?
И как тогда быть на следующий год?
Работать с единичными отрывками и делать из них статистические выводы нельзя. Значит, нужно сгруппировать данные, чтобы численности каждой группы хватало для формулировки выводов.
Нужна категоризация — объединение данных в категории. Распределим клиентов так:
- Клиенты до 18 лет включительно попадают в категорию «дети»;
- Клиенты от 19 до 64 лет — категория «взрослые»;
- Клиенты 65 лет и старше принадлежат к категории «пенсионеры».
Запишем правила классификации клиентов как функции. В отличие от стандартных методов Pandas функции гибки: в них можно закладывать более сложную логику и добавлять новые требования. Тестировать функции подстановкой разных значений удобнее, чем применять методы Pandas ко всему датафрейму.
На вход функции попадает возраст, а возвращает она категорию клиента. Опишем функцию:
def age_group(age):
"""
Возвращает возрастную группу по значению возраста age, используя правила:
- 'дети', если age <= 18 лет;
- 'взрослые', если age от 19 до 64;
- 'пенсионеры' — от 65 и старше.
"""
if age <= 18:
return 'дети'
if age <= 64:
return 'взрослые'
return 'пенсионеры'
Протестируем работу функции для каждого правила. Проверим, в какую категорию попадёт четырнадцатилетний клиент:
дети
... пятидесятипятилетний:
взрослые
... и наконец, семидесятилетний:
пенсионеры
Написанная функция работает корректно. Осталось создать отдельный столбец с возрастными категориями, и в его ячейках записать значения, возвращаемые функцией.
Для этого нужен метод apply()
: он берёт значения столбца датафрейма и применяет к ним функцию из своего аргумента. Здесь apply()
следует вызвать для столбца AGE
, так как в нём содержатся данные, которые функция примет на вход. Аргументом метода станет сама функция age_group
.
clients['age_group'] = clients['age'].apply(age_group)
print(clients.head(10))
id first_name last_name age age_group
0 1 Alikee O'Mullally 24 взрослые
1 2 Rosella Winnard 29 взрослые
2 3 Peri Talmadge 37 взрослые
3 4 Brose Attwooll 21 взрослые
4 5 Rycca Caunter 29 взрослые
5 6 Jennine Gamage 27 взрослые
6 7 Archibold Wife 72 пенсионеры
7 8 Karalynn Busk 23 взрослые
8 9 Kari Canlin 29 взрослые
9 10 Asia Cudde 28 взрослые
Выведем статистику по возрастной группе методом value_counts()
:
print(clients['age_group'].value_counts())
взрослые 145
дети 22
пенсионеры 7
Name: age_group, dtype: int64
В таком виде данные годятся для анализа.
Также методом apply()
можно создать столбец на основе данных из нескольких других столбцов.
Допустим, нужно сделать новый столбец с полным именем клиента. Напишем функцию, которая принимает строку датафрейма как аргумент row
. Строка таблицы — это объект Series
, поэтому в теле функции можно обратиться к отдельным её ячейкам:
def make_full_name(row):
"""
Возвращает полное имя (сочетание имени и фамилии)
"""
full_name = row['first_name'] + ' ' + row['last_name']
return full_name
Когда метод apply()
оперирует данными из нескольких столбцов, его вызывают ко всему датафрейму. В таком случае он принимает не только название функции, но и параметр axis
: со значением 1
, чтобы применить метод ко всем строкам датафрейма, и со значением 0
— ко всем столбцам.
clients['full_name'] = clients.apply(make_full_name, axis=1)
print(clients.head(10))
id first_name last_name age full_name
0 1 Alikee O'Mullally 24 Alikee O'Mullally
1 2 Rosella Winnard 29 Rosella Winnard
2 3 Peri Talmadge 37 Peri Talmadge
3 4 Brose Attwooll 21 Brose Attwooll
4 5 Rycca Caunter 29 Rycca Caunter
5 6 Jennine Gamage 27 Jennine Gamage
6 7 Archibold Wife 72 Archibold Wife
7 8 Karalynn Busk 23 Karalynn Busk
8 9 Kari Canlin 29 Kari Canlin
9 10 Asia Cudde 28 Asia Cudde
Метод apply()
позволяет применить функцию к каждой строке датафрейма без цикла. В pandas перебор строк циклами — это неоптимальный путь, «сжирающий» время и память.
Задача №1
Сгруппируйте данные по типу события и посчитайте количество событий. Сохраните группировку с подсчётом в переменной support_log_grouped и выведите её на экран.
import pandas as pd
support_log = pd.read_csv('/datasets/support_log.csv')
support_log_grouped = support_log.groupby('type_id').count()
print (support_log_grouped)
Результат
Unnamed: 0 user_id timestamp
type_id
1 311 311 311
2 302 302 302
3 606 606 606
4 312 312 312
5 586 586 586
6 303 303 303
7 283 283 283
8 297 297 297
Ваша наука — борьба с самыми разными проблемами. Любая наука начинается с классификации. Вот противники разделены на типы и пересчитаны по головам.
Задача №2
Напишите функцию alert_group(messages)
, которая оценивает приоритет в зависимости от количества сообщений. Если параметр не более 300, она должна возвращать строку 'средний'
, если значение параметра от 301
до 500
включительно, тогда строку 'высокий'
. Для более высоких значений должна возвращаться строка 'критичный'
. Проверьте, что ваша функция отвечает верно, когда ей передают числа 10
, 450
, 1000
. Каждое значение выводите на новой строке.
import pandas as pd
support_log = pd.read_csv('/datasets/support_log.csv')
support_log_grouped = support_log.groupby('type_id').count()
def alert_group(messages):
if messages <= 300:
return 'средний'
elif messages <= 500:
return 'высокий'
return 'критичный'
print (alert_group(10))
print (alert_group(450))
print (alert_group(1000))
средний
высокий
критичный
Трудности есть большие, очень большие и средние. А мелких нет. Потому что каждое обращение клиента важно. Привыкайте!
Задача №3
Добавьте к таблице support_log_grouped
столбец 'alert_group'
, где хранятся результаты применения вашей функции alert_group()
.
Закомментируйте результаты предыдущего задания. Посмотрите верхние 10 строк support_log_grouped
: убедитесь, что функция правильно расставила приоритеты.
import pandas as pd
support_log = pd.read_csv('/datasets/support_log.csv')
support_log_grouped = support_log.groupby('type_id').count()
def alert_group(messages):
if messages <= 300:
return 'средний'
elif messages <= 500:
return 'высокий'
return 'критичный'
#print (alert_group(10))
#print (alert_group(450))
#print (alert_group(1000))
support_log_grouped['alert_group'] = support_log_grouped['user_id'].apply(alert_group)
print (support_log_grouped.head(10))
Результат
Unnamed: 0 user_id timestamp alert_group
type_id
1 311 311 311 высокий
2 302 302 302 высокий
3 606 606 606 критичный
4 312 312 312 высокий
5 586 586 586 критичный
6 303 303 303 высокий
7 283 283 283 средний
8 297 297 297 средний
Каждому обращению прописан приоритет. Если критичных нет, можете устроить совещание. А если есть, придётся работать.
Задача №4
Посчитайте количество обращений по каждому приоритету и выведите результат на экран. Вывод от прошлых задач при необходимости закомментируйте.
import pandas as pd
support_log = pd.read_csv('/datasets/support_log.csv')
support_log_grouped = support_log.groupby('type_id').count()
def alert_group(messages):
if messages <= 300:
return 'средний'
elif messages <= 500:
return 'высокий'
return 'критичный'
#print (alert_group(10))
#print (alert_group(450))
#print (alert_group(1000))
support_log_grouped['alert_group'] = support_log_grouped['user_id'].apply(alert_group)
#print (support_log_grouped.head(10))
print (support_log_grouped.groupby('alert_group').sum())
Результат
Unnamed: 0 user_id timestamp
alert_group
высокий 1228 1228 1228
критичный 1192 1192 1192
средний 580 580 580
Спросите у Алисы, как грамотно сообщать своим сотрудникам, что всё плохо.
5.Функция для одной строки
Вашу изначальную задачу усложнили: теперь необходимо поделить «взрослых» на занятых и безработных. Разработчики добавили новый столбец ['unemployed']
, где значение 1 означает, что у клиента нет работы, а значение 0 — что работа есть. Взглянем на обновленные данные:
import pandas as pd
clients = pd.read_csv('/datasets/stats_by_age_employment.csv')
clients.head(10)
id first_name last_name age unemployed
---------------------------------------------
0 1 Alikee O'Mullally 24 1
1 2 Rosella Winnard 29 0
2 3 Peri Talmadge 37 0
3 4 Brose Attwooll 21 1
4 5 Rycca Caunter 29 0
5 6 Jennine Gamage 27 0
6 7 Archibold Wife 72 1
7 8 Karalynn Busk 23 0
8 9 Kari Canlin 29 0
9 10 Asia Cudde 28 0
В прошлом уроке вы научились писать функции, работающие лишь с одним значением на входе. Так, функции age_group
, возвращающей возрастную категорию клиента, требовалось значение столбца ['age']
.
Однако правила распределения клиентов по категориям перестали зависеть только от возраста:
- Клиенты до 18 лет включительно по-прежнему в категории «дети»;
- Клиенты от 19 до 64 лет при наличии работы — категория «занятые»;
- Клиенты от 19 до 64 лет без работы — «безработные»;
- Клиенты старше 65 лет остаются в категории «пенсионеры».
Получается, называя клиента «занятым» или «безработным», принимают во внимание значения двух столбцов: возраста ['age']
и занятости ['unemployed']
.
Чтобы функция учитывала несколько столбцов датафрейма, в качестве аргумента ей передают всю строку целиком. Обозначим строку переменной row, а в коде функции обратимся к конкретным значениям столбцов row['age']
и row['unemployed']
.
import pandas as pd
clients = pd.read_csv('/datasets/stats_by_age_employment.csv')
def age_group_unemployed(row):
"""
Возвращает возрастную группу по значению возраста age и занятости unemployed, используя правила:
- 'дети' при значении age <= 18 лет
- 'безработные' при значении age от 19 до 64 лет включительно и значении unemployed = 1
- 'занятые' при значении age от 19 до 64 лет включительно и значении unemployed = 0
- 'пенсионеры' во всех остальных случаях
"""
age = row['age']
unemployed = row['unemployed']
if age <= 18:
return 'дети'
if age <= 64:
if unemployed == 1:
return 'безработные'
return 'занятые'
return 'пенсионеры'
В прошлом уроке, тестируя работу функции, мы меняли количество лет в её аргументе. Сейчас на входе не только возраст, но и занятость, значит, для проверки нужно передавать целую строку датафрейма с этими значениями. Это делают в несколько шагов:
- Создают два списка. В одном — значения, в другом — названия столбцов датафрейма
row_values = [24, 1] #значения возраста и занятости
row_columns = ['age', 'unemployed'] #названия столбцов
- Формируют строку:
row = pd.Series(data=row_values, index=row_columns)
- Передают строку в качестве аргумента функции для тестирования:
age_group_unemployed(row)
Проверим работу функции при разных значениях на входе:
...
row_values = [24, 1] #клиенту 24 года, и он безработный
row_columns = ['age', 'unemployed']
row = pd.Series(data=row_values, index=row_columns)
age_group_unemployed(row)
'безработные'
...
row_values = [36, 0] #клиенту 36 лет, и у него есть работа
row_columns = ['age', 'unemployed']
row = pd.Series(data=row_values, index=row_columns)
age_group_unemployed(row)
'занятые'
Функция работает корректно. Осталось создать новый столбец clients['age_group']
со значениями-результатами работы функции age_group_unemployed()
. Как и в прошлом уроке, вызовем метод apply()
, однако с двумя важными отличиями от прошлого примера:
- Метод
apply()
применяем не к столбцу clients['age']
, а к датафрейму clients
.
- По умолчанию Pandas передаёт в функцию
age_group_unemployed()
столбец. Чтобы на вход в функцию отправлялись строки, нужно указать параметр axis = 1
метода apply()
.
С учётом этих двух замечаний новый столбец age_group
формируется так:
clients['age_group'] = clients.apply(age_group_unemployed, axis=1)
Проверим результат работы функции:
import pandas as pd
clients = pd.read_csv('/datasets/stats_by_age_employment.csv')
def age_group_unemployed(row):
"""
Возвращает возврастную группу по значению возраста age и занятости unemployed, используя правила:
- 'дети' при значении age <= 18 лет
- 'безработные' при значении age от 19 до 64 лет включительно и значении unemployed = 1
- 'занятые' при значении age от 19 до 64 лет включительно и значении unemployed = 0
- 'пенсионеры' во всех остальных случаях
"""
age = row['age']
unemployed = row['unemployed']
if age <= 18:
return 'дети'
if age <= 64:
if unemployed == 1:
return 'безработные'
return 'занятые'
return 'пенсионеры'
clients['age_group'] = clients.apply(age_group_unemployed, axis=1)
print(clients.head(10))
id first_name last_name age unemployed age_group
0 1 Alikee O'Mullally 24 1 безработные
1 2 Rosella Winnard 29 0 занятые
2 3 Peri Talmadge 37 0 занятые
3 4 Brose Attwooll 21 1 безработные
4 5 Rycca Caunter 29 0 занятые
5 6 Jennine Gamage 27 0 занятые
6 7 Archibold Wife 72 1 пенсионеры
7 8 Karalynn Busk 23 0 занятые
8 9 Kari Canlin 29 0 занятые
9 10 Asia Cudde 28 0 занятые
Функция работает корректно. Применим метод value_counts()
для подсчёта значений каждой категории.
print(clients['age_group'].value_counts())
занятые 108
безработные 37
дети 22
пенсионеры 7
Name: age_group, dtype: int64
Научившись прописывать функции для одной строки и поделив взрослых на безработных и занятых, вы подготовили более точный отчёт для руководителя.
Задача №1
Прочитайте файл с отзывами, размещённый по адресу: /datasets/support_log_grouped.csv Результат выведите на экран.
import pandas as pd
#напишите ваш код здесь
support_log_grouped = pd.read_csv('/datasets/support_log_grouped.csv')
print (support_log_grouped)
Результат
type_id user_id timestamp alert_group importance
0 1 311 311 высокий 1
1 2 302 302 высокий 0
2 3 606 606 критичный 0
3 4 312 312 высокий 1
4 5 586 586 критичный 1
5 6 303 303 высокий 1
6 7 283 283 средний 1
7 8 297 297 средний 0
Лайфхак: делайте срочное и важное. А несрочное и неважное не делайте.
Задача №2
Напишите функцию alert_group_importance(row)
, работающую в соответствии со следующей логикой:
- Если приоритет
'alert_group'
средний, а важность 'importance'
оценена в единицу
, возвращать команду для отдела поддержки: 'обратить внимание'
;
- Если приоритет
высокий
, а важность — единица
, возвращать: 'высокий риск'
;
- Если приоритет
критичный
, а важность — единица
, возвращать: 'блокер'
;
Во всех остальных случаях выводить: 'в порядке очереди'
.
Проверьте работоспособность функции для разных значений. Чтобы тренажёр принял решение, оставьте в переменной row_values значение ['высокий', 1]
.
Не забудьте передать строке датафрейма названия столбцов.
import pandas as pd
support_log_grouped = pd.read_csv('/datasets/support_log_grouped.csv')
def alert_group_importance(row):
# реализуйте логику функции
if row['alert_group'] == 'средний' and row['importance'] == 1:
return 'обратить внимание'
elif row['alert_group'] == 'высокий' and row['importance'] == 1:
return 'высокий риск'
elif row['alert_group'] == 'критичный' and row['importance'] == 1:
return 'блокер'
return 'в порядке очереди'
row_values = ['высокий', 1]
row_columns = ['alert_group', 'importance']
row = pd.Series(data=row_values, index=row_columns)
print(alert_group_importance(row))
высокий риск
Да вы ещё и тестировщик или тестировщица!
Задача №3
Создайте новый столбец importance_status
и сохраните в нём результаты работы функции alert_group_importance()
. Закомментируйте вывод предыдущей задачи и напечатайте обновлённый датасет на экране.
import pandas as pd
support_log_grouped = pd.read_csv('/datasets/support_log_grouped.csv')
def alert_group_importance(row):
# реализуйте логику функции
if row['alert_group'] == 'средний' and row['importance'] == 1:
return 'обратить внимание'
elif row['alert_group'] == 'высокий' and row['importance'] == 1:
return 'высокий риск'
elif row['alert_group'] == 'критичный' and row['importance'] == 1:
return 'блокер'
return 'в порядке очереди'
row_values = ['высокий', 1]
row_columns = ['alert_group', 'importance']
row = pd.Series(data=row_values, index=row_columns)
#print(alert_group_importance(row))
support_log_grouped['importance_status'] = support_log_grouped.apply(alert_group_importance, axis=1)
print(support_log_grouped)
Результат
type_id user_id timestamp alert_group importance importance_status
0 1 311 311 высокий 1 высокий риск
1 2 302 302 высокий 0 в порядке очереди
2 3 606 606 критичный 0 в порядке очереди
3 4 312 312 высокий 1 высокий риск
4 5 586 586 критичный 1 блокер
5 6 303 303 высокий 1 высокий риск
6 7 283 283 средний 1 обратить внимание
7 8 297 297 средний 0 в порядке очереди
Благодаря вашим усилиям, агент поддержки больше не будет задаваться вопросами приоритизации и сможет сконцентрироваться на решении проблем.
Задача №4
Посчитайте, сколько раз встречается каждый из статусов в столбце importance_status
. Закомментируйте вывод предыдущей задачи и напечатайте результат подсчётов на экране.
import pandas as pd
support_log_grouped = pd.read_csv('/datasets/support_log_grouped.csv')
def alert_group_importance(row):
# реализуйте логику функции
if row['alert_group'] == 'средний' and row['importance'] == 1:
return 'обратить внимание'
elif row['alert_group'] == 'высокий' and row['importance'] == 1:
return 'высокий риск'
elif row['alert_group'] == 'критичный' and row['importance'] == 1:
return 'блокер'
return 'в порядке очереди'
row_values = ['высокий', 1]
row_columns = ['alert_group', 'importance']
row = pd.Series(data=row_values, index=row_columns)
#print(alert_group_importance(row))
support_log_grouped['importance_status'] = support_log_grouped.apply(alert_group_importance, axis=1)
#print(support_log_grouped)
print (support_log_grouped['importance_status'].value_counts())
Результат
высокий риск 3
в порядке очереди 3
блокер 1
обратить внимание 1
Name: importance_status, dtype: int64
Признание проблемы — половина успеха в её разрешении. А вы не просто признали, ещё классифицировали, посчитали и в список аккуратно положили. Это уже 90% успеха. Осталась самая малость — решить проблему.
6.Заключение
Вы проанализировали трафик и выдачу в поиске. Если ваши рекомендации помогут увеличить количество пользователей, это добавит работы службе поддержки. К такому повороту нужно подготовиться заранее.
В этой теме вы классифицировали обращения в службу поддержки. Теперь легко определить, какие проблемы требуют срочного решения, а какие можно отложить. Команда поддержки может не тратить время на выявление приоритетных задач, а сосредоточиться на качественном обслуживании клиентов. Возможно, ваша классификация вызовет чрезмерный рост количества счастливых пользователей! Но это уже совсем другая гипотеза...
Заберите с собой
Чтобы ничего не забыть, скачайте шпаргалку и конспект темы.
Где ещё почитать про категоризацию данных
Полезные приёмы библиотеки Pandas
Проверочные задания. Категоризация данных
Чтобы пройти тест нужно правильно ответить на 4 вопроса из 8.
Время на прохождение: 10 минут
Задание 1 из 8
Информацию о категориях иногда хранят внутри датафрейма, не создавая отдельную таблицу. В чём недостатки такого способа? Выберите несколько вариантов.
Правильный ответ
Такие данные занимают больше места, и их долго обрабатывать.
Правильный ответ
Обновлять данные о категориях слишком долго.
Казалось бы, можно хранить все данные в одном месте — так ничего не потеряется. Но одна таблица не справится с большим объёмом информации, поэтому лучше разделять данные на небольшие сегменты. Это значительно упростит работу и позволит избежать ошибок.
Задание 2 из 8
Что входит в категоризацию данных на этапе предобработки?
Объединение числовых значений в группы-диапазоны
Когда вы учились заполнять пропуски в данных, вы определяли тип значений в столбце: количественный или категориальный. Некоторые количественные значения можно объединить в группы, например значение возраста. Такие группы проще анализировать. Обратите внимание, что количественное значение возраста в этом случае станет категориальным. Поэтому такое объединение данных и называют категоризацией.
Задание 3 из 8
Сотрудники интернет-магазина ведут учёт жалоб на доставку:
в колонке order_id указан номер заказа;
в колонке type_message — тип жалобы;
в колонке type_id — идентификатор жалобы;
в колонке timestamp — время обращения.
Но так хранить информацию неудобно. Как можно оптимизировать данные?
order_id type_message type_id timestamp
0 87645733 Товар не доставили в срок 2 2021-03-28 13:58:24
1 95736342 Прислали не тот товар 3 2021-03-08 14:11:59
2 93746625 Нарушена упаковка товара 5 2021-03-03 18:52:34
3 96573547 Нарушена упаковка товара 5 2021-03-16 19:18:21
4 97365575 Прислали товар с браком 4 2021-03-13 11:43:14
5 95475838 Товар не доставили в срок 2 2021-03-29 17:32:17
6 98485626 Прислали не тот товар 3 2021-04-08 12:37:15
7 83547487 Товар не доставили 1 2021-04-06 20:15:20
8 84646255 Товар не доставили 1 2021-04-09 17:30:43
9 94756363 Прислали товар с браком 4 2021-04-16 21:26:16
Создать отдельную таблицу с колонками type_id и type_message.
В колонках type_id и type_message — категориальные значения. Поэтому нет смысла объединять их в группы на основе чисел. От колонки type_message в основной таблице лучше избавиться, но сразу удалять её не стоит — как тогда разобраться, на что жалуются пользователи? Тип жалобы с идентификатором можно сохранить в отдельной таблице.
Задание 4 из 8
В этом датафрейме хранятся данные о разных электростанциях:
в колонке country указана страна, где находится электростанция;
в колонке name — название;
в колонке capacity_mw — мощность электростанции в мегаваттах.
country name capacity_mw
30 Algeria Kahrama IPP 345.0
31 Algeria Koudiet Eddraouch 1200.0
32 Algeria Labreg 396.0
33 Algeria Marsat 840.0
34 Algeria Marsat TG 184.0
35 Algeria Msila 1 980.0
36 Algeria Ras Djinet 672.0
37 Algeria Ravin Blanc 73.0
38 Algeria Relizane 465.0
39 Algeria SKB IPP 484.0
40 Algeria SKS IPP SNC Lavalin 815.0
Такие данные сложно сравнивать, поэтому электростанции можно объединить в группы в зависимости от их мощности. Как выделить такие группы?
Правильный ответ
Написать функцию, которая возвращает категорию по значению мощности.
Группировка поможет сравнить данные, главное — выбрать признак, по которому группировать. Колонка name для этого не подойдёт — все значения в ней уникальны. В колонке capacity_mw много единичных значений, но их удобно объединять в категории. Так можно выделить группы по значению мощности.
Задание 5 из 8
В датафрейм добавили новые колонки — в них содержится информация о том, сколько энергии произвела каждая электростанция в 2013 и 2014 годах. Какие функции посчитают среднее количество произведённой энергии?
country name generation_2013 generation_2014
30 Algeria Ighil Emda 33.0 27.4
31 Algeria Kahrama IPP 1440.4 1059.5
32 Algeria Labreg 393.0 394.9
33 Algeria Marsat 839.1 810.5
34 Algeria Marsat TG 181.9 180.4
35 Algeria Naâma 30.9 29.6
36 Algeria Ras Djinet 1560.5 1200.2
37 Algeria Ras el Oued 19.0 20.0
38 Algeria Ravin Blanc 73.2 70.6
39 Algeria Reggane 5.0 5.0
40 Algeria SKS IPP 480.7 450.1
Правильный ответ
def avg_generation(row):
avg = (row['generation_2013'] + row['generation_2014']) / 2
return avg
Правильный ответ
def avg_generation(row):
generation2013 = row['generation_2013']
generation2014 = row['generation_2014']
avg = (generation2013 + generation2014) / 2
return avg
Нужно написать функцию, которая учтёт две колонки датафрейма. Тогда в качестве аргумента функции передают всю строку целиком — её можно обозначить переменной row. Не забудьте указать эту переменную в параметре функции, иначе произойдёт ошибка.
Задание 6 из 8
Датафрейм clients_df хранит информацию о покупателях магазина. Чтобы выделить категории в данных, написали функцию categorize_age(). К каким элементам датафрейма clients_df применили эту функцию?
clients_df['age_category'] = clients_df.apply(categorize_age, axis=1)
Правильный ответ
К каждой строке датафрейма
Таблица в pandas — двумерный объект. У него есть две координаты: строка и столбец. Перебирать элементы в цикле не стоит — это неоптимальный путь, который займёт много времени. Выбором строки или столбца управляет параметр axis. Чтобы применить метод к строкам, в аргументе указывают axis со значением 1.
Задание 7 из 8
К каким элементам датафрейма применили функцию calc_average()?
average_df = clients_df.apply(calc_average, axis=0)
Правильный ответ
К каждому столбцу датафрейма
Параметр axis позволит применить код к нужным элементам: строкам или столбцам. Он вам ещё не раз пригодится, поэтому запомните, как его применять.
Задание 8 из 8
К какому элементу датафрейма применили функцию categorize_age()?
clients_df['age_category'] = clients_df['age'].apply(categorize_age)
Правильный ответ
К столбцу age
Можно по-разному написать функцию, которая выделяет возрастные группы. Функция может принимать на вход строки, тогда её вызывают для всего датафрейма построчно с помощью аргумента axis=1. Если функция принимает на вход значения age, то и применять её можно к отдельному столбцу.
Следующая тема: ПД. Системное и критическое мышление в работе аналитика
Вернуться к разделу: "Предобработка данных"
Вернуться в оглавление: Я.Практикум