Следующая тема: ИАД. Работа с несколькими источниками данных
Вернуться в раздел: Исследовательский анализ данных
Вернуться в оглавление: Я.Практикум
1. Введение
2. Срезы данных и поиск авиабилетов
3. Срезы данных методом query()
6. "Слишком долгая" заправка - это сколько?
8. Графики
9. Группировка с pivot_table()
11. Диаграмма размаха в Python
13. Заключение
14. Проверочные задания. Изучение cрезов
Введение
Вы получили общее представление о данных — самое время исследовать их более тщательно.
Чему вы научитесь
- Получать срезы данных вручную и методом query().
- Округлять время и переводить его в другие часовые пояса.
- Строить графики методом plot().
- Составлять правильные баг-репорты.
Вам предстоит:
- Изучить АЗС со сверхдолгими заправками;
- Определить границу, после которой можно считать заправку «слишком долгой»;
- Узнать, чем ночные заезды отличаются от дневных;
- Найти аномально быстрые заправки.
Сколько времени это займёт
10 уроков от 5 до 30 минут.
Постановка задачи
Погрузитесь в детали: найдите аномально быстрые и сверхдолгие заправки. Узнайте, для каких АЗС они характерны.
Срезы данных и поиск авиабилетов
Пора перейти от общего представления о данных к деталям. Откуда взялись короткие заезды на АЗС? А очень долгие? Это характерно для одной АЗС или для всех? Отличаются ли чем-то АЗС со сверхдолгими заправками от других?
Чтобы ответить на эти вопросы, нужны не все данные, а лишь их часть — срез данных. Рассмотрим работу срезов на примере покупки авиабилетов.
Закройте глаза на секунду, помечтайте об отдыхе — и вот вы уже готовы разбирать пример с поиском авиабилетов.

В вашем распоряжении датафрейм с информацией об авиабилетах. Указаны пункты вылета From и прилёта To, наличие багажа Has_luggage, прямой ли рейс Is_Direct, цена билета туда-обратно Price, даты вылета Date_From и прилёта Date_To, название авиалиний Airline, время в пути в минутах туда Travel_time_from и обратно Travel_time_to.
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
Изучим только рейсы из Москвы. Нужен код, работающий как фильтр. Он берёт в будущий срез строки со значением Moscow в столбце From, а лишние строки со значением St. Petersburg не возьмёт.
Этим фильтром может стать булев массив, состоящий из True и False. Так, значениями True отметим, что хотим включить строку c Moscow в срез данных. А False обозначим ненужные для среза строки.
Составим булев массив и назовём его filter_list:
filter_list = [True, True, False, False, False]
Он сообщает, что последние три строки не понадобятся, а первые две надо взять. Чтобы автоматически собрать данные в срез, передадим массив как индекс датафрейма:
df[filter_list]
Однако размечать нужные строки вручную — занятие утомительное. Построим фильтр автоматически. Сперва соберём список, где строкам с Moscow соответствуют значения True, а остальным — False.
print(df['From'] == 'Moscow')
1 True
2 False
3 False
4 False
Name: From, dtype: bool
True:print(df[df['From'] == 'Moscow']) # передаём булев массив как индекс датафрейма
From To Is_Direct Has_luggage Price Date_From Date_To Airline \
0 Moscow Rome False True 21032 01.07.19 07.07.19 Belavia
1 Moscow Rome True False 19250 01.07.19 07.07.19 S7
Travel_time_from Travel_time_to
0 995 350
1 230 225
Отлично! Вы узнали, как задавать условия для получения срезов. Приём одинаково эффективен для датафреймов в 5, 10, 200 и 1000000 строк. Условием создания булева массива может выступать не только равенство, но и другие операции сравнения: !=, >, >=, <, <=.
Бюджет на отпуск даже у аналитиков ограничен. Выведем срез данных, где цена на билет меньше 21000 рублей:
df[df['Price'] < 21000]
Значения в столбцах можно сравнивать и с числами, и между собой. Бывает, что путь туда занимает не столько же времени, сколько путь обратно. Рассмотрим случаи, когда на дорогу назад Travel_time_to уходит больше времени, чем на дорогу вперёд Travel_time_from:
df[df['Travel_time_to'] > df['Travel_time_from']]
Travel_time_to более чем в 1.5 раза быстрее, чем дорога туда Travel_time_from. df[1.5 * df['Travel_time_to'] < df['Travel_time_from']]
Чтобы проверить наличие конкретных значений в столбце, вызовем метод isin(). Посмотрим, какие рейсы вылетают после 3 июля 2019:
# находим элементы столбца Date_From, равные 4 или 5 июля
df[df['Date_From'].isin(['04.07.2019', '05.07.2019'])]
Иногда нужно получить выборку, соответствующую сразу нескольким условиям, — для этого существуют логические операции pandas. Их синтаксис:
| Name | Описание | Синтаксис | Оператор |
|---|---|---|---|
| И | Результат выполнения логической операции True, только если оба условия — True | (df['Is_Direct']) & (df['Price'] < 21000) | & |
| ИЛИ | Результат выполнения — True, если хотя бы одно из условий — True | (df['Has_luggage']) | (df['Price'] < 20000) | | |
| НЕ | Результат выполнения — True, если условие — False | ((df['Is_Direct']) ~ (df['Has_luggage'])) | ~ |
Оператор ~ можно использовать и для проверки одного условия, например, чтобы отобрать билеты без багажа: ~(df['Has_luggage']).
Обратите внимание, что здесь условия указывают в скобках — в отличие от синтаксиса логических операций Python с or, and или not.
Задача №1/3
Выберите строки с выгодной ценой за авиабилет. Выгодными считаются те билеты, которые дешевле самого дорогого билета более чем в 1,5 раза.
Выведите на экран полученную выборку.
import pandas as pd
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
print(df[df['Price'].max()/1.5 > df['Price']]) # впишите нужное условие
From To ... Travel_time_from Travel_time_to
1 Moscow Rome ... 230 225
2 St. Petersburg Rome ... 605 720
3 St. Petersburg Barcelona ... 365 355
[3 rows x 10 columns]
А вам что милее: посетить «Камп Hоу» в Барселоне или бросить монетку в римский фонтан Треви?
Задача №2/3
Выберите строки, где значения столбца Travel_time_from больше или равны 365 или значения Travel_time_to меньше 250. Результат выведите на экран.
import pandas as pd
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
print(df[(df['Travel_time_from']>=365) | (df['Travel_time_to']<250)]) # впишите нужное условие
Результат
From To ... Travel_time_from Travel_time_to
0 Moscow Rome ... 995 350
1 Moscow Rome ... 230 225
2 St. Petersburg Rome ... 605 720
3 St. Petersburg Barcelona ... 365 355
[4 rows x 10 columns]
Здесь вам не привычные or, and и not. Условия указывают в скобках.
Задача 3/3
Выберите строки, где:
- полёт с пересадкой;
- прилёт до 8 июля (ни 9, ни 10, ни 11 июля).
Результат выведите на экран.
import pandas as pd
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
print(df[(df['Is_Direct']==0) & (df['Date_To']<'08.07.2019')]) # впишите нужное условие
Результат
From To Is_Direct ... Airline Travel_time_from Travel_time_to
0 Moscow Rome False ... Belavia 995 350
[1 rows x 10 columns]
Всё верно. Лишь одна строка удовлетворяет таким строгим условиям.
Срезы данных методом query()
В прошлом уроке вы делали срезы в 2 шага:
- Получали булев массив, соответствующий условиям.
- Делали срез по нему.
Это гибкий инструмент получения срезов, и владеть им полезно. Однако существует и более простой способ — метод query() (пер. «запрос»).
Необходимое условие для среза записывается в строке, которую передают как аргумент методу query(). А метод применяют к датафрейму. В результате получаем нужный срез.
import pandas as pd
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
print(df.query('To == "Barcelona"'))
From To Is_Direct Has_luggage Price Date_From \
3 St. Petersburg Barcelona False True 20168 03.07.2019
4 St. Petersburg Barcelona True False 31425 05.07.2019
Date_To Airline Travel_time_from Travel_time_to
3 09.07.2019 Swiss 365 355
4 11.07.2019 Rossiya 255 250
Условия, указанные в параметре query():
- Поддерживают разные операции сравнения:
!=,>,>=,<,<=. - Проверяют, входят ли конкретные значения в список, конструкцией:
Date_To in ["07.07.19", "09.07.2019"]. Если нужно узнать, нет ли в списке определённых значений, пишут так:Date_To not in ["07.07.19", "09.07.2019"]. - Работают с логическими операторами в привычном виде, где «или» —
or, «и» —and, «не» —not. Указывать условия в скобках необязательно. Без скобок операции выполняются в следующем порядке: сначалаnot, потомandи, наконец,or.
Обратите внимание, что значение "Barcelona" в коде задачи заключено в двойные кавычки. Это нужно, чтобы различать одинарные кавычки, оформляющие строку, и кавычки для элемента Barcelona.
Условия для среза данных можно объединять. Например, чтобы отыскать прямые рейсы или билеты с включённым в стоимость багажом — мало кому нравится летать со множеством пересадок, да ещё и без вещей.
print(df.query('Is_Direct == True or Has_luggage == True'))
Строки с какими индексами не будут выбраны в результате выполнения этого кода?
print(df.query('Is_Direct == True or Has_luggage == True'))
From To Is_Direct Has_luggage Price Date_From Date_To Airline Travel_time_from Travel_time_to
0 Moscow Rome False True 21032 01.07.19 07.07.19 Belavia 995 350
1 Moscow Rome True False 19250 01.07.19 07.07.19 S7 230 225
2 St. Petersburg Rome False False 19301 04.07.2019 10.07.2019 Finnair 605 720
3 St. Petersburg Barcelona False True 20168 04.07.2019 09.07.2019 Swiss 365 355
4 St. Petersburg Barcelona True False 31425 04.07.2019 11.07.2019 Rossiya 255 250
0
1
Правильный ответ
2
Верно! Все дороги ведут в Рим, но перелёт из Санкт-Петербурга с пересадкой и без багажа — не самая приятная из них.
3
4
А как насчёт слетать куда-нибудь вместе с командой Практикума? Правда, есть ограничение: вылететь мы сможем только из Москвы. Добавьте в query() новое условие. Порядок выполнения трёх и более условий можно регулировать скобками.
df.query('Travel_time_from < Travel_time_to.mean()')
query() можно включать внешние переменные (не из датафрейма). Когда упоминаете такую переменную, помечайте её знаком @:Задача 1/2
Выберите строки, где: Has_luggag равно False и Airline не равно ни S7, ни Rossiya. Напечатайте полученную выборку на экране.
import pandas as pd
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
# впишите условие создания нужной выборки
print(df.query('Has_luggage==False and Airline!="S7" and Airline!="Rossiya"'))
From To Is_Direct ... Airline Travel_time_from Travel_time_to
2 St. Petersburg Rome False ... Finnair 605 720
[1 rows x 10 columns]
Записать запрос в query() легко. Просто проговорите условие:
«Has_luggage равно False» — Has_luggage == False;
«и» — and;
«Airline не равно ни S7, ни Rossiya» — Airline not in ["S7", "Rossiya"].
Задача 2/2
Выберите строки, где Airline равно Belavia, S7 или Rossiya, при этом Travel_time_from меньше переменной под названием max_time. Напечатайте полученную выборку на экране.
import pandas as pd
df = pd.DataFrame(
{
'From': [
'Moscow',
'Moscow',
'St. Petersburg',
'St. Petersburg',
'St. Petersburg',
],
'To': ['Rome', 'Rome', 'Rome', 'Barcelona', 'Barcelona'],
'Is_Direct': [False, True, False, False, True],
'Has_luggage': [True, False, False, True, False],
'Price': [21032, 19250, 19301, 20168, 31425],
'Date_From': [
'01.07.19',
'01.07.19',
'04.07.2019',
'03.07.2019',
'05.07.2019',
],
'Date_To': [
'07.07.19',
'07.07.19',
'10.07.2019',
'09.07.2019',
'11.07.2019',
],
'Airline': ['Belavia', 'S7', 'Finnair', 'Swiss', 'Rossiya'],
'Travel_time_from': [995, 230, 605, 365, 255],
'Travel_time_to': [350, 225, 720, 355, 250],
}
)
max_time = 300
# впишите условие создания нужной выборки
print(df.query('(Travel_time_from < @max_time) and (Airline=="Belavia" or Airline=="S7" or Airline=="Rossiya") '))
Результат
From To ... Travel_time_from Travel_time_to
1 Moscow Rome ... 230 225
4 St. Petersburg Barcelona ... 255 250
[2 rows x 10 columns]
Вы уже умеете строить срезы данных целыми двумя способами. Не пора ли применить эту магию к данным по АЗС?
Есть несколько заездов со временем около 30 000 секунд (это около 8 часов). Крайне маловероятно, что водители заправляются так долго. Они повстречали свою любовь или случайно вырвали заправочный пистолет? А может, всё одновременно?

У вас есть всё необходимое, чтобы с этим разобраться.
Чтобы построить графики, обращаются к библиотеке matplotli (от англ. mathematical plotting library, «библиотека математических построений»). Точнее, к её модулю — pyplot:
import matplotlib.pyplot as plt
show(). Он позволяет посмотреть, как отличаются гистограммы с разным числом корзин:import matplotlib.pyplot as plt
data.hist(bins=10)
plt.show()
data.hist(bins=100)
plt.show()
Задача 1/3
Итак, нужно разобраться с аномалиями в выборке. Для начала найдите АЗС с самыми большими значениями в столбце time_spent.
Одной строкой кода отсортируйте таблицу по убыванию значений в столбце time_spent и выведите на экран первые 10 строк всей таблицы.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
print(data.sort_values(by='time_spent', ascending=False).head(10))
Результат
date_time id time_spent name
114797 20180402T055708 3c1e4c52 28925.0 Василёк
27147 20180406T080254 4b5f2af5 28519.0 Немезия
60547 20180408T000002 cf1ba8a5 28292.0 Василёк
19042 20180408T204208 5410e876 23696.0 Василёк
118597 20180408T165020 3c1e4c52 21184.0 Василёк
118058 20180402T111333 3c1e4c52 20359.0 Василёк
114406 20180408T083722 3c1e4c52 19886.0 Василёк
132164 20180405T160745 627ea5e3 19445.0 Левкой
281360 20180406T180459 d0c0928d 18614.0 Пион
165326 20180402T230204 3af3bb71 18569.0 Агератум
Во-первых, в столбце id разные значения, значит, долго заправляются не на одной-единственной АЗС. Во-вторых, АЗС в списке вообще из разных сетей.
Выделяется id == "3c1e4c52", он встречается несколько раз. Изучите эту АЗС детальнее.
Задача 2/3
Четыре из десяти самых долгих заездов произошли на станции под номером 3c1e4c52. Аналитик данных непременно спросит: «А как распределение времени, проведённого на этой АЗС, соотносится с распределением времени заездов в целом?» Нужно проверить. Для этого сделайте срез данных и извлеките всю информацию о станции 3c1e4c52.
- Сделайте срез
dataпо АЗС сid == "3c1e4c52"и сохраните результат в переменнуюsample. - Выведите на экран число заездов на эту АЗС.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
sample = data.query('id == "3c1e4c52"')
print(sample['date_time'].count())
5814
На АЗС под кодовым названием 3c1e4c52 заезжали не раз.
Задача №3/3
Нужно сравнить распределение времени пребывания на станции 3c1e4c52 с распределением времени пребывания на всех АЗС. Если они сильно различаются, возможно, станция 3c1e4c52 представляет собой статистический выброс.
- Методом
hist()постройте две гистограммы распределения значений в столбцеtime_spent: одну для объектаdata, вторую — дляsample. Не забудьте использоватьplt.show()после каждого вызоваhist(). Для обеих гистограмм задайте одинаковые аргументы:range— от 0 до 1500,bins— 100.
import pandas as pd
import matplotlib.pyplot as plt
data = pd.read_csv('/datasets/visits.csv', sep='\t')
sample = data.query('id == "3c1e4c52"')
data['time_spent'].hist(range=(0,1500), bins=100)
plt.show()
sample['time_spent'].hist(range=(0,1500), bins=100)
plt.show()