Следующая тема: ИАД. Изучение срезов данных

Вернуться в раздел: Исследовательский анализ данных

Вернуться в оглавление: Я.Практикум

1. Введение

2. Знакомство с задачей

3. Сводные таблицы для расчета среднего

4. Применяем сводные таблицы

5. Есть ли проблема в данных?

6. Базовая проверка данных

7. Гистограмма

8. Гистограмма для двух кубиков

9. Распределения

10. Диаграмма размаха

11. Диаграмма размаха в Python

12. Описание данных

13. Заключение

14. Проверочные задания. Первые графики и выводы

Знакомство с задачей

Как исследовать поведение водителей на заправках, если вы — Яндекс? Обратиться к статистике Яндекс.Навигатора: узнать, на какую именно заправку заезжал водитель и сколько времени там провёл.

Ваши коллеги из Навигатора собрали необходимые данные и прислали их в таком виде:

  • Зашифрованное наименование сети АЗС (столбец name): вместо брендов — названия растений.
  • Уникальный идентификатор конкретной АЗС (столбец id) — в сети их много.
  • Время заезда на АЗС (столбец date_time) в формате ISO: 20190405T165358 означает, что водитель прибыл на заправку 5 апреля 2019 года в 16 часов 53 минуты 58 секунд по UTC.
  • Проведённое на АЗС время (столбец time_spent) в секундах.

Нужно ответить на вопрос, сколько в среднем времени тратят водители на заправку в каждой из сетей АЗС. image Выведем первые строки таблицы методом head():

import pandas as pd

data = pd.read_csv('/datasets/visits.csv')

print(data.head())

date_time\tid\ttime_spent\tname

0 20190405T165358\t76144fb2\t98.0\tВасилёк

1 20190403T173913\t76144fb2\t15.0\tВасилёк

2 20190402T172824\t76144fb2\t220.0\tВасилёк

3 20190406T070441\t76144fb2\t19.0\tВасилёк

4 20190403T132049\t76144fb2\t14.0\tВасилёк

Вот и первая проблема. Данные склеились в одну строку вместо того, чтобы разбиться по колонкам. Это произошло из-за разделителей в формате csv. Напомним, что csv — это Comma-Separated Values, или значения, разделённые запятыми. Действительно, в прошлых уроках вы работали с таблицами, где строку на колонки делили запятые. Однако вместо них могут быть точки с запятой, знаки табуляции или другие символы. Могут вносить путаницу и десятичные дроби, записанные с запятой. Какими символами разделять колонки и дроби, указывают в аргументах функции read_csv(). За разделитель колонок отвечает параметр sep (от англ. separate — «отделять, разделять»), а дробей — параметр decimal (пер. «десятичная дробь»):

 file = pd.read_csv('file.csv', sep=';', decimal=',') 

6 ноября 1974 года пуэрториканская обсерватория Аресибо отправила в космос радиосигнал, вошедший в историю под названием «Послание Аресибо».

Это адресованная инопланетянам информация, записанная двоичным кодом: числа от 1 до 10; химические элементы; строение и форма ДНК; количество людей на Земле, их средний рост и эскиз устройства человека; описание Солнечной системы; сведения об обсерватории Аресибо. Фрагмент послания выглядит так: image1 Само послание — последовательность нулей и единиц. Для расшифровки нужно разбить её на 73 строки по 23 символа. Вызовем функцию read_csv() — обратите внимание, что ей можно «скормить» файл в формате txt — и прочитаем послание Аресибо:

arecibo = pd.read_csv('arecibo.txt')
print(arecibo.head())

         date_time        id  time_spent     name
0  20180406T165358  76144fb2        98.0  Василёк
1  20180404T173913  76144fb2        15.0  Василёк
2  20180403T172824  76144fb2       220.0  Василёк
3  20180407T070441  76144fb2        19.0  Василёк
4  20180404T132049  76144fb2        14.0  Василёк

Когда что-то не клеится — это обычно нехорошо. Когда данные не склеиваются в одну строку — это, наоборот, здорово.

Сводные таблицы для расчёта среднего

Нужно узнать, сколько времени в среднем водители тратят на заправку в каждой сети АЗС. В каждой строке датафрейма есть название сети и время, проведённое на АЗС. Занимаясь предобработкой данных, вы применяли pivot_table() — метод для построения сводных таблиц.

Напомним его параметры:

  • index — столбец, значения которого становятся названиями строк (индексом);
  • columns — столбец, значения которого становятся названиями столбцов;
  • values — значения, по которым вы хотите увидеть сводную таблицу;
  • aggfunc — функция, применяемая к значениям.

Прежде значением aggfunc вы указывали sum, то есть складывали элементы столбца. Если параметр aggfunc не указывать, то по умолчанию метод pivot_table() рассчитает среднее арифметическое значений, указанных в параметре values.

Чтобы начать работать с данными о заправках, надо сначала научиться строить сводные таблицы. Для этого у вас будет новый набор данных — с информацией о матчах Национальной хоккейной лиги.

Нужно проанализировать среднюю посещаемость домашних матчей команд Национальной хоккейной лиги, начиная с сезона 2000–2001 гг. и заканчивая сезоном 2017–2018 гг. Посмотрим на первые пять строк таблицы, где для каждой команды указано количество домашних матчей и их суммарная посещаемость home_att:

nhl_att = pd.read_excel('nhl_attendance.xlsx')
print(nhl_att.head()) 

season    team    home_games    home_att
0    2017-2018    Chicago    41    887794
1    2017-2018    Montreal    41    873283
2    2017-2018    Philadelphia    41    800214
3    2017-2018    Detroit    41    800115
4    2017-2018    Toronto    41    786677

seasonteamhome_gameshome_attavg_home_att
0 2017-2018 Chicago 41 887794 21653.512195
1 2017-2018 Montreal 41 873283 21299.585366
2 2017-2018 Philadelphia 41 800214 19517.414634
3 2017-2018 Detroit 41 800115 19515.000000
4 2017-2018 Toronto 41 786677
 
Посчитаем среднюю сезонную посещаемость матчей. Эта метрика поможет проанализировать, насколько успешен был тот или иной сезон:
nhl_pivot = nhl_att.pivot_table(index='season', values='avg_home_att') 
# группируем таблицу по сезонам, для каждого находим среднюю посещаемость  
print(nhl_pivot) 
 
season    avg_home_att
2000-01    16559.268467
2001-02    16758.941463
2002-03    16590.563415
2003-04    16549.755285
2005-06    16954.608943
2006-07    16960.798374
2007-08    17307.805691
2008-09    17476.049593
2009-10    17072.668333
2010-11    17122.738211
2011-12    17455.410569
2012-13    17720.623611
2013-14    17587.389431
2014-15    17502.595122
2015-16    17576.266667
2016-17    17500.631707
2017-18    17446.312352
 

             time_spent
name
Агератум     337.802721
Амарант      132.760012
Аммобиум     256.708042
Арктотис      73.879984
Астильба     376.143149
Бальзамин    134.508411
Бархатцы     145.300328
Бегония      163.200647
Белоцветник  100.818966

Есть ли проблемы в данных?

Таблицу, которую получили в прошлом уроке, нельзя показывать менеджерам: они всё равно не поверят!

У сетей АЗС «Нарцисс», «Арктотис» и «Малопа» среднее время заправки около 70 секунд. Неправдоподобно мало.

Как вы думаете, всё ли в порядке с данными?

Всё правильно посчитано, в результатах не может быть ошибки.

Правильный ответ Всё может быть. Нужно проверить и данные, и расчёты.

Надо искать ошибку в расчётах.

Наверное, нам прислали неправильные данные.

Доверяй, но проверяй. В следующем уроке начнём проверять данные.

import pandas as pd

data = pd.read_csv ('/datasets/visits.csv', sep='\t')
#количество заездов на АЗС 
total_visits = data['id'].count()
print('Количество заездов:', total_visits)
import pandas as pd

data = pd.read_csv ('/datasets/visits.csv', sep='\t')
#количество заездов на АЗС 
total_visits = data['id'].count()
#print('Количество заездов:', total_visits)
total_stations = len(data['id'].unique())
print('Количество АЗС:', total_stations)
import pandas as pd

data = pd.read_csv ('/datasets/visits.csv', sep='\t')
#количество заездов на АЗС 
total_visits = data['id'].count()
print('Количество заездов:', total_visits)
total_stations = len(data['id'].unique())
print('Количество АЗС:', total_stations)
print(data['date_time'].min(), data['date_time'].max())
import pandas as pd

data = pd.read_csv ('/datasets/visits.csv', sep='\t')
#количество заездов на АЗС 
total_visits = data['id'].count()
print('Количество заездов:', total_visits)
total_stations = len(data['id'].unique())
print('Количество АЗС:', total_stations)
print(data['date_time'].min(), data['date_time'].max())
total_days = round(((pd.to_datetime(data['date_time'].max(), format='%Y%m%dT%H%M%S') - pd.to_datetime(data['date_time'].min(), format='%Y%m%dT%H%M%S'))/pd.Timedelta("1s")/60/60/24))
station_visits_per_day = total_visits/total_stations/total_days
print ('Количество заездов на АЗС в сутки:', station_visits_per_day)
import pandas as pd

data = pd.read_csv ('/datasets/visits.csv', sep='\t')
#количество заездов на АЗС 
total_visits = data['id'].count()
print('Количество заездов:', total_visits)
total_stations = len(data['id'].unique())
print('Количество АЗС:', total_stations)
print(data['date_time'].min(), data['date_time'].max())
total_days = round(((pd.to_datetime(data['date_time'].max(), format='%Y%m%dT%H%M%S') - pd.to_datetime(data['date_time'].min(), format='%Y%m%dT%H%M%S'))/pd.Timedelta("1s")/60/60/24))
station_visits_per_day = total_visits/total_stations/total_days
print ('Количество заездов на АЗС в сутки:', station_visits_per_day)
print(data['name'].value_counts().head(10))

Количество заездов: 317104
Количество АЗС: 471
20180402T000008 20180408T235957
Количество заездов на АЗС в сутки: 96.17955717318775
Календула      85648
Василёк        79006
Георгина       34356
Немезия        20138
Колокольчик    18835
Мальва         17386
Гейхера        14125
Доротеантус     6312
Нарцисс         3640
Амарант         3221
Name: name, dtype: int64

Эксперты рынка АЗС подтвердили, что сети «Календула» и «Василёк» — крупнейшие игроки. Другие сети, по словам специалистов, также распределились правдоподобно.
Поздравляем! С объёмом данных всё в порядке. Нужно разбираться дальше.

simpsons = pd.read_csv('simpsons.csv')
print(simpsons.head()) 

     id  title  season  \
0  10                                  Homer's Night Out       1   
1  12                                 Krusty Gets Busted       1   
2  14                                   Bart Gets an "F"       2   
3  17  Two Cars in Every Garage and Three Eyes on Eve...       2   
4  19                               Dead Putting Society       2   

   number_in_season  imdb_rating  
0                10          7.4  
1                12          8.3  
2                 1          8.2  
3                 4          8.1  
4                 6          8.0 

Методом value_counts() можно вывести количество серий, получивших тот или иной рейтинг:

print(simpsons['imdb_rating'].value_counts()) 

7.3    48
7.0    42
7.1    42
6.9    40
7.2    39
6.7    29
8.2    29
7.7    27
8.0    25
6.6    23
6.8    21
6.5    18
8.3    18
7.5    18
8.1    18
...

Теперь гистограмма соответствует действительности. Что, если количество шаров с разными номерами изменится? Скажем, будет два шара под номером 8 и ни одной «семёрки»? 

pd.Series([6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16]).hist(bins=11)
pd.Series([6, 8, 8, 9, 10, 11, 12, 13, 14, 100]).hist(range = (6, 14))
pd.Series([4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6]).hist(range=(0, 10))

льтат im

Мощный узкий пик около 0 секунд. Широкий пик около 200 секунд. Очень мало значений после 1000 секунд, но отдельные выбросы были и до 30000 секунд. Как же интерпретировать эти данные?

import random 

Напишем функцию, возвращающую случайное число точек на верхней грани:

# от англ. dice — «кубик», 
#          roll — «бросок»
def dice_roll():
    score = random.randint(1, 6)  
    return score

print(dice_roll())
print(dice_roll())
print(dice_roll())

1
2
5

У нас пара кубиков. Значит, нужна функция, получающая количество очков от броска двух кубиков:

# от англ. double – «двойной», roll – «бросок», score – «очки»
def double_roll_score():
    first = dice_roll()
    second = dice_roll()
    score = first + second
    return score

print(double_roll_score())
print(double_roll_score())
print(double_roll_score())

4
8
7

Сделаем 1000 таких бросков и построим гистограмму полученных очков:

import pandas as pd

# Cоздаём пустой список. В него 
# попадут результаты экспериментов.
experiments = []
for i in range(1000):
    score = double_roll_score()
    # Напомним: метод append() добавляет новый
    # элемент score в конец списка experiments.
    experiments.append(score)

df_experiments = pd.DataFrame(experiments)
df_experiments.hist(bins=11, range=(2, 12))

Диаграмма размаха

Вспомните распределение времени на АЗС, которое получили в седьмом уроке.

data['time_spent'].hist(bins=100, range=(0, 1500));
print(data.describe())

count    3.0 # количество наблюдений в наборе данных
mean     2.0 # среднее арифметическое
std      1.0 # стандартное отклонение
min      1.0 # минимальное значение
25%      1.5 # первый квартиль
50%      2.0 # медиана, или второй квартиль
75%      2.5 # третий квартиль
max      3.0 # максимальное значение
dtype: float64 # тип данных

Мы оказались правы! Виктор в основном получал четвёрки и время от времени — тройки и пятёрки. Иван же знал предмет на отлично, внезапно превратился в двоечника, а «среднюю» четвёрку получал только раз. Это повод для отдельного исследования: что стряслось с Иваном? Он влюбился, попал в плохую компанию или просто заскучал?

Результат
          time_spent
count  317104.000000
mean      203.382294
std       395.754791
min         0.000000
25%        19.000000
50%       108.000000
75%       274.000000
max     28925.000000

Обратите внимание, что среднее почти в два раза больше медианы. Значит, в распределении есть длинный хвост с высокими значениями или несколько очень больших значений. Это влияет на среднее, но не на медиану.

Заключение

В этой теме вы узнали, что такое распределение, познакомились с базовой проверкой данных, научились строить гистограммы. Они понадобятся ещё не раз там, где исследование даст неожиданные результаты.

Вы сильно продвинулись в решении поставленной бизнесом задачи: сколько времени тратят водители на АЗС. Уже рассчитали среднюю продолжительность заправки методом pivot_table(), проверили распределение числа заездов по сетям станций и распечатали топ-10 с наибольшим заездом. Сколько всего! Но самое важное — научились проверять данные и расчёты на правдоподобность и находить неочевидные аномалии. В этом заслуга вас и распределений :-)

Раз добрались до аномалий, пора взять на вооружение срез данных. Что это такое — в следующей теме.

Заберите с собой

Чтобы ничего не забыть, скачайте шпаргалку и конспект темы.

Где ещё почитать про гистограммы, ящик с усами и описательную статистику

Гистограммы

Описательные статистики: среднее, медиана, стандартное отклонение, перцентили

Проверочные задания. Первые графики и выводы

Чтобы пройти тест нужно правильно ответить на 5 вопросов из 10.

Время на прохождение: 20 минут

Задание 1 из 10
У метода read_csv() есть параметр sep, в котором задают разделитель колонок датафрейма. Какой символ указать при чтении CSV-файла?


sep='|'

sep=','

sep='\t'

Правильный ответ
Выбор символа зависит от того, какой разделитель использован в данных.
Значения в CSV-файле чаще всего разделены запятыми. Но так бывает не всегда: разделителями могут быть также знаки '\t' или ';'. Если ваши данные при чтении склеились в одну строку — проверьте, какой символ использован в данных.


Задание 2 из 10
Нужно проанализировать данные Национальной баскетбольной ассоциации и построить несколько графиков. Для какой задачи подойдёт гистограмма?


Сравнить распределение значений роста у игроков разных десятилетий и проанализировать медиану, первый и третий квартили.

Правильный ответ
Визуализировать распределение очков команд-победителей и найти диапазон самых распространённых значений.

Нарисовать график, который отобразит количество игроков из разных стран, и сравнить количество игроков из Австралии и Канады.
Гистограмма поможет оценить форму распределения и частоту каждого очка в наборе данных. Самое высокое значение на гистограмме — самое распространённое. Но для визуализации медианы или общего количества значений по категориям гистограмма не подойдёт.


Задание 3 из 10
Нужно проанализировать данные Национальной баскетбольной ассоциации и построить несколько графиков. Для какой задачи подойдёт диаграмма размаха?


Правильный ответ
Отобразить распределение значений роста у игроков за последние десять лет и проанализировать медиану, первый и третий квартили.

Визуализировать распределение очков проигравших команд и найти диапазон самых распространённых значений.

Нарисовать график, который отобразит количество побед десяти самых успешных команд.
Для отображения медианы и квартилей строят специальный график — диаграмму размаха. На гистограмме можно попытаться найти медиану, но точного значения такой график не даст.


Задание 4 из 10
Соотнесите номер на графике с тем, что он обозначает.
image
1
Медиана
2
Пределы нормальных значений
3
Выбросы
4
Межквартильный размах
Диаграмма размаха — непростой график. Нужно знать, как такой график устроен, чтобы интерпретировать результат. Диаграмму размаха ещё иногда называют «ящик с усами». На графике действительно есть «ящик»: он ограничен первым и третьим квартилями и отображает межквартильный размах. «Ящик» раздёлен линией — это медиана. «Усами» на графике отмечены пределы нормальных значений. За пределами «усов» находятся выбросы, изображённые точками.


Задание 5 из 10
Какое значение называют вторым квартилем (Q2)?


Самое частотное значение в данных.

Число, которое отделяет первую четверть выборки.

Число, которое отделяет последнюю четверть выборки.

Правильный ответ
Число, которое разделяет выборку пополам.
Квартили разделяют данные на четверти. Первый квартиль отделяет первую четверть, второй — две четверти или половину. Второй квартиль выборки — это медиана.


Задание 6 из 10
Чем является число 3 в объекте data? Возможно несколько ответов. 
data = pd.DataFrame([1, 2, 3, 4, 5]) 


Правильный ответ
Медианным значением

Тоже правильный ответ
Средним значением

Стандартным отклонением

Первым квартилем
Медиана и среднее в этой выборке совпадают, но так бывает редко. Напомним, что стандартное отклонение характеризует разброс значений. В объекте data значения отличаются от среднего на 1.58. Получить стандартное отклонение и другие числовые характеристики выборки можно методом describe().


Задание 7 из 10
Выберите верные утверждения.


Правильный ответ
Если стандартное отклонение высокое, в данных могут быть выбросы.

В первом датасете значение среднего меньше, чем во втором. Стандартное отклонение в первом датасете тоже будет меньше.

В двух датасетах одинаковое среднее, но в первом 100 наблюдений, а во втором — 1000. В первом датасете стандартное отклонение будет меньше.

Правильный ответ
Если в данных все значения одинаковые, стандартное отклонение будет равно нулю.
Стандартное отклонение не зависит от среднего или объёма выборки. Сравните: в списке [3, 5, 7] среднее меньше, чем в списке [7, 7, 7]. Но стандартное отклонение первого списка больше. Другой пример: в списке [3, 5, 7] меньше значений, чем в списке [5, 5, 5, 5]. Но значения в первом списке сильнее отличаются от среднего.


Задание 8 из 10
Колонка age в датафрейме data хранит количественные значения. Нужно построить гистограмму, которая отобразит распределение значений в колонке age в диапазоне от 18 до 30. Какой код построит такую гистограмму?


Правильный ответ
data.hist('age', range=(18, 30))

data.hist('age', bins=(18, 30))

data.hist('age', bins=18, range=30)

data.hist('age', (18, 30))
Нужные значения диапазона передают в аргументе range. Параметр bins управляет «корзинами» — количеством областей, на которые разделятся данные. Если передать значения диапазона в аргументе, не указав bins или range, Python интерпретирует их как позиционный аргумент и передаст второму аргументу метода. Из документации следует, что это будет параметр by, который управляет группировкой.


Задание 9 из 10
Выберите метод, который построит диаграмму размаха.


hist()

Правильный ответ
boxplot()

plot()

whiskerplot()
Диаграмму размаха иногда называют «ящиком с усами» (англ. box and whisker plot) и строят методом boxplot(). Жаль, нет метода whiskerplot(): «усатый график» довольно говорящее название.


Задание 10 из 10
Выберите код, который ограничит масштаб графика значениями 100 и 1000 по вертикали.


Правильный ответ
import matplotlib.pyplot as plt 

plt.ylim(100, 1000) 

import matplotlib.pyplot as plt 

plt.xlim(100, 1000) 

from pandas import plt

plt.ylim(100, 1000) 

from pandas import plt

plt.xlim(100, 1000) 
Методы ylim() и xlim() вызывают из библиотеки matplotlib. Эта библиотека вам не раз пригодится в работе с графиками. Метод ylim() меняет масштаб по вертикали или, иначе, оси ординат, а метод xlim() — по горизонтали или оси абсцисс.

 

 

Следующая тема: ИАД. Изучение срезов данных

Вернуться в раздел: Исследовательский анализ данных

Вернуться в оглавление: Я.Практикум