python

Работа с данными среднего размера в Python. Pandas и Seaborn

  • вторник, 15 сентября 2015 г. в 02:11:04
http://habrahabr.ru/post/266289/

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

Я попробовал много инструментов: Excel, Python+Matplotlib, R+ggplot, Python+ggplot, и остановился на связке Python+Pandas+Seaborn. Решил с их использованием уже много задач и хотел бы поделиться наблюдениями.



Я покажу процесс исследования одного странного датасета. Речь пойдёт о расписании всех спортивных мероприятий с участием российских спортсменов за последние 8 лет. Кстати, если у кого-то после прочтения появятся идеи, что с этими данными делать, пожалуйста, напишите, потому что я ничего толкового так и не придумал.

Захватывающий этап парсинга rst и doc-файликов с сайта Минспорта я пропускаю. Предположим, что все данные уже есть в виде DataFrame'a:
data.head()


Таблица не большая, но и не маленькая:

len(data)
76601

Первым делом хотелось бы посмотреть, какие виды спорта представлены:

table = data.groupby('section').size()
table
section
R4                                                             11
Авиамодельный спорт                                          1206
Автомобильный спорт                                          2787
Автомодельный спорт                                           160
Айкидо                                                         26
Айсшток                                                        49
Академическая гребля                                          358
Акробатический рок-н-ролл                                     293
Альпинизм                                                     355
Американский футбол                                            86
Армейский рукопашный бой                                       15
Армспорт                                                      159
Бадминтон                                                     698
Баскетбол                                                    1160
Бейсбол                                                       176
Биатлон                                                      1101
Бильярдный спорт                                              442
Бобслей                                                        53
Бобслей (скелетон)                                            368
Бодибилдинг                                                    94
Бокс                                                         1374
Борьба на поясах                                              144
Боулинг                                                       134
...

Отсортируем:

table = data.groupby('section').size()
table = table.sort(inplace=False, ascending=False)
table
section
Автомобильный спорт                                          2787
Спорт лиц с поражением ОДА                                   2469
Фехтование                                                   2253
Мотоциклетный спорт                                          2025
Дзюдо                                                        2022
Теннис                                                       1770
Велоспорт-шоссе                                              1561
Легкая атлетика                                              1511
Футбол                                                       1432
Парусный спорт                                               1418
Бокс                                                         1374
Конный спорт                                                 1297
Спорт глухих                                                 1277
Волейбол                                                     1208
Авиамодельный спорт                                          1206
Художественная гимнастика                                    1196
...

Достаточно странный топ. Отсортируем не по числу мероприятий, а по числу участников:

table = data.groupby('section').participants.sum()
table = table.sort(inplace=False, ascending=False)
table
Футбол                                                       535305
Баскетбол                                                    460100
Автомобильный спорт                                          401397
Танцевальный спорт                                           307731
Дзюдо                                                        297023
Самбо                                                        219013
Легкая атлетика                                              217761
Тхэквондо                                                    202859
Киокусинкай                                                  191995
Велоспорт-шоссе                                              190293
Комплексные мероприятия                                      187294
Спортивная борьба                                            177312
Мотоциклетный спорт                                          176387
Спортивный туризм                                            153417
Бокс                                                         148654
Хоккей                                                       136849
Кикбоксинг                                                   136099
...

Так лучше. Теперь нарисуем график для топ-30:

table = data.groupby('section').participants.sum()
table = table.sort(inplace=False, ascending=False)
table.head(30).plot(kind='bar')


Я не фанат супер-сурового инженерного дизайна, поэтому всегда импортирую Seaborn:
import seaborn

table = data.groupby('section').participants.sum()
table = table.sort(inplace=False, ascending=False)
table.head(30).plot(kind='bar')


Идеально! Никакой другой функциональности Seaborn здесь использовано не будет. У меня чаще всего так и получается — Seaborn появляется только, чтобы графики стали красивыми. Это уже немало, но, вообще, библиотека умеет много чего другого.

Теперь интересно посмотреть на изменение числа мероприятий во времени. Всё то же самое только группировать нужно по дате:

table = data.groupby('start').size()
table.plot()


Почти готово. Нужно сделать детализацию, например, по неделям, а не по дням:
table = data.groupby('start').size()
table = table.resample('M', how='sum')
table.plot()
  • Спортивная жизнь зимой наименее активна.
  • Тренд до последнего времени восходящий.
  • В 2010 был какой-то провал.


Было бы интереснее посмотреть на этот граф в разбивке по видам спорта. Для этого нужно сгруппировать данные по дате и виду спорта:

table = data.pivot_table(index='start', columns='section', values='participants', aggfunc=len)
table.head()


И нарисовать:

table.plot()


Блестяще, ничего не понятно. Для начала уберём всё кроме топ-40 видов спорта, кстати, код для расчёта топа уже есть, заново не надо писать:
table = data.groupby('section').size()
table = table.sort(inplace=False, ascending=False)
order = table.head(40).index

table = data.pivot_table(index='start', columns='section', values='participants', aggfunc=len)
table = table.reindex(columns=order)
table.plot()


Ладно, сделаем детализацию по неделям:
table = data.groupby('section').size()
table = table.sort(inplace=False, ascending=False)
order = table.head(40).index

table = data.pivot_table(index='start', columns='section', values='participants', aggfunc=len)
table = table.reindex(columns=order)
table = table.resample('M', how='sum')
table.plot()


Почти то, что надо, теперь расположим каждую линию на отдельном графике:
table = data.groupby('section').size()
table = table.sort(inplace=False, ascending=False)
order = table.head(40).index

table = data.pivot_table(index='start', columns='section', values='participants', aggfunc=len)
table = table.reindex(columns=order)
table = table.resample('M', how='sum')
table.plot(subplots=True, layout=(8, 5), figsize=(20, 20))
  • Для некоторых видов наблюдаются тренды в числе мероприятий:
    • Сильно растёт спорт для инвалидов (лиц с поражением ОДА) и автомобильный спорт.
    • Слегка растёт мотоциклетный спорт, дзюдо, самбо, парусный спорт, волейбол, тхэквондо, бадминтон.
    • У футбола какой-интересный тренд.
    • Бокс перестал расти в 2012 году.
    • Надо отметить что падения числа мероприятий нигде не наблюдается.
  • Иногда есть периодичность в числе мероприятий, что не удивительно:
    • Парусный спорт, конные спорт, авиамодельный спорт, велоспорт замирают зимой.
    • Вообще зимой у многих видов затишье. Но бывает и наоборот, например, во фристайле.
    • Интересный рисунок у тенниса и лёгкой атлетики, который повторяется каждый год


Что мне нравится в этом подходе? Функция plot очень простая, у неё всего несколько важных аргументов: kind, subplots, figsize. Результат зависит в основном от структуры данных, для которых вызывается plot. То есть не нужно осваивать отдельно инструмент для рисования графиков. Нужно научиться преобразовывать данные. С Pandas это сделать не очень сложно. Достаточно выучить несколько «кирпичиков»: groupby, sort, head, pivot_table, reindex, resample, и применять их в разных комбинациях.