Вы когда -нибудь задумывались, насколько хорошо вы квалифицируете в своем клубе Strava? Вы хотите узнать уровень клуба вашего друга? Хотели бы вы найти те, которые имеют аналогичные результаты верховой езды, чем вы в своем клубе Strava?
Ну, я задал себе подобные вопросы и решил исследовать API Strava и провести небольшой анализ, чтобы группировать членов вашего клуба Strava в не Количество кластеров, основанных на производительности езды 🚴♀ 🚴🏿🚴🏻♀ 🚴🏼 🚴🏼tice 🚴🏼tic🚴🏽 🚴🏼чего 🚴🏻♂💨💨💨 Что это хорошая в клубной поездке?
Здесь я хотел бы сосредоточиться на процессе кластеризации, а не на использовании API Strava, потому что я думаю, что более поздняя тема широко распространена там. Если вы не знакомы с API, вы можете взглянуть на официальную документацию здесь
Давайте начнем, не так ли?
Я чувствую, что должен начать отсюда: определения
Для меня клубное занятие и, в частности, в клубной поездке – это то, что происходит в типичное воскресенье, вы просыпаетесь рано, хорошо завтракаете, одевайтесь в Лайкра, и у вас есть куча часов с парнями из клуба.
Ну, оказывается, определение не разделяется в мире Strava. Оставайся со мной здесь Анкет Strava рассматривает мероприятия в клубе конкретного клуба как все виды деятельности от всех пользователей этого клуба. На простом английском языке, если вы присоединитесь к клубу, все ваши действия будут указаны в качестве клубных мероприятий такого клуба.
План простой – Чем проще, тем лучше они говорят – Прежде всего, мы позаботимся о том, чтобы у нас были припасы вашего любимого горячего напитка, в моем случае Earl Grey Tea 🍵. ИМХО это всегда должен быть первым шагом, прежде чем пытаться сделать что -нибудь славное. ОК, двигаться вперед ..
Идея состоит в том, чтобы получить как можно больше данных о поездках на интересующем клубе, сделайте некоторое очищение данных, и как только мы будем довольны данными, мы будем рады, поскольку мы будем готовы к группе.
Мой чайник включен, мой граф Грей собирается подготовиться ☑
Пришло время посмотреть на какой -то код:
import requests ACCESS_TOKEN = 'your_access_token_here' n_clubs = 30 endpoint = "https://www.strava.com/api/v3/athlete/clubs?&pagenobody =1&per_page={}&access_token={}" r = requests.get(endpoint.format(n_clubs,ACCESS_TOKEN)) my_clubs = r.json()
Я думаю, что этот шаг не был подробно описан в плане 🙃 В любом случае, в основном то, что это делает, это получение списка всех ваших клубов Strava, в которые вы присоединились. Там вы сможете найти ключ я бы для каждого из ваших клубов. После того, как вы определили из списка клуб, который вас интересует, отметьте его я D – С этого момента мы будем называть это Club_id Анкет
Обратите внимание, что вам понадобится токен доступа здесь. Если вы знаете, как получить это, это хорошая новость для вас, если не боюсь, я не буду освещать это здесь, извините, я считаю, что другие люди, которые могут общаться намного лучше, чем я, уже опубликовали путь к получить ваши.
Теперь, как мы планировали, мы будем использовать Club_id Чтобы получить столько данных, что нам разрешено:
import pandas as pd endpoint = "https://www.strava.com/api/v3/clubs/{}/activities?&page={}&per_page={}&access_token={}" df = None for ii in range(2): r = requests.get(endpoint.format(str(club_id),str(ii+1),'100',ACCESS_TOKEN)) club_activity = r.json() df = pd.concat([df, pd.DataFrame(club_activity)]) df = df.reset_index(drop=True) # Unpack the nested athlete dictionary into columns df = pd.concat([df, pd.DataFrame((d for idx, d in df['athlete'].iteritems()))], axis=1) df.drop(['athlete','resource_state'],axis=1,inplace=True) df['full_name'] = df.firstname + ' ' + df.lastname
Это должно привести к созданию данных данных с базовой информацией о деятельности клуба интересующего клуба.
In[]: df.head() Out[]: distance elapsed_time moving_time name 0 55359.5 6709 6709 Afternoon Ride 1 23363.7 5911 5595 Afternoon Ride 2 28746.8 4961 4823 Afternoon Ride 3 64576.7 13551 10647 Afternoon Ride 4 24094.0 2712 2712 Morning Ride total_elevation_gain type workout_type full_name(*) 0 816.0 Ride 10.0 Sanglier 1 427.0 Ride NaN Julius Pompilius 2 724.0 Ride NaN Moralélastix 3 1343.7 Ride NaN Amnésix 4 146.0 VirtualRide NaN Sténograf
(*) По причинам конфиденциальности я буду отображать Астерикс символы вместо реальных имен.
Несколько заметок здесь,
- К счастью, единицы, кажется, в Si, Это приятное прикосновение! 🙌🙌
- У нас есть две функции для описания времени из приведенных выше данных:
elapsed_time
должен включать перерывы, тогда какMOVEST_TIME
должно быть то, что описывает имя. Если бы это было так, я ожидаю, что будут иметь более высокие значения истекающего времени, чем время для движения, всегда. Как вы можете видеть, это не так, то, что заставляет меня думать, что некоторые поездки не входят в систему с включенной автопаузой 🤦♂️ Ай, давай, ребята! - Средняя скорость не показана, поэтому мы вычисляем ее с
df ['speed_kph']. Distance/df.moving_time*3.6
Извините за тех людей, которые не автопауза, так как их скорость будет уменьшена - Похоже, что не все идут по дороге, Стенографу было довольно удобно делать раннюю сессию дома!
Мы хотели бы, чтобы данные были индексированы спортсменом, один из способов достижения их – это использование метода Groupby, прикованной средней статистикой.
summary = df[df.type=='Ride'].groupby('full_name')['distance','total_elevation_gain','speed_kph'].mean()
In[]: summary.head() Out[]: distance total_elevation_gain speed_kph full_name Abraracourcix 34325.1 502.1 15.2 Absolumentexclus 50507.7 796.8 23.7 Amnésix 48812.6 981.7 21.5 Amonbofis 54889.8 1014.0 20.5 Aplusbégalix 92074.0 956.0 27.5
Итак, вы видите, это дает нам 3 функции – расстояние, общее усиление высоты и скорость – для каждого гонщика. Обратите внимание, что мы игнорируем виртуальные поездки, фильтруя тип поездки. Следуя, мы будем использовать именно эти данные для классов гонщиков в группах.
У меня мало чая .. держись на минуту, этот раздел заслуживает чуть больше, чем просто чай. Я думаю, что печенье будет делать 😋
Хорошей практикой при работе с алгоритмами машинного обучения является возврат ваших данных. В этом случае мы будем предварительно обработать данные с помощью Minmax Scaler. Это будет масштабировать все функции, так что его значения попадают в данное диапазон, как правило, между 0 и 1. Затем мы будем использовать эти значения для подачи алгоритма кластеризации K-средних.
from sklearn.preprocessing import minmax_scale from sklearn.cluster import KMeans X = minmax_scale(np.array(summary)) kmeans = KMeans(n_clusters=3, random_state=0).fit(X) summary['cluster'] = kmeans.labels_
Довольно быстро, не так ли? Ну, давайте посмотрим на результаты, прежде чем спешить с выводами. Я хотел бы построить выступление спортсмена с помощью наших 3 функций, показывая только группы, которые мы только что сделали.
import matplotlib.pyplot as plt import seaborn as sns _= plt.figure() _= plt.subplots_adjust(hspace=0,wspace=0) _= plt.subplot(221) _= sns.scatterplot(x=summary.distance/1000,y='total_elevation_gain',data=summary,hue=summary.cluster,legend=False) _= plt.subplot(223) _= sns.scatterplot(x=summary.distance/1000,y='speed_kph',data=summary,hue=summary.cluster,legend=False) _= plt.subplot(224) plt.yticks([]) _= sns.scatterplot(x='total_elevation_gain',y='speed_kph',data=summary,hue=summary.cluster,legend=False)
Хороший сюжет Но я не совсем доволен этим. Конечно, нам удастся кластеризировать гонщиков в 3 группах или, скажем, 3 команды. Держите шампанское на данный момент, это хорошая новость, что у нас хорошо сгруппированы гонщики, которые они охватывают, но выглядя немного ближе, некоторые из этих команд довольно несбалансированы с точки зрения скорости 😓. Посмотрите на нижние сюжеты – расстояние в зависимости от скорости и общее усиление высоты в зависимости от скорости – теперь обратите внимание на синюю команду. Их диапазон в скорости огромный, и помните, что эта скорость – средняя скорость !! Я лично не хотел бы быть в синей команде, если вы лучшие гонщики, вы ничего не делаете, кроме как ждать остальных, и если вы самый медленный гонщик там … какой это кошмар !!
Нам нужна вторая попытка.
Мы хотели бы иметь меньший диапазон скорости на каждой группе, чтобы все гонщики могли легко идти в ногу с темпами группы. Это означает, что скорость функции должна иметь значение больше, чем остальные. Как вы реализуете эту концепцию? Ключ в масштабировании. Следуйте за Minmax Scaler, мы будем масштабировать скорость в 2 в 2 и оставить другие функции такими, какие они есть. Это поможет.
X_weighted = np.multiply(X, np.tile([1,1,2], (len(X), 1))) kmeans = KMeans(n_clusters=3, random_state=0).fit(X_weighted) summary['cluster2'] = kmeans.labels_
А теперь мы создаем ту же фигуру:
_= plt.figure() _= plt.subplots_adjust(hspace=0,wspace=0) _= plt.subplot(221) _= sns.scatterplot(x=summary.distance/1000,y='total_elevation_gain',data=summary,hue=summary.cluster2,legend=False) _= plt.subplot(223) _= sns.scatterplot(x=summary.distance/1000,y='speed_kph',data=summary,hue=summary.cluster2,legend=False) _= plt.subplot(224) plt.yticks([]) _= sns.scatterplot(x='total_elevation_gain',y='speed_kph',data=summary,hue=summary.cluster2,legend=False)
Теперь это выглядит намного лучше, гонщики сгруппированы по количеству расстояния, которое они покрывают, как высоко они поднимаются и как быстро они ездят, следя за тем, чтобы распространение в средней скорости в группах сохранялось низким.
И вот и вы, как скрепить свои поездки в клуб Strava. Время открыть бутылку шампанского 🍾
Оригинал: “https://dev.to/xbasf/cluster-strava-club-riders-5cjd”