Следующая тема: ИАД. Проектная работа
Вернуться в раздел: Исследовательский анализ данных
Вернуться в оглавление: Я.Практикум
Введение
Вы сделали всё, чтобы получить список тех сетей АЗС, с которыми Яндекс.Навигатору выгоднее начать сотрудничество.
Чему вы научитесь
- строить столбчатые графики и круговые диаграммы;
- выборочно изменять значения в списке методом where();
- писать цикл, который строит сразу много гистограмм.
Вам предстоит:
- объединить мелкие сети в группу «Другие»;
- построить гистограммы продолжительности заправки в каждой сети;
- получить финальный список крупных сетей АЗС.
Сколько времени это займёт
2 урока по 30 минут.
Постановка задачи
Перепроверьте реалистичность данных, чтобы подготовить финальный отчёт для руководства.
Укрупняем группы
Перед тем как делать выводы, надо перепроверить реалистичность данных. Отчёт выглядит так. Напомним, что достоверная типичная продолжительность заправки указана в median_time
:
print(final_stat)
time_spent good_time_spent median_time stations
name
Агератум 337.802721 309.0 308.50 3.0
Амарант 132.760012 187.5 169.00 5.0
Аммобиум 256.708042 180.5 178.75 4.0
Арктотис 73.879984 185.0 190.50 3.0
Астильба 376.143149 423.0 435.50 4.0
Бальзамин 134.508411 158.0 158.00 1.0
Бархатцы 145.300328 181.5 181.50 1.0
Бегония 163.200647 195.0 190.00 3.0
Фасоль 133.631957 NaN NaN NaN
Функия 302.494737 171.5 171.50 1.0
Хризантема 195.738710 188.0 188.25 2.0
Данные бывают разной природы, и для всяких наблюдений есть подходящие графики. Например, если нужно продемонстрировать доли от 100%, строят круговую диаграмму. В методе plot()
задают тип: kind='pie'
(от англ. pie chart — «диаграмма-пирог», похожа на круглый пирог, поделённый на дольки). Столбец с данными для диаграммы указывают в параметре y
метода plot()
.
На графике же нужно отразить факт, что каждая сеть АЗС — это отдельный объект, никак не связанный с ближайшими соседями в рейтинге. Для анализа таких наблюдений лучше подходит столбчатый график, он подчёркивает изолированность данных.
Столбчатый график строят методом plot()
, параметром передают тип графика: kind='bar'
(от англ. bar chart — «столбчатая диаграмма»). Однако график может быть не наглядным, ведь сетей много и лишь несколько из них — крупные. Лучше объединить все мелкие сети и отрисовать как один столбец.
Данные по малым сетям можно просто отбросить, но полезнее сгруппировать их в «сеть» под названием «Другие», то есть сделать выборочное переименование. Так вы и данные не потеряете, и покажете отличие малых сетей от старших братьев.
Выборочно изменяют значения методом where()
(пер. «где»). Ему передают два параметра: условие для булева массива и новые значения. Если условие равно True
, соответствующее ему значение не изменится, а если False
— поменяется на второй параметр метода. Возьмём, к примеру, список покупок:
shopping = pd.Series(['молоко', 'хамон', 'хлеб', 'картошка', 'огурцы'])
print(shopping) # список того, что нужно купить
print()
print(
shopping.where(shopping != 'хамон', 'обойдусь')
) # тот же список после проверки баланса карты
0 молоко
1 хамон
2 хлеб
3 картошка
4 огурцы
dtype: object
0 молоко
1 обойдусь
2 хлеб
3 картошка
4 огурцы
dtype: object
Мы написали для булева массива условие, проверяющее, не оказался ли элемент списка хамоном: shopping != 'хамон'
. Получили массив вида: [True, False, True, True, True]
. Строки со значением True не изменились, а хамон поменялся на значение 'обойдусь'.
При объединении данных случается отбрасывать пустые значения. Вы знакомы с методом dropna()
из вводного курса. Напомним, что он удаляет пропущенные значения из датафрейма. Когда нужно удалить строки с пропусками в конкретном столбце, его название передают параметру subset
метода dropna()
:
dataframe.dropna(subset=['column_name'])
Задача 1/7
Итак, вы выявили аномалии, отфильтровали данные, создали показатели для типичного времени заезда и изучили влияние большого числа аномальных заездов на эти показатели. Проверьте всё ещё разок, прежде чем делиться результатами. Для начала визуализируйте распределение лучших показателей заездов с типичной продолжительностью по сетям АЗС.
Выполните следующие шаги, используя чистый код. Не вводите вспомогательные переменные — помните о бритве Оккама:
- Упорядочьте таблицу
final_stat
по возрастанию лучших показателей из столбцаmedian_time
.median_time
— это медиана для распределения медианной продолжительности заправки по АЗС в каждой сети. - Постройте столбчатый график по значениям median_time. Задайте размер графика 10х5 дюймов.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
#print(final_stat)
(
final_stat
.sort_values(by='median_time')
.plot(y='median_time', kind='bar', figsize=(10, 5))
)
Результат
Хорошо видно, как различаются сети. Есть группа сетей, где заправляют быстрее 200 секунд. И группа более медлительных конкурентов: с диапазоном 200–350 секунд. Утешительный приз в конкурсе «скорость без границ» получает сеть «Астильба», где ухитряются заправлять дольше 400 секунд. Наверное, там есть подкачка шин, телевизор и вкусный кофе.
Задача 2/7
Предыдущий столбчатый график отображал шесть сетей АЗС без данных: «Годецию», «Лобулярию», «Нарцисс», «Обриету» и «Фасоль». Это значения NaN
в final_stat
, которые появились, потому что вы исключили непопулярные АЗС. Таблица final_stat
была создана объединением таблиц stat
(включает все АЗС) и good_stat2
(исключает АЗС с малым числом заездов). Так как в join()
по умолчанию левое соединение, индексы из final_stat
будут идентичны индексам из stat. Поэтому любой индекс из таблицы stat
, которого нет в таблице good_stat2
, после объединения получит значение NaN
. Наведите порядок в графике, удалив значения NaN
.
Выполните следующие шаги, помня о бритве Оккама:
- Отбросьте значения
NaN
в столбцеmedian_time
таблицыfinal_stat
. - Упорядочьте таблицу
final_stat
по возрастанию значений в столбцеmedian_time
. - Постройте столбчатый график
median_time
. Задайте размер графика 10х5 дюймов. Добавьте линии сетки.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
(
final_stat
.dropna(subset=['median_time'])
.sort_values(by='median_time')
.plot(y='median_time', kind='bar', figsize=(10, 5), grid=True)
)
Результат
Теперь ничего лишнего. Хорошо видна условная граница, разделяющая «быстрые» и «медленные» сети.
Что даёт этот факт? Клиентам «быстрых» АЗС оплата заправки через Яндекс.Навигатор вряд ли принесёт много пользы. Они и без этого и заправляются, и платят быстро. А вот клиентам АЗС, чьи процессы отлажены не так хорошо, Яндекс.Навигатор поможет проводить меньше времени на станции. А сами АЗС могли бы значительно улучшить показатели.
От аналитика ждут, что он укажет, кого избрать ключевым партнёром. А какая «медленная» сеть всех крупнее, всех масштабнее и виднее?
Задача 3/7
До этого момента вы фильтровали данные по количеству заездов на одну АЗС и по длительности заправки. Но стоит учесть ещё одну переменную: число АЗС внутри сетей. С точки зрения маркетинга интересны и сети с большей продолжительностью заправки, и сети, в которых много АЗС. Значит, нужно исключить те сети, в которых заправочных станций мало. А для начала посмотрите, как число заправочных станций распределяется по сетям.
Используя данные из таблицы final_stat
, постройте гистограмму, отображающую число АЗС внутри сетей. Поделите значения на 100 корзин.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
final_stat.hist(column='stations', bins=100)
Результат
Сеть — это звучит гордо! Особенно когда в сети всего одна АЗС. У нас таких «гордых» целых 15. Есть и такие, у которых меньше 10 станций, и отдельные большие сети. Крупные рыбы интересуют нас в первую очередь, а мальков нужно собрать в одной группе.
Задача 4/7
Так как с точки зрения маркетинга небольшие сети неважны, создайте новую переменную с данными только крупных сетей.
Выполните следующие шаги:
- Создайте переменную
big_nets_stat
и поместите в неё строки из таблицыfinal_stat
, в которых значение переменнойstations
больше 10. - Выведите новую переменную на экран и изучите результат.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
#print(final_stat.head())
big_nets_stat = final_stat.query('stations > 10')
print(big_nets_stat)
Результат
time_spent good_time_spent median_time stations
name
Василёк 268.849897 264.0 252.00 103.0
Гейхера 167.445947 204.0 192.00 12.0
Календула 207.357323 254.0 240.00 36.0
Колокольчик 119.131564 161.0 161.00 21.0
Люпин 235.440937 186.0 200.00 13.0
Мальва 136.562234 182.0 177.75 22.0
Немезия 186.535207 226.0 227.50 21.0
Роза 260.877217 315.0 350.00 18.0
Вы получили «большую восьмёрку». Это лучше, чем «двадцатка» — договориться проще.
Задача 5/7
Теперь можно разделить все сети на две группы: «Большая восьмёрка» и «Другие». Вторая группа будет восприниматься как одна большая сеть.
Лучшие показатели средней продолжительности заправки содержатся в таблице good_stat2
и рассчитываются по данным station_stat_full
(просмотрите код, чтобы вспомнить эти вычисления). Повторите вычисления, но вместо того, чтобы группировать данные по столбцу name
, сгруппируйте данные по новому столбцу, содержащему категорию Другие
. Чтобы создать этот столбец в таблице station_stat_full
, примените метод where()
для сравнения столбца name
в station_stat_full
с индексами big_nets_stat
.
Выполните следующие шаги:
- Добавьте в таблицу
station_stat_full
новый столбецgroup_name
. - Поместите в столбец
group_name
значения столбцаname
, если сеть присутствует вbig_nets_stat
. Если столбецname
отсутствует, поместите вgroup_name
значения изДругие
. - Выведите на экран первые пять строк таблицы
station_stat_full
.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
big_nets_stat = final_stat.query('stations > 10')
station_stat_full['group_name'] = station_stat_full['name'].where(station_stat_full['name'].isin(big_nets_stat.index), 'Другие')
print(station_stat_full.head())
Результат
name count time_spent group_name
id
00ca1b70 Вероника 131 166.0 Другие
0178ce70 Василёк 164 234.5 Василёк
01abf4e9 Гацания 30 181.5 Другие
030a9067 Колокольчик 228 135.5 Колокольчик
03740f2d Василёк 157 289.0 Василёк
Да, иногда аналитические выводы похожи на сюжеты бульварных романов: «Василёк остался прежним, а Вероника стала другой». Не обращайте внимания, вы-то всё правильно сделали.
Задача 6/7
У вас есть категория Другие
с небольшими сетями. Теперь повторите анализ, в процессе которого создали good_stat2
, но в этот раз сгруппируйте данные по group_name
.
Выполните следующие шаги:
- Создайте переменную
stat_grouped
, которая повторяет вычисленияgood_stat2
, но группирует поgroup_name
. - Переименуйте столбцы в
stat_grouped
наtime_spent
иcount
. - Упорядочьте
stat_grouped
по возрастанию значений столбцаtime_spent
. Убедитесь, что изменение постоянное, а не временное. - Выведите на экран
stat_grouped
.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
big_nets_stat = final_stat.query('stations > 10')
station_stat_full['group_name'] = (
station_stat_full['name']
.where(station_stat_full['name'].isin(big_nets_stat.index), 'Другие')
)
stat_grouped = (
station_stat_full
.query('count > 30')
.pivot_table(index='group_name', values='time_spent', aggfunc=['median', 'count'])
)
stat_grouped.columns = ['time_spent', 'count']
stat_grouped = stat_grouped.sort_values(by='time_spent')
print(stat_grouped)
Результат
time_spent count
group_name
Колокольчик 161.00 21
Мальва 177.75 22
Гейхера 192.00 12
Люпин 200.00 13
Другие 208.75 104
Немезия 227.50 21
Календула 240.00 36
Василёк 252.00 103
Роза 350.00 18
Медленнее всего обслуживают в «Розе». «Василёк», «Календула» и «Немезия» тоже не могут особо похвастаться. Надо что-то делать с уровнем сервиса. Если долго смотреть на машину с пустым бензобаком, можно увидеть, как она уезжает заправляться к конкурентам!
Задача 7/7
Теперь у вас есть таблица с лучшими показателями типичной продолжительности заезда для крупных сетей АЗС. Дальше уже команде маркетинга решать, сколько сил тратить на то, чтобы завоевать «Розу» (типичная продолжительность заезда 350 секунд, 18 заправочных станций) или «Василька» (типичная продолжительность заезда 252 секунды, 103 заправочные станции).
В следующем уроке вы сделаете финальную проверку, а пока визуализируйте относительную величину этих сетей с точки зрения количества заправочных станций.
По данным stat_grouped
постройте круговую диаграмму с числом АЗС в каждой сети. Задайте её размер 8x8 дюймов.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
big_nets_stat = final_stat.query('stations > 10')
station_stat_full['group_name'] = (
station_stat_full['name']
.where(station_stat_full['name'].isin(big_nets_stat.index), 'Другие')
)
stat_grouped = (
station_stat_full
.query('count > 30')
.pivot_table(index='group_name', values='time_spent', aggfunc=['median', 'count'])
)
stat_grouped.columns = ['time_spent', 'count']
stat_grouped.plot(y='count', kind='pie', figsize=(8,8))
Результат
Вы получили финальный список наиболее интересных АЗС: «Роза». Самая медленная среди крупных сетей. «Василёк». Вторая по медлительности, но очень крупная по размеру сеть АЗС. «Календула». Близка по времени к «Васильку» и вторая по величине. «Немезия». Последняя из медленных и сравнительно крупных АЗС.
Разбитые по группам данные
Вы получили список, готовый лечь на стол начальству. Страшновато, если честно. Давайте ещё разок перепроверим. Вдруг на среднюю продолжительность заправки у ваших избранников повлияли аномально короткие заезды? Нужно посмотреть на гистограммы отдельно для каждой сети.
Придётся перебрать все сети и построить их гистограммы. Звучит трудоёмко, но это можно автоматизировать! Например, в цикле. Сначала создать столбец group_name
и перебрать названия групп так: good_data['group_name'].unique()
. Затем получить срез из good_data
методом query()
и построить гистограмму по отфильтрованным данным. Однако в pandas
есть метод groupby()
, автоматически перебирающий уникальные значения. Если передать ему столбец, то он вернёт последовательность пар: значение столбца — срез данных с этим значением.
Посмотрите, как это работает. Напишем цикл, возвращающий частоту появления имени разработчика в IT-отделе:
IT_names = pd.DataFrame(
{
'name': ['Саша', 'Саша', 'Саша', 'Игорь', 'Игорь', 'Максим', 'Максим'],
'surname': [
'Иванов',
'Попов',
'Сидоров',
'Смирнов',
'Крылов',
'Курепов',
'Максимов',
],
}
)
print(IT_names)
print()
for developer_name, developer_data in IT_names.groupby('name'):
print(
'Имя {} встречается {} раза'.format(
developer_name, len(developer_data)
)
)
name surname
0 Саша Иванов
1 Саша Попов
2 Саша Сидоров
3 Игорь Смирнов
4 Игорь Крылов
5 Максим Курепов
6 Максим Максимов
Имя Игорь встречается 2 раза
Имя Максим встречается 2 раза
Имя Саша встречается 3 раза
Сгруппировали данные из датафрейма IT_names
по совпадениям столбца name
. На каждом шаге мы получали имя разработчика (developer_name
) и срез датафрейма по этому имени (developer_data
). После чего нашли количество элементов каждой «именной» выборки.
Метод groupby()
позволяет сразу производить простые вычисления над элементами группы. Например, применив метод count()
, можно добиться аналогичного результата. Получим список имён с их частотой:
print(IT_names.groupby('name').count())
name surname
Игорь 2
Максим 2
Саша 3
Результат работы функции, применённой к groupby()
, становится аналогом pivot_table()
. Однако нас интересует перебор значений в цикле. Постройте гистограммы для каждой из сетей.
Задача 1/3
Напоследок посмотрите, как продолжительность заездов распределяется по девяти сетям («Большая восьмёрка» и «Другие»). Загвоздка может быть вот в чём: если продолжительность сильно различается, то сравнивать показатели разных сетей будет неправильно.
Например, если в одной сети больше АЗС с продолжительностью заездов по 60–70 секунд, чем в других, это может понижать медианное значение. Чтобы проверить, не происходит ли такое, сгруппируйте данные из good_data
по group_name
и постройте гистограммы. Первым делом создайте столбец для группировки.
Выполните следующие шаги:
- Создайте столбец
group_name
в таблицеgood_data
так же, как делали раньше вstation_stat_full
. - Выведите на экран первые 5 строк good_data, чтобы проверить работу нового столбца.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
big_nets_stat = final_stat.query('stations > 10')
station_stat_full['group_name'] = (
station_stat_full['name']
.where(station_stat_full['name'].isin(big_nets_stat.index), 'Другие')
)
stat_grouped = (
station_stat_full
.query('count > 30')
.pivot_table(index='group_name', values='time_spent', aggfunc=['median', 'count'])
)
stat_grouped.columns = ['time_spent', 'count']
good_data['group_name'] = (
good_data['name']
.where(good_data['name'].isin(big_nets_stat.index), 'Другие')
)
print(good_data.head())
Результат
date_time id time_spent ... too_fast too_slow group_name
0 20180406T165358 76144fb2 98.0 ... False False Василёк
2 20180403T172824 76144fb2 220.0 ... False False Василёк
6 20180402T082321 76144fb2 555.0 ... False False Василёк
7 20180407T003408 76144fb2 286.0 ... False False Василёк
9 20180405T131939 76144fb2 248.0 ... False False Василёк
[5 rows x 7 columns]
Вы создали group_name, теперь с ним можно проделывать разное волшебство. Масса возможностей хранить одно в другом:
group_name в group_data,
group_data в цикле for - in,
игла в яйце,
яйцо в утке,
утка в зайце,
заяц в шоке.
Задача 3/3
Теперь, когда есть столбец group_name
, сгруппируйте good_data
и постройте гистограмму, чтобы увидеть, как распределяется продолжительность заездов в каждой сети.
Выполните следующие шаги:
- Сгруппируйте
good_data
поgroup_name
, используя циклfor
. Используйте в цикле переменныеname
иgroup_data
. - В каждой итерации
group_data
вызывайте методhist()
, чтобы построить гистограмму по значениямtime_spent
на 50 корзин.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
big_nets_stat = final_stat.query('stations > 10')
station_stat_full['group_name'] = (
station_stat_full['name']
.where(station_stat_full['name'].isin(big_nets_stat.index), 'Другие')
)
stat_grouped = (
station_stat_full
.query('count > 30')
.pivot_table(index='group_name', values='time_spent', aggfunc=['median', 'count'])
)
stat_grouped.columns = ['time_spent', 'count']
good_data['group_name'] = (
good_data['name']
.where(good_data['name'].isin(big_nets_stat.index), 'Другие')
)
for name,group_data in good_data.groupby('group_name'):
group_data.hist('time_spent', bins=50)
Результат
Ловкость автоматизации и никакого ручного перебора! Вы получили все гистограммы. Красота. Не хватает самой малости: названий сетей.
Задача 3/3
Построенные гистограммы будут полезными, только если вы знаете, к какой именно сети относятся данные. Повторите предыдущее задание, но теперь сделайте название сети заголовком гистограмм. Как вы помните, метод plot()
даёт больше вариантов для форматирования, чем метод hist()
.
Выполните следующие шаги:
- Напишите ещё один цикл
for
, как в предыдущем задании. Используйте в качестве переменных циклаname
иgroup_data
. - В каждой итерации
group_data
вызовите методplot()
, чтобы построить гистограмму по значениямtime_spent
. - Поместите в название каждой гистограммы переменную цикла
name
и разбейте гистограммы на 50 корзин.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
# считаем показатели сетей из показателей АЗС,
# а не усреднённые заезды на все АЗС сети
good_stat2 = (
station_stat_full
.query('count > 30')
.pivot_table(index='name', values='time_spent', aggfunc=['median', 'count'])
)
good_stat2.columns = ['median_time', 'stations']
final_stat = stat.join(good_stat2)
big_nets_stat = final_stat.query('stations > 10')
station_stat_full['group_name'] = (
station_stat_full['name']
.where(station_stat_full['name'].isin(big_nets_stat.index), 'Другие')
)
stat_grouped = (
station_stat_full
.query('count > 30')
.pivot_table(index='group_name', values='time_spent', aggfunc=['median', 'count'])
)
stat_grouped.columns = ['time_spent', 'count']
good_data['group_name'] = (
good_data['name']
.where(good_data['name'].isin(big_nets_stat.index), 'Другие')
)
for name,group_data in good_data.groupby('group_name'):
group_data.plot(
y = 'time_spent',
title = name,
kind='hist',
bins=50)
Результат
Распределение времени заправок по сетям выглядит логичным и более-менее одинаковым: больших скачков из-за коротких заездов не наблюдается. Можно не волноваться!
Заключение
В этой теме вы перепроверяли реалистичность данных: строили графики, переименовывали, группировали и строили новые графики.
У многих сетей АЗС явно обнаруживается аномальный пик на коротких заездах. Но в основном распределение имеет ожидаемую форму, а значит, медиана хорошо передаёт характерное время заправки.
Вы действительно выявили много долгих заправок в самых медленных сетях АЗС («Роза», «Календула», «Василёк», «Немезия»). Причём форма распределения достаточно плавная: не походит на явную аномалию в продолжительности заправки.
Данные действительно реалистичны — значит, курс пройден не зря :)
Заберите с собой
Чтобы ничего не забыть, скачайте шпаргалку и конспект темы.
Где ещё почитать про группировку данных
«Первичный анализ данных с Pandas», раздел «Группировка данных» — группировка с groupby.
«Аналитикам: большая шпаргалка по Pandas», раздел «Считаем производные метрики» — примеры groupby.
Следующая тема: ИАД. Проектная работа
Вернуться в раздел: Исследовательский анализ данных
Вернуться в оглавление: Я.Практикум