Skip to main content

A library for analyzing data from distance runners

Project description

The Running API

The Running API is a python library intended to provide functionality for organizing and understanding data from various running activities. Funcitons and classes for both trainig runs and races are included as well as helpers for implementing functions from the Strava API.

Strava Client

The Running API comes with classes that automate the interaction with the Strava REST API. This allows The Running API to quickly grab runs that have been posted to Strava profiles and clubs assuming the user has an available application registered with Strava.

Let's see an example of how to instance a Strava activities client to pull data from a series of runs.

Note: You will need to set up a Strava application first and get the Client ID and Client Secret at well as the authorization token and other information related to the athlete profile. Strava has instructions on how to set this up here.

from running_api.utils.strava_utils import StravaActivitiesClient
from running_api.schemas.schemas import AthleteStravaAccessToken, StravaClientConfig

# set up some pre-requisites
# you will need to substitue your own values
athlete_token = AthleteStravaAccessToken(
    athlete_name = "Test Athlete",
    token_type = "Bearer",
    access_token = "Test Token",
    expires_at = 100000,
    expires_in = 100000,
    refresh_token = "Test Refresh Token",
)

client_config = StravaClientConfig(
    client_id = 10000,
    client_secret = "Test Secret",
)

# initialize the client
client = StravaActivitiesClient(
    athlete_token,
    client_config
)

# now let's grab some activity data!
single_activity = client.get_individual_activity_stats(
    "Test Activity ID"
)

# we can also grab a series of runs in a time range
many_activities = client.get_activities_in_time_range(
    start = "2023-10-10T00:00:00",
    end = "2023-10-15T00:00:00",
)

Note that all of the data returned from these functions are in json format. This can be occasionally cumbersome to work with, so The Running API has provided the user with some custom workout datatypes to load these activities into.

Note: A particular quirk in how authentication is handled with the Strava API requires that the access token be refreshed after 6 hours. The StravaActivitiesClient handles this automatically, but the new values for the access_token, refresh_token, and the expiration timeout values will need to be stored somewhere for the next use otherwise the user will need to generate a fresh set of tokens as outlined in the Strava API documentation. It is suggested that these updated values are continuously saved into a file and loaded any time a new set of activities is pulled. Simply pull the up-to-date access token information by getting the StravaActivitiesClient.athlete_token attribute and then save to a file.

Loading Workout Activities

As mentioned above, if one would like a simple interface to organize and load many workouts into a list of custom datatypes, we have a specialized function to do just that.

from running_api.utils.training_utils import get_training_runs

# reuse the client created in the previous section
# initialize the client
client = StravaActivitiesClient(
    athlete_token,
    client_config
)

training_runs = get_training_runs(
    client, 
    start = "2023-10-10T00:00:00",
    end = "2023-10-15T00:00:00",
)

The training_runs object is a list storing each activity as a TrainingRun class. This hopefully will be a little easier to manage than the dict\json object. In addition, functions such as convert_training_runs_to_df are provided to convert lists of TrainingRun objects into dataframes for even more flexibility.

From Raw FIT Files

The Running API has some ability to capture run data from raw files downloaded directly from a GPS watch. Garmin and a number of other device manufacturers use a file format called FIT. In the event that you are not enthusiastic about sending you're data all over the internet or just can't be bothered to set up a Strava account, you can simply dump your files onto your computer and read them in individually for analysis.

Let's take a look at how we can use The Running API to read in a single FIT file.

from running_api.utils.data_utils import FitFileDataExtractor

extractor = FitFileDataExtractor("test_file.fit")

# get some basic information from the file
tot_distance = extractor.get_total_distance()
start_timestamp = extractor.get_start_time()

# convert the file to either a Race or TrainingRun class
race = extractor.convert_to_run(
    athlete="Test Athlete", race=True, race_name="Test Race"
)
training_run = extractor.convert_to_run(athlete="Test Athlete")

Racing Tools

There are also some niffty ways to organize and track racing data. Let's suppose we want to note down what an athlete has done for a particular race. We can use a real-life example from the 2024 US Olympic Trials.

from running_api.utils.race_utils import Race, RaceType
from running_api.utils.data_utils import save_races

# let's log a track race
individual_race = Race(
    athlete = "Grant Fisher",
    race_name = "U.S. Olympic Trials",
    date = "2024-06-21T22:27:00",
    tot_time = 1669.47, #s
    tot_distance = 10000, #m
    location = "Eugene, OR",
    race_type = RaceType.OUTDOOR_TRACK,
)

# let's add the first 400m as a split
individual_race.add_split(
    time = 64.43,
    start_loc = 0.0,
    end_loc = 400.0,
)

# now we can can save this race to a json file for later analysis
race_collection = [individual_race] # this can be a list of many races

save_races(race_collection, "races.json")

Run Classes

The custom run classes are split into two categories that share a number of features. These classes are build as pydantic base models. We also defined a custom base model for the individual splits that constitute a run.

class Split(BaseModel):
    time: Positive
    start_loc: Positive
    end_loc: Positive
    pe: Optional[BoundedPE] = None
    heart_rate: Optional[Positive] = None
    elevation_gain: Optional[Positive] = None
    elevation_loss: Optional[Positive] = None
    total_elevation_change: Optional[Positive] = None

    def get_split_distance(self):
        return self.end_loc - self.start_loc

    def get_volume(self):
        if self.pe is not None:
            return self.pe * self.get_split_distance()

The two variations of the BaseRunClass, TrainingRun and Race, have small varations that differentiate them. Namely Race requires declairing a race name and has an additional optional input for a RaceType class to distinguish between XC, Indoor Track, Outdoor Track, or Road Racing. For TrainingRun, the extension onto BaseRunClass adds elapsed_time to differentiate between the moving time and the total time and a flag for the run to indicate if the run violates volume or mileage limits.

Analysis

There are a few ways that these training and racing activities can be processed. One of the first ways that one can evaluate a set of training runs would be to find the total volume of a collection of workouts.

Currently, The Running API calculates volume by $V = mileage*intensity$. Practically, over a collection of runs, this is computed using $V=\sum_{i=0}^{N} m(i)pe(i)$ where $m(i)$ is the total miles for run $i$ and $pe(i)$ is the percieved effort, usually expressed as a range from 1-10. There are certainly many other ways to compute volume, but this appears simple enough to be useful to a broad range of users.

Let's take a collection of training runs over the course of a week runs, collected using the get_training_runs function, and calculate the volume using the above fomula assuming percieved effort values are included.

from running_api.analysis.metrics import collection_volume

weekly_volume = collection_volume(runs)

The user can also set flags based on volume to evaluate if some volume or mileage limit has been reached.

from running_api.analysis.flags import is_high_volume_collection, is_high_mileage_collection

high_volume = is_high_volume_collection(
    runs, 
    volume_limit = 500, 
    distance_units = "km", # set distance to km
)
high_mileage = is_high_mileage_collection(
    runs, 
    mileage_limit = 40, 
    distance_units = "mi", # distance in miles
)

Getting Weekly Statistics

Let's say that a user is interested in understanding how weekly mileage and pace has changed over a given build phase of marathon preparation. If the user has been bulding through the last 12 weeks, then they can use the get_previous_weekly_metrics function provided.

from running_api.utils.strava_utils import StravaActivitiesClient
from running_api.schemas.schemas import AthleteStravaAccessToken, StravaClientConfig
from running_api.analysis.macro_analysis import get_previous_weekly_metrics

athlete_token = AthleteStravaAccessToken(
    athlete_name = "Test Athlete",
    token_type = "Bearer",
    access_token = "Test Token",
    expires_at = 100000,
    expires_in = 100000,
    refresh_token = "Test Refresh Token",
)

client_config = StravaClientConfig(
    client_id = 10000,
    client_secret = "Test Secret",
)

# initialize the client
client = StravaActivitiesClient(
    athlete_token,
    client_config
)


weekly_df, runs_df = get_previous_weekly_metrics(client, previous_weeks = 12)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

the_running_api-0.3.0.tar.gz (5.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

the_running_api-0.3.0-py3-none-any.whl (5.5 kB view details)

Uploaded Python 3

File details

Details for the file the_running_api-0.3.0.tar.gz.

File metadata

  • Download URL: the_running_api-0.3.0.tar.gz
  • Upload date:
  • Size: 5.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.0.1 CPython/3.12.8

File hashes

Hashes for the_running_api-0.3.0.tar.gz
Algorithm Hash digest
SHA256 fb4016df984615a3b2038924e2bca223b251a632d93d2e1ef023c92a56292fb7
MD5 ec18879b5dfa768ddab53586bf75f1a8
BLAKE2b-256 de71a411065568f5492e08afef2a4813245c2680197aac5d257d3f0b7db17a1b

See more details on using hashes here.

Provenance

The following attestation bundles were made for the_running_api-0.3.0.tar.gz:

Publisher: publish.yml on csacco1/running-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file the_running_api-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for the_running_api-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 aed258addceb2f9c451cf92c96dabbe0a3b50e9f3550d3b9bb057223a75bca60
MD5 aece9f89b740b949c35aa4cd0ce73a30
BLAKE2b-256 b40659af971f1b4702ef7940ba879c5c64592e121d390113fd05039bede6d513

See more details on using hashes here.

Provenance

The following attestation bundles were made for the_running_api-0.3.0-py3-none-any.whl:

Publisher: publish.yml on csacco1/running-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page