Skip to content

Analytics recipes

These examples start with a filtered pandas.DataFrame returned by PyroxClient. Keep gender and division filters explicit; a pooled comparison can put athletes doing different work into the same benchmark.

Compare station splits with z-scores

This calculation measures each station against the mean and standard deviation for one race cohort:

import numpy as np
import pandas as pd

race = client.get_race(
    season=7,
    location="london",
    gender="male",
    division="open",
)

station_cols = [
    "skiErg_time",
    "sledPush_time",
    "sledPull_time",
    "burpeeBroadJump_time",
    "rowErg_time",
    "farmersCarry_time",
    "sandbagLunges_time",
    "wallBalls_time",
]

field = race[station_cols].astype(float)
means = field.mean()
stds = field.std(ddof=0).replace(0, np.nan)
z_scores = (field - means) / stds

race_z = pd.concat([race[["name"]], z_scores], axis=1)

For split times, a negative z-score means faster than the cohort mean. Missing splits remain NaN; a station with no variation also produces NaN because a z-score would have no useful denominator.

Express finish rank as a percentile

Lower finish times are better, so rank in descending order when 1.0 should mean faster than the whole cohort:

race = race.sort_values("total_time")
race["finish_percentile"] = race["total_time"].rank(
    method="average",
    ascending=False,
    pct=True,
)

Tied times receive the average rank. Drop missing total_time values before comparing counts if incomplete results are present.

Group finish times into bands

bins = [0, 55, 60, 65, 70, float("inf")]
labels = ["under 55", "55–60", "60–65", "65–70", "70+"]

race["finish_band"] = pd.cut(
    race["total_time"],
    bins=bins,
    labels=labels,
    right=False,
)

summary = (
    race.groupby("finish_band", observed=False)["total_time"]
    .agg(["count", "median"])
    .reset_index()
)

right=False puts an exact 60-minute result in the 60–65 band. Change the edge rule if your published analysis uses a different convention, and write that convention beside the chart.

Find an athlete's largest station shares

This reports how much of an athlete's station time each workout consumed. It doesn't include running or roxzone time.

athlete = client.get_athlete_in_race(
    season=7,
    location="london",
    athlete_name="surname, name",
).iloc[0]

station_times = athlete[station_cols].astype(float)
station_share = (station_times / station_times.sum()).sort_values(ascending=False)
print(station_share.head())

A large share can mean a slow station, but it can also reflect the normal cost of that workout. Compare the shares with the race cohort before calling one a personal weakness.