В предыдущей статье Я рассказал о том, как получить персональные данные о тренировках из набора FIT-файлов, которые создаются при использовании носимых устройств (фитнес-браслетов, часов, смартфонов, велокомпьютеров).
После дальнейшего анализа своей деятельности я решил сосредоточиться на виртуальных тренировках на велосипеде по нескольким причинам:
- наличие более сотни записей данного вида тренировок, выполненных за последние два года на тренажере одного и того же типа с использованием одних и тех же датчиков (измерителя мощности, датчика частоты вращения педалей и пульсометра) и одного и того же приложения
- тренировки проводились в зимний сезон в помещении при относительно одинаковых условиях, то есть исключается фактор разных погодных условий (температура воздуха, сила ветра, влажность)
- тренировки проводились регулярно примерно в одно и то же время суток и дни недели
Стоит отметить, что уровень точности датчиков устойчивости пока не претендует на абсолютную значимость результатов анализа, а сам анализ интересен лишь с любительской точки зрения.
Что такое виртуальная тренировка на велосипеде?
Виртуальные тренировки на велосипеде — это замена или дополнение тренировок на велосипеде в помещении, проводимых на велотренажерах.Минимальный набор оборудования для начала виртуальной тренировки на велосипеде включает в себя сам велосипед и тренажер, пульсометр, датчик частоты вращения педалей, измеритель мощности и приложение (на компьютере, смартфоне или приставке Smart TV), транслирующее структурирует тренировки и записывает данные со всех датчиков.
Виртуальность можно дополнить прорисованным в приложении маршрутом и соревнованиями с другими велосипедистами.
Подробнее о том, как проходят виртуальные тренировки на велосипеде, вы можете посмотреть.
Здесь .
Ключевые показатели тренировок на виртуальном велосипеде включают в себя:
- власть (мощность, Вт) – сила, приложенная к педалям, умноженная на скорость.
Измеряется в ваттах и показывает эффективность работы, которую вы выполняете при вращении педалей.
- ритм (каденс, об/мин) - частота вращения педалей или другими словами количество оборотов педали велосипедиста, совершаемых за одну минуту.
- пульс (сердцебиение, удары в минуту) – частота сердечных сокращений или другими словами количество ударов сердца в минуту.
Если рассматривать файл FIT, то упомянутые данные для каждой тренировки хранятся в сообщении Record, обобщенные данные хранятся в Session.
Как построить график тренировок
Чтобы получить данные о конкретной тренировке из базы данных, которую я ранее развернул, я использую запрос, указывающий уникальный номер тренировки.(идентификатор активности =124863703316) в таблицу записей:
select * from record where activity_id =124863703316 order by timestamp asc
Структура таблицы рекордов с данными одной тренировки Добавлю, что другие показатели - координаты, высота, скорость, расстояние - в структурированной виртуальной тренировке на велосипеде они выводятся из ключевых (мощность, частота вращения педалей и частота сердечных сокращений), поэтому исключаются из дальнейшего анализа.
Для построения обычного графика показателей обучения я использовал подключение к базе данных PostgreSQL через модуль психокопг2 , панды для работы с набором данных и matplotlib для визуализации.
На первом этапе мы подключаемся к данным: import psycopg2
import pandas as pd
activity_id = 124863703316
conn = psycopg2.connect(host="localhost", database="garmin_data", user="postgres", password="afande")
df = pd.read_sql_query("""select timestamp, heart_rate, cadence, power from record
where activity_id ={} order by timestamp asc""".
format(activity_id), conn)
Для каждой записи мы имеем указание на конкретный момент времени в виде 2022-02-15 16:18:16+00:00 , что не очень удобно для общего графика тренировок.
Обозначим начало тренировки как нулевую секунду; все последующие записи будут пересчитаны относительно начала.
Давайте добавим новый столбец сек в набор данных: df['sec'] = (df['timestamp']-min(df['timestamp'])).
dt.total_seconds()/60
На графике предполагается одновременное отображение частоты пульса, мощности и частоты шагов, то есть необходимо иметь три шкалы показателей.
Я нашел пример построения трех осей Y на одном графике.
Здесь .
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.subplots_adjust(right=0.75)
plt.title(str(min(df['timestamp']).
date()) + " / Activity - " +str(activity_id))
twin1 = ax.twinx()
twin2 = ax.twinx()
twin2.spines.right.set_position(("axes", 1.1))
p1, = ax.plot(df.sec, df.heart_rate,"r-", label="HR")
p2, = twin1.plot(df.sec, df.power, "b-", label="Power")
p3, = twin2.plot(df.sec, df.cadence, "g-", label="Cadence")
ax.set_xlim(0, 90)
ax.set_ylim(0, 200)
twin1.set_ylim(0, 400)
twin2.set_ylim(0, 120)
ax.set_xlabel("Time, min")
ax.set_ylabel("HR, bpm")
twin1.set_ylabel("Power, watts")
twin2.set_ylabel("Cadence, bpm")
ax.yaxis.label.set_color(p1.get_color())
twin1.yaxis.label.set_color(p2.get_color())
twin2.yaxis.label.set_color(p3.get_color())
tkw = dict(size=4, width=1.5)
ax.tick_params(axis='y', colors=p1.get_color(), **tkw)
twin1.tick_params(axis='y', colors=p2.get_color(), **tkw)
twin2.tick_params(axis='y', colors=p3.get_color(), **tkw)
ax.tick_params(axis='x', **tkw)
ax.legend(handles=[p1, p2, p3])
plt.rcParams['figure.figsize'] = [10, 5]
plt.show()
В результате мы получаем следующий график:
График пульса, мощности и частоты шагов за одну тренировку На этом графике хорошо видно, что показатели могут меняться довольно резко (секунды падают и растут), что может быть связано с потерей сигнала датчика или остановкой педалирования.
Для визуально приятного отображения графика обучения вы можете добавить сглаживание линий с помощью функции сплайна.
Метод описан Здесь .
Я добавил функцию в код сплайн с тремя параметрами — значениями по оси X, значениями по оси Y и параметром n, отвечающим за уровень сглаживания итоговой линии (чем он меньше, тем ровнее линия будет): from scipy.interpolate import make_interp_spline
import numpy as np
def spline(x, y, n):
do_spline = make_interp_spline(x,y)
x_ = np.linspace(x.min(), x.max(), n)
y_ = do_spline(x_)
return x_, y_
Заменим код линий на графике следующим: p1, = ax.plot(spline(df.sec, df.heart_rate, 150)[0], spline(df.sec, df.heart_rate, 150)[1],"r-", label="HR")
p2, = twin1.plot(spline(df.sec, df.power, 150)[0], spline(df.sec, df.power, 150)[1], "b-", label="Power")
p3, = twin2.plot(spline(df.sec, df.cadence, 150)[0], spline(df.sec, df.cadence, 150)[1], "g-", label="Cadence")
На рисунке ниже показано сравнение двух вариантов:
Сравнение двух графиков: слева с исходными данными, справа с помощью сплайн-функции
Какие виды тренировок на велосипеде существуют?
Регулярное планирование велосипедных тренировок представляет собой достаточно сложный процесс и может быть направлено на достижение различных целей: конкретного старта, повышения выносливости на длинных дистанциях, общей максимизации эффективности тренировочного времени и т. д. Самый популярный метод планирования основан на объединении различных зон интенсивности в рамках одной структурированной тренировки и/или в рамках еженедельного/ежемесячного плана тренировок, в зависимости от ваших целей.Деление на зоны интенсивности чаще всего происходит по признаку функциональный пороговая мощность ( Функциональная пороговая мощность, FTP ) – средняя максимальная мощность при езде на велосипеде в течение часа.
Персональное значение FTP велосипедиста можно определить с помощью FTP-тест .
Одна из наиболее распространенных моделей предполагает разделение интенсивности на семь зон в соответствии с физиологической реакцией организма спортсмена:
Семь зон интенсивности езды на велосипеде.
Источник: https://www.highnorth.co.uk/articles/cycling-training-zones Дано подробное описание каждой из семи зон.
Фактически, одна тренировка может сочетать в себе несколько типов, и к одному типу тренировку можно отнести лишь условно.
Как идентифицировать похожие тренировки
Всю совокупность выполненных тренировок я постарался разделить на несколько типов с учетом продолжительности тренировки, соотношения времени пребывания в зонах интенсивности, соотношения времени пребывания на низком и среднем каденсе.Далее я расскажу вам, как я получил каждый из показателей.
Продолжительность обучения
Продолжительность тренировки можно узнать прямо из таблицы Сессия - это столбец total_timer_time , где время обучения записано в секундах.При дальнейшем сравнении времени тренировки с показателями в других единицах измерения целесообразно нормировать эти данные.
Для готового кадра данных нормализацию можно выполнить следующим образом, где time_n – столбец с нормализованными данными, время - исходные данные: data['time_n'] = data.apply(lambda row: round((row['time']-min(data['time']))/(max(data['time'])-min(data['time']))*100.00)
Соотношение времени, проведенного в зонах интенсивности
Установленные зоны интенсивности меняются во время тренировки и зависят от последнего результата FTP-теста.В сезоне 2020/2021 я провел три подобных теста, а в сезоне 2021/2022 – два.
Результаты теста я внес в отдельную таблицу Тест в вашей базе данных.
Таблица испытаний с пороговыми значениями мощности для каждого теста FTP Далее для каждой тренировки из таблицы Сессия (общая информация о тренировке хранится здесь) Я добавил значение последнего FTP-теста, на основании которого выставляются зоны интенсивности для всех последующих тренировок.
Я также исключил все тренировки перед первыми тестами сезона, поскольку использование значений прошлого сезона нецелесообразно (за лето форма могла измениться).
В запросе я использовал перекрестное соединение И ранжировать() выше : select * from (select session.activity_id, session.timestamp, session.avg_heart_rate, session.avg_power,
session.total_timer_time/3600 as time,
test.power_threshold, test.timestamp as test_timestamp,
rank() over (partition by session.activity_id order by session.activity_id, test.timestamp desc) as ftp_rank
from session cross join test
where sub_sport = 'virtual_activity' and avg_heart_rate > 90
and session.timestamp > '2020-12-26' and session.timestamp >= test.timestamp
and record.activity_id not in (109983788203, 110771005101, 111376595537, 111494782478)
order by session.activity_id) a
where a.ftp_rank = 1
Для получения подробных данных обучения я сделал запрос к таблице Записывать (ранее мы также сделали к нему запрос на построение одной тренировки), исключая тренировки в каждом сезоне перед первым тестом: select record.record_id, record.activity_id, record.timestamp, record.heart_rate,
record.cadence, record.power
from record join session on record.activity_id = session.activity_id
where record.timestamp >= '2020-12-26' and session.sub_sport = 'virtual_activity' and record.power > 0
and record.activity_id not in (109983788203, 110771005101, 111376595537, 111494782478)
order by timestamp asc
Объединив таблицы, я получил подробные данные по каждой тренировке вместе с пороговой мощностью.
Теперь вы можете рассчитать соотношение времени, проведенного в разных зонах интенсивности.
Добавим в объединенную таблицу новый столбец, где для каждой записи (каждую секунду) будет рассчитываться процент пороговой мощности: record_data['percent_power'] = round(record_data['power']/record_data['power_threshold']*100.00)
Для дальнейших расчетов я добавил следующую функцию на основе приведенной ранее таблицы семи зон интенсивности: def zone(row):
if row['percent_power'] <= 55:
val = 1
elif row['percent_power'] > 55 and row['percent_power'] <=75:
val = 2
elif row['percent_power'] > 75 and row['percent_power'] <=90:
val = 3
elif row['percent_power'] > 90 and row['percent_power'] <=105:
val = 4
elif row['percent_power'] > 105 and row['percent_power'] <=120:
val = 5
elif row['percent_power'] > 120 and row['percent_power'] <=130:
val = 6
elif row['percent_power'] > 130:
val = 7
else:
val = 0
return val
Расчет номера зоны для каждой строки в кадре данных упрощается до вида: record_data['zone'] = record_data.apply(zone, axis=1)
Далее агрегируем данные, чтобы получить соотношение времени, проведенного в каждой зоне интенсивности для отдельной тренировки: record_pivot = pd.pivot_table(record_data, index = ['activity_id', 'zone'], values = ['percent_power'], aggfunc='count')
record_pivot = record_pivot.reset_index()
record_pivot['record_count'] = record_pivot.groupby('activity_id')['percent_power'].
transform('sum')
record_pivot['percent_zone'] = round(record_pivot.percent_power/record_pivot.record_count*100.00)
record_pivot = record_pivot[['activity_id', 'zone', 'percent_zone']]
record_pivot['zone_desc'] = record_pivot.apply(lambda row: 'zone_'+str(int(row['zone'])), axis=1)
training_zone = pd.pivot_table(record_pivot, index=['activity_id'], values='percent_zone', columns='zone_desc')
training_zone = training_zone.reset_index()
Окончательный фрейм данных выглядит так:
Тренировки с рассчитанным соотношением времени, затрачиваемого на зоны интенсивности (зона_1 - зона_7)
Соотношение времени, проведенного при низком и высоком темпе
Используя метод расчета зон интенсивности, аналогичный расчет можно провести и для частоты шагов.Я использовал следующую функцию для расчета трех условных зон частоты вращения педалей.
(низкий, нормальный или средний и высокий) : def cadence(row):
if row['cadence'] >= 45 and row['cadence'] < 65:
val = 1
elif row['cadence'] >=85 and row['cadence'] <= 95:
val = 2
elif row['cadence'] > 95:
val = 3
else:
val = 0
return val
Аналогично рассчитываем зоны каденса на каждую секунду тренировки: cadence_pivot = pd.pivot_table(record_data, index = ['activity_id', 'cadence_zone'], values = ['cadence'], aggfunc='count')
cadence_pivot = cadence_pivot.reset_index()
cadence_pivot['record_count'] = cadence_pivot.groupby('activity_id')['cadence'].
transform('sum')
cadence_pivot['cadence'] = round(cadence_pivot.cadence/cadence_pivot.record_count*100.00)
cadence_pivot = cadence_pivot[['activity_id', 'cadence_zone', 'cadence']]
cadence_pivot['cadence_zone_desc'] = cadence_pivot.apply(lambda row: 'cadence_zone_'+str(int(row['cadence_zone'])), axis=1)
cadence_zone = pd.pivot_table(cadence_pivot, index=['activity_id'], values='cadence', columns='cadence_zone_desc')
cadence_zone = cadence_zone.reset_index()
Когда мы объединяем все данные в одну таблицу, мы получаем фрейм данных, который выглядит следующим образом:
Тренировки с рассчитанным соотношением времени, проведенного по зонам интенсивности (зона_1 - зона_7), зонам каденции (cadence_zone_0-cadence_zone_3) и ее продолжительности в нормированном виде (time_n)
Использование контролируемой классификации
В итоговой таблице я получил список 102 тренировок за последние два сезона (2020-2021 и 2021-2022).Учитывая размер выборки, можно классифицировать тренировки вручную, но я решил попробовать метод контролируемой классификации.
Скажу сразу, рациональность этого действия сведена к минимуму, но меня интересовало, как этот метод может сработать в данном случае.
Классификация с учителем (или обучение с учителем) использует предварительно обученные выборки или тесты для классификации данных или точного прогнозирования результатов.
Узнайте больше о методах обучения Здесь .
Подготовка стандартов обучения
Для 37 из 102 тренировок я назначил наиболее близкий тип тренировки, сопоставив для каждой временные зоны и зоны интенсивности.Примеры всех видов стандартного обучения и их описание приведены в таблице:
Код ссылки | Имя | Продолжительность | Зоны интенсивности | Зоны каденса |
3 | Условное пороговое обучение / FTP-тест | короткий (менее часа) | значительная часть времени (50% и более) проводится в зонах высокой интенсивности | большая часть времени проводится при среднем и высоком темпе |
2 | Условно тренировочный темп | средний (1,5-2 часа) | большая часть времени (более 70%) проводится в зонах низкой и средней интенсивности | большая часть времени проводится при средней частоте или от низкой до средней.
|
1 | Условно тренировка на выносливость | долгосрочный (более 1,5 часов) | большая часть времени (более 70%) проводится в зонах с низкой интенсивностью | большая часть времени проводится на среднем темпе |
0 | Обучение условному восстановлению | короткий (до 1,5 часов) | Большую часть времени (более 70%) проводят в зонах низкой интенсивности.
| большая часть времени проводится на среднем темпе |
Случайные леса
Я использовал самый распространенный и простой в реализации алгоритм Random Forests, который включен в модуль sklearn. Доступны четкие инструкции по использованию.Здесь .
Для построения модели в качестве входных данных я использовал различные комбинации столбцов из заранее подготовленной таблицы.
На мой взгляд, лучшая комбинация для моего набора данных выглядит так: X=df_train[['time_n', 'zone_1_2', 'zone_3', 'zone_4_6', 'cadence_zone_1']]
Я суммировал значения первая и вторая зоны интенсивности , поскольку они почти всегда соответствуют низкому уровню, аналогично для четвертый, пятый и шестой – потому что они соответствуют высокому уровню.
Седьмой Зону я исключил, так как для некоторых тренировок она отображается только один раз (1 процент и меньше).
Для каденции я взял только зону с низкими значениями, так как значение средней и высокой зон будет напрямую зависеть.
В качестве вывода я использовал 37 значений, соответствующих типу обучения, который я перечислил ранее.
Тестирование модели показало ее высокую точность (точность: 1,0), но, скорее всего, она меньше, исходя из небольшого набора данных.
Используя эту модель, я классифицировал все остальные тренировки на четыре типа.
По итогам классификации я получил 5 пороговых силовых тренировок, 39 темповых, 14 выносливых, 44 восстановительных.
Сравнение похожих тренировок
Для аналогичных тренировок я попробовал объединить их профили мощности на одном графике, поскольку они представляют собой исходную структурированную задачу.Теоретически я должен был увидеть похожие профили для одного и того же типа обучения.
Для визуализации нескольких профилей одновременно я использовал опцию подсюжет из библиотеки matplotlib .
# import libraries
import psycopg2
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
# connect to the dataset
df = pd.read_csv('power_zones_data_rf_comparison.csv', index_col=0)
df = df[['activity_id', 'training_type']]
training_3 = df.loc[df['training_type']==3]
list_3 = training_3['activity_id'].
to_list() conn = psycopg2.connect(host="localhost", database="garmin_data", user="postgres", password="afande") # generate a chart for each training from the list_3 for i in range(5): activity_id = list_3[i] df = pd.read_sql_query("""select timestamp, heart_rate, cadence, power from record where activity_id ={} order by timestamp asc""".
format(activity_id), conn) df['sec'] = (df['timestamp']-min(df['timestamp'])).
dt.total_seconds()/60 plt.subplot(2,3,i+1) plt.plot(df.sec, df.power) plt.title(min(df['timestamp']).
date())
plt.xlim(0, 60)
plt.ylim(0,450)
plt.xlabel('time, min')
plt.ylabel('power, watts')
plt.rcParams['figure.figsize'] = [20, 10]
plt.show()
Наиболее точно определялись пороговые тренировки (или FTP-тесты).
Три из них были вручную идентифицированы как эталонные; модель правильно определила оставшиеся два.
Сравнение профилей тренировочной мощности с кодом 3: верхний ряд — эталонные тренировки, нижний ряд — определены с помощью контролируемой модели классификации.
Остальные виды обучения определены относительно точно.
Сравнение обучения внутри выявленных типов будет продолжено в последующем анализе.
Сравнение профилей тренировочной мощности с кодом 2: верхний ряд — эталонные тренировки, нижний ряд — определены с помощью контролируемой модели классификации.
Полученные результаты
Это моя первая попытка проанализировать данные, полученные из файлов FIT. По сути, мне удалось воспроизвести отображение базового набора показателей, которые в дальнейшем можно использовать для расчета эффективности тренировок.В нынешнем виде это, конечно, ничего не добавляет к существующему функционалу фитнес-приложений (таких как Garmin Connect или Strava), но это первый шаг к независимости от их интерфейса.
Я попытался классифицировать завершенные тренировки, чтобы в дальнейшем сравнивать результативность аналогичных тренировок, а именно сравнивать реакцию организма на подобную нагрузку через пульс.
Я надеюсь поделиться этим анализом в будущих статьях.
Теги: #python #postgresql #garmin #matplotlib #cycling #sklearn #zwift #fit #fit
-
Как Заправить Картриджи В Принтере Canon?
19 Oct, 24 -
Взгляд В Будущее Python
19 Oct, 24 -
Z-Monitor - Система Мониторинга Госзаказа
19 Oct, 24 -
Новая Серия Ip-Телефонов Grandstream Gxp1700
19 Oct, 24