Skip to main content

Standardize cross country, track, and road-race times with NRCD formulas (weather, grade, altitude, distance, indoor track, and more).

Project description

nrcd

Python library for National Running Club Database (NRCD) performance standardization (cross country, track, road race). Implements the formulas documented in the NRCD paper preprint (not yet officially published; arXiv preprint forthcoming).

GitHub Python 3.10+ PyPI License: MIT CI

Use this to standardize your own race results — no NRCD dataset download required.

Quick start

Times can be seconds or clock strings ("22:15", "4:12.00", "10.52"). XC distances accept m/km/mi or labels like "5k". Invalid time, distance, or unit strings raise ValueError with a short hint (they do not return NaN silently).

Cross country — pass a target distance when you want a Riegel conversion (e.g. 8000m). Without it, the standardized time stays at the race distance.

Cross country — 5K in 22:15, target distance 8000m

from nrcd.standardize import format_time, standardize_xc

std = standardize_xc(
    "22:15",
    gender="M",
    reported_distance="5k",
    target_distance_m=8000,  # 8000m
    # target_distance="8k",
    # target_distance=8, target_distance_unit="km",
    # target_distance=4.97, target_distance_unit="mi",  # ~8000m
)
print(format_time(std))
# → 36:31.94

Cross country — 8 km with weather, grade, altitude, target distance 8000m

from nrcd.standardize import format_time, standardize_xc

std = standardize_xc(
    "27:30",
    gender="M",
    reported_distance=8,
    distance_unit="km",
    actual_distance=8.01,
    temperature=72,          # °F (default)
    dew_point=65,
    elevation_gain=2.5,      # % grade (default)
    elevation_loss=2.5,
    meet_elevation=5200,     # feet (default)
    target_distance_m=8000,  # 8000m
)
print(format_time(std))
# → 26:42.19

Outdoor track — 100m with wind

from nrcd.standardize import standardize_outdoor_track

std = standardize_outdoor_track(
    "13.52",
    gender="F",
    event_name="100m",
    wind_mps=2.0,
)
print(f"{std:.3f} s")
# → 13.630 s

Indoor track — 200m on a banked 200m oval

from nrcd.standardize import standardize_indoor_track

std = standardize_indoor_track(
    "21.80",
    gender="M",
    event_name="200m",
    lap_length_m=200,
    banked=True,
)
print(f"{std:.3f} s")
# → 22.588 s

Road — half marathon with weather and grade

from nrcd.standardize import format_time, standardize_road

std = standardize_road(
    "1:25:30",
    gender="F",
    event_name="Half Marathon",
    temperature=55,
    dew_point=48,
    elevation_gain=1.2,
    elevation_loss=1.2,
)
print(format_time(std))
# → 1:25:40.54 (same event distance; no Riegel target conversion)

Pipelines

Sport Function What differs
Cross country standardize_xc Weather, grade, altitude; optional Riegel to a target distance (e.g. 8000m)
Road standardize_road Same weather / grade / altitude as XC; distance from event_name; no Riegel target
Outdoor track standardize_outdoor_track Sprint wind, weather, grade, altitude
Indoor track standardize_indoor_track Lap/bank venue factors; no wind

standardize_result remains available when you need a custom sport_name string.

Factor breakdown — see why a time changed (weather, grade, altitude, wind, Riegel):

from nrcd.standardize import format_time, standardize_xc_detail

detail = standardize_xc_detail(
    "27:30",
    gender="M",
    reported_distance=8,
    distance_unit="km",
    temperature=72,
    dew_point=65,
    elevation_gain=2.5,
    elevation_loss=2.5,
    meet_elevation=5200,
    target_distance_m=8000,
)
print(format_time(detail.std_sec))
for step in detail.steps:
    print(step.name, step.factor, step.note or "")
# weather 0.98…  |  grade 1.02…  |  altitude …  |  target_distance … (8000m → 8000m)

Use standardize_result_detail for track/road (includes wind on outdoor sprints) or standardize_seconds_detail(ctx) from a RaceContext.

Batch — standardize many rows (pip install "nrcd[data]"):

from nrcd.standardize import standardize_dataframe

out = standardize_dataframe(df)  # adds std_time_sec
out = standardize_dataframe(df, detail=True)  # also std_detail per row

# Backfill weather/altitude then standardize (pip install "nrcd[data,apis]")
out = standardize_dataframe(df, enrich=True)

Column names map to RaceContext fields automatically (result_timetime_str, altitudemeet_elevation, meet_citycity, etc.). Override with column_map={"time_str": "result_time"}.

Rows at the same meet (same city/date/start time) share the enrich cache — 100 results trigger one weather API lookup, not 100. Or enrich only:

from nrcd.standardize import enrich_dataframe

df = enrich_dataframe(df, fetch_altitude=False)  # weather only

# Track API calls (cache misses only; same meet → one OpenWeather lookup)
from nrcd.standardize import DataframeBatchResult, enrich_dataframe

result = enrich_dataframe(df, return_usage=True)
assert isinstance(result, DataframeBatchResult)
print(result.api_usage.to_dict())  # openweather_geocode, openweather_timemachine, openweather_total, …

Unit switches (optional kwargs on all pipelines):

Field Default Alternative
temperature, dew_point °F temp_unit="C"
meet_elevation feet venue_elevation_unit="m"
elevation_gain, elevation_loss % grade grade_input="feet" or "m"

More examples

Runnable scripts in examples/:

Script
XC xc_examples.py, compare_improvement.py, standardize_one_result.py
Track track_outdoor_examples.py, track_indoor_examples.py, track_compare_meets.py
Both race_context_example.py, load_dataset_example.py

load_dataset_example.py exits with code 1 if Zenodo CSVs are not in data/ — that is expected without the optional dataset.

pip install nrcd
python examples/xc_examples.py
python examples/track_outdoor_examples.py

Install

Requires Python 3.10+.

pip install nrcd              # after first PyPI release
pip install "nrcd[apis]"      # optional: weather / elevation APIs
pip install "nrcd[data]"      # optional: Zenodo CSV helpers (pandas)

Until the package is on PyPI, install from source:

git clone https://github.com/National-Running-Club-Database/nrcd
cd nrcd
pip install -e .

From source (development):

git clone https://github.com/National-Running-Club-Database/nrcd
cd nrcd
pip install -e ".[dev]"
pip install -e ".[dev,apis]"

API keys (optional — nrcd.enrich only)

Standardization does not need API keys. Use enrichment only when backfilling weather or meet altitude from city/state.

  1. pip install "nrcd[apis]"
  2. OpenWeatherAPI keys → set NRCD_OPENWEATHER_API_KEY
  3. For weather at a race date/time, also TimeZoneDBNRCD_TIMEZONE_API_KEY
  4. Meet altitude uses free USGS EPQS (no key) after OpenWeather geocodes the city
export NRCD_OPENWEATHER_API_KEY="your_key"
export NRCD_TIMEZONE_API_KEY="your_key"   # weather only
export NRCD_GEOCODE_COUNTRY_SUFFIX=US     # optional; ISO country for geocoding

Live tests (optional): copy local_api_keys.env.examplelocal_api_keys.env (gitignored), add your OpenWeather key, then pytest -m live_api -v. AQI history starts 2020-11-27; default test date is 2024-10-12.

Full walkthrough: API keys guide. In Python: from nrcd.enrich import API_GUIDE; print(API_GUIDE).

Historical OpenWeather timemachine weather may require a paid OpenWeather plan; geocoding + USGS altitude often work on the free tier.

Geographic coverage (nrcd.enrich)

Method Example Weather Altitude (USGS)
US city + state city="Notre Dame", state="IN" ✅ (US)
City + country city="London", country="GB" ⚠️ set meet_elevation non-US
Free-form query geocode_query="Paris,FR" ⚠️ same
Config / env default country EnrichConfig(geocode_country_suffix="CA") or NRCD_GEOCODE_COUNTRY_SUFFIX=CA ⚠️ same
Lat/lon latitude=51.5, longitude=-0.12 ✅ global ⚠️ US-focused EPQS
import datetime as dt
from nrcd.enrich import EnrichConfig, enrich_race_context
from nrcd.standardize import RaceContext

# International weather — city + country (no US state required)
ctx = RaceContext(
    time_str="15:30",
    gender="M",
    sport_name="Cross Country",
    reported_distance="10k",
    city="London",
    country="GB",
    event_date=dt.date(2024, 10, 12),
    event_time=dt.time(11, 0),
)
enrich_race_context(ctx, fetch_altitude=False)  # skip USGS outside US

# Or free-form geocode query
ctx.geocode_query = "Paris,FR"

Standardization has no geographic limit — only optional enrichment does.

Do you need the NRCD dataset?

Use case Zenodo CSVs needed?
Standardize your own results No
examples/ scripts No
nrcd.enrich API backfill No
examples/load_dataset_example.py only Yes (optional)

Optional public export: Zenodo dataset (see data/README.md).

API

Full parameter tables: from nrcd import PARAMETERS_DOC or help(nrcd.standardize).

Entry points

Quick start and examples import pipelines from nrcd.standardize. The same standardize_* helpers are also on top-level nrcd; utilities like format_time stay on nrcd.standardize only.

Function Import from Use for
standardize_xc nrcd or .standardize Cross country — optional target distance (target_distance_m, target_distance + unit, or "8k")
standardize_road nrcd or .standardize Road / marathon — event_name; weather, grade, altitude
standardize_outdoor_track nrcd or .standardize Outdoor track — event_name; wind on sprints
standardize_indoor_track nrcd or .standardize Indoor track — event_name; lap length / banking
standardize_result nrcd or .standardize Low-level — any sport_name (advanced)
standardize_seconds nrcd or .standardize Dispatch from a RaceContext / XCRaceContext row
standardize_xc_detail .standardize XC pipeline with per-step breakdown (StandardizeDetail)
standardize_result_detail .standardize Track/road pipeline with breakdown (wind, weather, grade, …)
standardize_seconds_detail .standardize Breakdown from a RaceContext row
standardize_dataframe .standardize Batch rows → std_time_sec; optional enrich=True (nrcd[data,apis])
enrich_dataframe .standardize Batch backfill weather/altitude on rows (cache dedupes per meet)
enrich_race_context nrcd.enrich Fill missing weather/altitude on a context (pip install "nrcd[apis]")

nrcd.standardize — pipelines & context

Name Description
RaceContext Dataclass for one result (XC or track); time_str or time_sec
XCRaceContext XC-focused RaceContext subclass
StandardizeDetail raw_sec, std_sec, and ordered steps from a *_detail call
StandardizeStep One step: name, factor, delta_sec, time_before_sec, …
row_to_race_context Build RaceContext from one DataFrame row
resolve_column_map Map RaceContext fields → DataFrame columns
COLUMN_ALIASES Default NRCD column aliases (result_time, altitude, …)
StandardizeConfig Paper coefficients (Riegel exponents, heat k, grade bases, …)
PARAMETERS_DOC Full required/optional parameter reference (text)
PARAMETER_SPECS Same metadata as structured ParameterSpec tuples
parameter_specs() Return PARAMETER_SPECS
`required_for("xc" "road"

nrcd.standardize — time, distance, units

Name Description
parse_time "22:15", "1:10:13", or seconds → float seconds
format_time Seconds → clock string; returns "" for negative/NaN/inf
parse_distance "5k", 8, "8000m" + unit → meters
distance_to_meters Numeric distance with distance_unit (m / km / mi)
c_to_f, f_to_c Temperature conversion
feet_to_meters, meters_to_feet Length conversion
temperature_to_fahrenheit Value + temp_unit (F / C) → °F
venue_elevation_to_feet Meet altitude + venue_elevation_unit (ft / m) → ft
grade_percent_from_feet Vertical ft over course → % grade
grade_percent_from_meters Vertical m over course → % grade
resolve_grade_percent Normalize gain/loss with grade_input (percent / feet / m)

nrcd.standardize — factors & sport helpers

Name Description
weather_factor Heat slowdown multiplier from temp + dew point (°F)
heat_index, heat_slowdown_percent Hadley-style heat index components
elevation_factor Maurer grade multiplier from % gain/loss
apply_course_grade_factor Grade step on a race time
warn_one_sided_course_grade Warn when only gain or only loss is set
apply_meet_altitude Peronnet meet-altitude correction
peronnet_f_alt, sea_level_time_seconds Altitude factor and sea-level equivalent
resolve_meet_altitude_inputs Parse elevation + barometric pressure from a record
barometric_pressure_hpa_from_record hPa from row fields
barometric_pressure_torr_from_hpa, parse_barometric_pressure_hpa Pressure unit helpers
riegel_convert, riegel_exponent Distance conversion
xc_target_distance_m Config helper for target distance in meters (8000m M / 6000m F)
apply_factors Multiply time by weather/grade/altitude/track/wind factors
is_cross_country, is_track, is_outdoor_track, is_indoor_track Sport name checks
normalize_sport_name, pipeline_kind Sport string normalization / xc vs track

nrcd.data — Zenodo CSV helpers

Name Description
meet_altitude_column Altitude column name in a meet DataFrame
meet_altitude_ft_from_record Meet altitude (ft) from a merged row + course details
derive_course_details_fields Derived weather/grade fields from course details

nrcd.enrich — optional APIs (pip install "nrcd[apis]")

Name Description
API_GUIDE Full signup guide (text); see also API keys guide
EnrichConfig Throttle intervals, cache TTL, API keys
api_keys_from_env Load keys from environment
fetch_weather Temperature, dew point, humidity, AQI — global with lat/lon; US city/state by default
lookup_altitude_ft, lookup_altitude_detail, lookup_elevation_ft Meet altitude (USGS EPQS, ft) — US city/state path; set meet_elevation for non-US
enrich_race_context, enrich_race_context_result Backfill a RaceContext in place
run_enrich_jobs, EnrichJob, JobResult Batch enrichment with thread pool
EnrichResult, ApiUsage, WeatherData, AltitudeResult Result / usage dataclasses
cache_stats, clear_enrich_cache, reset_throttle_state Cache and rate-limit controls
AQI_HISTORY_AVAILABLE_FROM, AQI_HISTORY_AVAILABLE_UNIX AQI history window constants

Citation

NRCD: An Open Database of Collegiate Running with Unified Performance Standardization
Jonathan A. Karr Jr, Ryan M. Fryer, Ben Darden, Nicholas Pell, Kayla Ambrose, Evan Hall, Ramzi K. Bualuan, and Nitesh V. Chawla.
Preprint PDF (not yet officially published). arXiv preprint (forthcoming).

Dataset (if using Zenodo export): Zenodo dataset

Author

Jonathan Karr ORCID Email

Development

pytest

See CONTRIBUTING.md for setup, tests, and pull request guidelines.

See Changelog. Package version: src/nrcd/init.py (__version__).

License: MIT

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

nrcd-0.1.3.tar.gz (51.0 kB view details)

Uploaded Source

Built Distribution

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

nrcd-0.1.3-py3-none-any.whl (61.0 kB view details)

Uploaded Python 3

File details

Details for the file nrcd-0.1.3.tar.gz.

File metadata

  • Download URL: nrcd-0.1.3.tar.gz
  • Upload date:
  • Size: 51.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nrcd-0.1.3.tar.gz
Algorithm Hash digest
SHA256 0b00da42280d57c4064f044c53c447a50b8f5b0495b2a53d1fa940a408a600f8
MD5 f10c7367b3732b907d3c21598c1380a8
BLAKE2b-256 54e010663566fa3c11e6c786f6df099493a3c2f3a60e386e62e554f165eaeee0

See more details on using hashes here.

Provenance

The following attestation bundles were made for nrcd-0.1.3.tar.gz:

Publisher: publish.yml on National-Running-Club-Database/nrcd

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

File details

Details for the file nrcd-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: nrcd-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 61.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nrcd-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d47a2b630bdcb7f16df843b592323f92580b483e76f4fe2ffaa2edc5e8db8f9d
MD5 181ca1744ab20107aaacb6d40136ea97
BLAKE2b-256 ff3528cb6df593f17d9204a4021d640f01f8344697623a682b297e4d3c6cae11

See more details on using hashes here.

Provenance

The following attestation bundles were made for nrcd-0.1.3-py3-none-any.whl:

Publisher: publish.yml on National-Running-Club-Database/nrcd

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