withdrawal data

withdrawal data#

The withdrawal data is defined at the rally level for each category and identifies the teams/crews that have withdrawn during each stage of the rally.

# Load in the required packages
import pandas as pd
from jupyterlite_simple_cors_proxy import furl, xurl

# Generate the API URL pattern
dakar_api_template = "https://www.dakar.live.worldrallyraidchampionship.com/api/{path}"

# Define the year
YEAR = 2025
# Define the category
CATEGORY = "A"

# Define the API path to the withdrawal resource
# Use a Python f-string to instantiate variable values directly
withdrawal_path = f"withdrawal-{YEAR}-{CATEGORY}"

# Define the URL
withdrawal_url = dakar_api_template.format(path=withdrawal_path)

# Preview the path and the URL
withdrawal_path, withdrawal_url
('withdrawal-2025-A',
 'https://www.dakar.live.worldrallyraidchampionship.com/api/withdrawal-2025-A')

Within the withdrawal data, we get a list of withdrawals by stage.

# Load in data
# Use furl() to handle CORS issues in Jupyterlite
withdrawal_df = pd.read_json(furl(withdrawal_url))

# Use the stage number as the index
withdrawal_df.set_index("stage", drop=False, inplace=True)
withdrawal_df
_id _bind _updatedAt _parent list stage
stage
8 withdrawal-2025-A-8 withdrawal-2025-A 1736877543094 stage-2025-A:d7ab3acebdb4cd17e8d4af9f122fc425 [{'bib': 619, 'reason': '1', 'team': {'bib': 6... 8
7 withdrawal-2025-A-7 withdrawal-2025-A 1736877541255 stage-2025-A:fadf454f8df8f758e326ad268007a286 [{'bib': 202, 'reason': '1', 'team': {'bib': 2... 7
5 withdrawal-2025-A-5 withdrawal-2025-A 1736877542722 stage-2025-A:e2b95b95a4ae9063eb4924fc2d7cf7e7 [{'bib': 208, 'reason': '1', 'team': {'bib': 2... 5
3 withdrawal-2025-A-3 withdrawal-2025-A 1736877554109 stage-2025-A:127ed31c3eff071ba6a7e88fe01083ef [{'bib': 225, 'reason': '1', 'team': {'bib': 2... 3
9 withdrawal-2025-A-9 withdrawal-2025-A 1736943602310 stage-2025-A:5f83172bf9f0bb97d14b83a7c0099fa8 [{'bib': 213, 'reason': '1', 'team': {'bib': 2... 9
6 withdrawal-2025-A-6 withdrawal-2025-A 1736877552621 stage-2025-A:33ee273dbd14a1657513f7dab64a635d [{'bib': 239, 'reason': '1', 'team': {'bib': 2... 6
2 withdrawal-2025-A-2 withdrawal-2025-A 1736877545655 stage-2025-A:9afdc50df69247c38b23cef4ed50bd14 [{'bib': 223, 'reason': '1', 'team': {'bib': 2... 2
4 withdrawal-2025-A-4 withdrawal-2025-A 1736877547536 stage-2025-A:8002af597f2d091dc91450266fab52d6 [{'bib': 219, 'reason': '2', 'team': {'bib': 2... 4
10 withdrawal-2025-A-10 withdrawal-2025-A 1736960963500 stage-2025-A:91f8c3e34c474fe774498af9a05e08c9 [{'bib': 205, 'reason': '1', 'team': {'bib': 2... 10
# Tweak the displayu of the dataframes, wrapping the display over
# several lines if the df is too wide
from dakar_rallydj.stylers import enable_split_display

enable_split_display(width=80, max_rows=10)
# Params: width, max_colwidth, max_rows
ERROR: max_colwidth (30) greater than width (30); resetting down.
withdrawals_by_stage = withdrawal_df["list"].explode()
withdrawals_by_stage_index = withdrawals_by_stage.index

withdrawals_by_stage_df = pd.json_normalize(withdrawals_by_stage)

withdrawals_by_stage_df["stage"] = withdrawals_by_stage_index
withdrawals_by_stage_df#.head();

bib reason team.bib
619 1 619
202 1 202
206 1 206
220 1 220
231 1 231
... ... ...
439 1 439
609 1 609
205 1 205
322 1 322
640 1 640

team.brand
MAN
MINI
TOYOTA
HAN WEI MOTORSPORT
TOYOTA
...
BRP
IVECO
TOYOTA
MMP
MAN

team.model
TGA
JCW RALLY 3.0I
HILUX IMT EVO
HWM T1+
HILUX OVERDRIVE
...
CAN-AM MAVERICK XRS TURBO RR
POWERSTAR
HILUX IMT EVO
RALLY RAID
TGA

team.vehicle
Eagle Racing Team
X-RAID MINI JCW TEAM
TOYOTA GAZOO RACING
212 HANWEI MOTORSPORT TEAM
OVERDRIVE RACING
...
PEDREGÀ TEAM
De Groot Sport
TOYOTA GAZOO RACING
MMP
TH-TRUCKS TEAM

team.vehicleImg
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
...
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
https://img.aso.fr/core_ap...
nan

team.clazz
fa2b8d6b8d84480b3fd11a2fd8...
96c0869600e0013dbf5f86f60e...
96c0869600e0013dbf5f86f60e...
96c0869600e0013dbf5f86f60e...
96c0869600e0013dbf5f86f60e...
...
058d77cc7db191813c30a902a8...
fa2b8d6b8d84480b3fd11a2fd8...
96c0869600e0013dbf5f86f60e...
a0a6386a4b9a61b73b036a5096...
ec2f26ebeb14824160c7204618...

team.w2rc
False
False
False
False
False
...
False
False
False
False
False

team.competitors stage
[{'name': 'P. CALABRIA', '... 8
[{'name': 'G. CHICHERIT', ... 7
[{'name': 'G. DE VILLIERS'... 7
[{'name': 'W. HAN', 'first... 7
[{'name': 'T. PRICE', 'fir... 7
... ...
[{'name': 'R. MUÑOZ CAMARA... 4
[{'name': 'B. DE GROOT', '... 4
[{'name': 'G. BOTTERILL', ... 10
[{'name': 'R. POREM', 'fir... 10
[{'name': 'A. ALONSO ROMER... 10

The withdrawal data also includes a reason column, although I haven’t (yet!) found a metadata feed that explains the reason code values.

Crew (that is, competitors) data is provided as a list of details for each crew member. We can extract this data into its own table, and widen it by adding the stage and reason data, to provide an alternative table for looking up withdrawal information by competitor name.

withdrawn_competitors_df = (
        withdrawals_by_stage_df[['stage', 'reason', 'bib', 'team.competitors']]
        .explode('team.competitors')
        .reset_index(drop=True)
    )

# Normalize the dictionary contents and combine with competitors
withdrawn_competitors_df = pd.concat([
    withdrawn_competitors_df[['stage', 'bib', 'reason']],
    pd.json_normalize(withdrawn_competitors_df['team.competitors'])
    ], axis=1)

withdrawn_competitors_df.head()
stage bib reason name firstName lastName role gender nationality profil profil_sm podium aid
0 4 219 2 S. LOEB SEBASTIEN LOEB P m fra https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... 5dd0184f-5f90-4b42-b675-88d6501b9965
1 4 219 2 F. LURQUIN FABIAN LURQUIN C m bel https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... c640056a-79d0-4454-8e6a-c8b303620020
2 4 236 1 E. AMOS EUGENIO AMOS P m ita https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... 0575de36-4a38-431c-8892-ac9514c8ccd9
3 4 236 1 P. CECI PAOLO CECI C m ita https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... fdacd829-75f9-4cfa-9992-636c8244481e
4 4 258 1 M. WALCHER MARKUS WALCHER P m deu https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... 93be84ca-0b2b-4da0-933a-e394a8a29cdb

We note, however, that much of this information is generic and could exist in a separate competitors data table, with just a unique key reference value, such as the name, linking the withdrawn competitors table to the competitors data table.

A similar approach could be applied to the team values, extracting the data to a separate teams data table and using the team.bib as the unique key.

team_cols = [c for c in withdrawals_by_stage_df.columns if c.startswith("team")]

# Create a new dataframe, rather than a reference, by using .copy()
withdrawn_teams_df = withdrawals_by_stage_df[team_cols].copy()
withdrawn_teams_df.drop("team.competitors", axis=1, inplace=True)

withdrawn_teams_df.head()
team.bib team.brand team.model team.vehicle team.vehicleImg team.clazz team.w2rc
0 219 DACIA SANDRIDER THE DACIA SANDRIDERS https://img.aso.fr/core_app/img-motorSports-da... 96c0869600e0013dbf5f86f60e5c4da4 True
1 236 MD OPTIMUS MD RALLYE SPORT https://img.aso.fr/core_app/img-motorSports-da... f00d7ec8d2d96e9cf11aa515109376cf False
2 258 RED-LINED NAVARRA VK 56 WALCHER RACING TEAM https://img.aso.fr/core_app/img-motorSports-da... f666973e89db183ecfefc75c3af8ffb1 False
3 337 BRP CAN-AM MAVERICK X3 COLORADO MOTORSPORT NaN a0a6386a4b9a61b73b036a50966345c0 False
4 408 BRP CAN-AM MAVERICK R SOUTH RACING CAN-AM https://img.aso.fr/core_app/img-motorSports-da... 9a68ed3c41c5c7a1642df5d93458baa6 True

Create a simpler withdrawals table:

# If we have more than one team member, we will get duplicate rows
withdrawals_df = withdrawn_competitors_df[["stage", "bib", "reason"]].drop_duplicates()
withdrawals_df.head()
stage bib reason name
0 4 219 2 S. LOEB
1 4 219 2 F. LURQUIN
2 4 236 1 E. AMOS
3 4 236 1 P. CECI
4 4 258 1 M. WALCHER

And tidy up the competitors table to just leave competitor (and team bib) data:

withdrawn_competitors_df.drop("stage", axis=1, inplace=True)
withdrawn_competitors_df.drop("reason", axis=1, inplace=True)

withdrawn_competitors_df.head()
bib name firstName lastName role gender nationality profil profil_sm podium aid
0 219 S. LOEB SEBASTIEN LOEB P m fra https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... 5dd0184f-5f90-4b42-b675-88d6501b9965
1 219 F. LURQUIN FABIAN LURQUIN C m bel https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... c640056a-79d0-4454-8e6a-c8b303620020
2 236 E. AMOS EUGENIO AMOS P m ita https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... 0575de36-4a38-431c-8892-ac9514c8ccd9
3 236 P. CECI PAOLO CECI C m ita https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... fdacd829-75f9-4cfa-9992-636c8244481e
4 258 M. WALCHER MARKUS WALCHER P m deu https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... https://img.aso.fr/core_app/img-motorSports-da... 93be84ca-0b2b-4da0-933a-e394a8a29cdb