
Продолжая цикл заметок про реальные проблемы в Data Science, мы сегодня разберемся с живой задачей и посмотрим, какие проблемы нас ждут в пути.
Например, помимо Data Science, я давно увлекаюсь атлетикой и одной из целей в беге для меня, конечно, является марафон. А где марафон там и вопрос за сколько же бежать? Часто ответ на этот вопрос дается на глаз ну в среднем бегут или вот Х хорошее время!
И сегодня мы займемся важным делом применим Data Science в реальной жизни и ответим на вопрос:
А что нам говорят данные о московском марафоне?
Точнее, как уже понятно по таблице в начале мы соберем данные, разберемся, кто и как бежал. А заодно это поможет понять, стоит ли нам соваться и позволит здраво оценить свои силы!
TL;DR: Я собрал данные по забегам московского марафона за 2018/2019, проанализиворовал время и показатели участников, а код и данные выложил в открытый доступ.
Сбор данных
Путем шустрого гугления мы обнаружили результаты прошлых пары лет, 2019 и 2018 годов.

Внимательно посмотрел на веб страницу, стало понятно, что данные довольно просто достать нужно лишь разобраться, какие классы за что отвечают, например, класс results-table__col-result, понятное дело, за результат и тд.
Осталось понять как достать все данные оттуда.

И это, оказывается, несложно, ибо тут есть прямая пагинация и собственно мы итерируем по всему отрезку чисел. Бинго, выкладываю собранные данные за 2019 и 2018 год здесь, если кому-то интересно для последующего анализа, то сами данные можно скачать здесь: здесь и здесь.
С чем тут пришлось повозиться
- Страница не отдает ошибок если что-то идет не так, никто не посигналит, сайт просто отдает какие-то данные (например, повторяет прошлую страницу с результатами).
- В какой-то момент сервер решает, что он устал и перестает отдавать данные и виснет проблема решается с помощью поспать и продолжить сбор с прошлой точки.
- Url-магия сайт что-то мудрит со ссылками, и нельзя просто поменять год в url и получить результаты другой гонки приходится ручками через поиск искать и перепроверять, что мы действительно получаем свежие данные иначе отгружает молча данные последнего года.
- В какой момент я собирал данные и параметризовал скрипт сбора данных годом запустил и стал собирать через час другой у меня было четыре датасета за 2016, 2017 и оказалось, что страница молча отдавала данные за 2019 год потому что в том месте год вообще игнорировался, что было совершенно неожиданно вывод стоит всегда проверять такие вещами руками, а не только постфактум хотя и постфактум, конечно, надо проверять данные.
- Здесь есть несколько типов NA: DNF, DQ, "-" придется проводить анализ и перепроверять, и чистить данные, иначе на выходе мусор.
- Типы данных: время здесь это timedelta, но из-за перезапусков и невалидных значений приходится поработать с фильтрами и очисткой временных значений, чтобы мы оперировали над чистыми временными результатами для подсчета средних значений все результаты здесь это усреднение по тем, кто финишировал и у кого зафиксировано валидное время.
А вот и код спойлера, если кто-то решит продолжить собирать интересные беговые данные.
from bs4 import BeautifulSoupimport requestsfrom tqdm import tqdmdef main(): for year in [2018]: print(f"processing year: {year}") crawl_year(year)def crawl_year(year): outfilename = f"results_{year}.txt" with open(outfilename, "a") as fout: print("name,result,place,country,category", file=fout) # parametorize year for i in tqdm(range(1, 1100)): url = f"https://results.runc.run/event/absolute_moscow_marathon_2018/finishers/distance/1/page/{i}/" html = requests.get(url) soup = BeautifulSoup(html.text) names = list( map( lambda x: x.text.strip(), soup.find_all("div", {"class": "results-table__values-item-name"}), ) ) results = list( map( lambda x: x.text.strip(), soup.find_all("div", {"class": "results-table__col-result"}), ) )[1:] categories = list( map( lambda x: x.text.strip().replace(" ",""), soup.find_all("div", {"class": "results-table__values-item-country"}), ) ) places = list( map( lambda x: x.text.strip(), soup.find_all("div", {"class": "results-table__col-place"}), ) )[1:] for name, result, place, category in zip(names, results, places, categories): with open(outfilename, "a") as fout: print(name, result, place, category, sep=",", file=fout)if __name__ == "__main__": main()```
Анализ времени и результатов
Перейдем к анализу данных и собственно результатов забега.
Использовались pandas, numpy, matplotlib и seaborn все по классике.
Помимо средних значений по всем массивам, мы отдельно рассмотрим следующие группы:
- Мужчины так как я вхожу в эту группу мне интересны именно эти результаты.
- Женщины для симметрии.
- Мужчины до 35 это условно одна из самых соревновательных групп и понятно, что сравнивать мне стоит именно с ними так как я в этой группе.
- Отдельно посмотрим на 2018 и 2019 годы а вдруг что поменялось?.
Сначала бегло глянем на таблицу ниже здесь еще раз, чтобы не скроллить: участников стало больше, 95% в среднем добегает до финиша и большая часть участников мужчины. Хорошо, это значит, что в среднем я в основной группе и данные в среднем должно хорошо отображать среднее время для меня. Продолжаем.


Как мы видим средние показатели за 2018 и 2019 практически не изменились примерно 1.5 минуты стали быстрее бегуны в 2019 году. Разница между интересующими меня группами незначительна.
Перейдем к распределениям целиком. И сначала к общему времени забега.

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

Как мы видим фактически вообще ничего не поменялось распределения выглядят фактически идентичными.
Далее рассмотрим распределения по полу:


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

Как мы видим принципиально картина такая же, как и в целом в мужской группе.
Отсюда делаем вывод, что 4 часа для меня тоже являются хорошим средним временем.
Изучаем улучшения участников 2018 2019
Из интересностей: я почему-то думал, что сейчас быстренько соберу данных и можно углубиться в анализ, искать там закономерности часами и тд. Оказалось все наоборот, сбор данных оказался сложнее самого анализа по классике работа с сетью, сырыми данными, очисткой, форматирование, приведение типов и тд заняло куда больше времени чем анализ и визуализация. Не стоит забывать, что мелочи отнимают немного времени но их [мелочей] совсем не мало и в конце они-то и скушают весь ваш вечер.
Отдельно хотелось посмотреть, а как улучшили свои результаты люди, которые участвовали оба раза, путем сопоставления данных между годами мне удалось установить следующее:
- 14 человек участвовали оба года и ни разу не финишировали
- 89 человека добежали в 18 м, но не смогли в 19
- 124 наоборот
- Те, кто смогли добежать оба раза в среднем улучшили на 4 минуты свой результат
Но тут оказалось довольно интересно все:

То есть в среднем люди чуть чуть улучшают результаты но вообще разброс невероятный и в обе стороны то есть хорошо надеяться, что будет лучше но судя по данным, получается вообще как угодно!
Выводы
Я сделал для себя следующие выводы из проанализированных данных
- В целом 4 часа хорошая цель в среднем.
- Основная группа бегущих как раз уже в самом соревновательном
возрасте (и одной группе со мной).
- В среднем люди чуть чуть улучшают свой результат, но вообще
судя по данным там как попадет вообще.
- Средние результаты всего забега примерно одинаковые оба
года.
- С дивана очень комфортно рассуждать о марафоне.