Skip to main content
Version: 0.1.5

SOCCER — additional Python functions

Hand-written wrappers, loaders, and helpers in sportsdataverse.soccer not covered by the generated API-endpoint reference above.

kloppy open event data​

NotFittedError​

NotFittedError(...)

The grid is all zeros: call fit or load a model first.

XThreat​

XThreat(grid: 'Optional[np.ndarray]' = None, *, l: 'int' = 16, w: 'int' = 12, eps: 'float' = 1e-05, max_iter: 'int' = 1000, meta: 'Optional[dict[str, Any]]' = None) -> 'None'

A fitted Expected Threat grid.

Parameters

ParameterTypeDefaultDescription
gridOptional[ndarray]NoneAn existing (w, l) array (row 0 = the top of the pitch); None for an unfitted model.
lint16Cells along the pitch length.
wint12Cells across the pitch width.
epsfloat1e-05Convergence tolerance on the absolute change of every cell.
max_iterint1000Iteration cap; exceeding it raises RuntimeError.
metaOptional[dict[str, Any]]NoneFree-form provenance stored in the JSON.

Example

from sportsdataverse.soccer import XThreat, soccer_open_dataset, soccer_spadl
actions = soccer_spadl(soccer_open_dataset("statsbomb", 8658))
model = XThreat().fit(actions)
actions = actions.with_columns(model.rate(actions))

Methods

XThreat.fit​

XThreat.fit(actions: 'pl.DataFrame') -> 'XThreat'

Fit the grid on SPADL actions by value iteration.

Parameters

ParameterTypeDefaultDescription
actionsDataFrameSPADL actions with type_name, result_name and start/end coordinates.

Returns

This model, fitted in place.

Example

from sportsdataverse.soccer import XThreat, soccer_open_dataset, soccer_spadl
model = XThreat().fit(soccer_spadl(soccer_open_dataset("statsbomb", 8658)))
print(model.iterations)

XThreat.from_json​

XThreat.from_json(path: 'Union[str, Path]') -> 'XThreat'

Read this module's format or socceraction's bare nested list.

Parameters

ParameterTypeDefaultDescription
pathUnion[str, Path]A JSON file written by to_json or socceraction's save_model.

Returns

The loaded XThreat.

Example

from sportsdataverse.soccer import XThreat
model = XThreat.from_json("xthreat.json")

XThreat.rate​

XThreat.rate(actions: 'pl.DataFrame') -> 'pl.Series'

Rate each action: end-cell minus start-cell value for successful passes, dribbles and crosses.

Parameters

ParameterTypeDefaultDescription
actionsDataFrameSPADL actions with type_name, result_name and start/end coordinates.

Returns

A Float64 series named xt_value; null for actions xT does not value.

Example

from sportsdataverse.soccer import load_xthreat_model
actions = actions.with_columns(load_xthreat_model().rate(actions))

XThreat.to_json​

XThreat.to_json(path: 'Union[str, Path]') -> 'None'

Write {"xT": grid, "w": .., "l": .., "meta": {..}} (readable by from_json).

Parameters

ParameterTypeDefaultDescription
pathUnion[str, Path]Destination file.

Example

model.to_json("xthreat.json")

load_xthreat_model​

load_xthreat_model() -> 'XThreat'

The bundled grid fit on StatsBomb open data (see meta for competitions, counts and license).

Returns

The fitted XThreat shipped with the package.

Example

from sportsdataverse.soccer import load_xthreat_model
model = load_xthreat_model()
print(model.xT.shape, model.meta["matches"])

soccer_events_to_frame​

soccer_events_to_frame(dataset: 'Any', *, return_as_pandas: 'bool' = False) -> 'Union[pl.DataFrame, pd.DataFrame]'

Turn a kloppy dataset (any provider, any file) into a tidy frame.

The one place the package's column convention is applied to kloppy output: one row per event, columns snake-cased (kloppy's own names -- event_id, event_type, period_id, timestamp, team_id, player_id, coordinates_x, coordinates_y, ... -- already are, so this is a no-op guard for any extra column).

Parameters

ParameterTypeDefaultDescription
datasetAnyA kloppy EventDataset (or any dataset with to_df), e.g. from kloppy.statsbomb.load(event_data=..., lineup_data=...) or kloppy.opta.load(...).
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

A polars DataFrame (pandas with return_as_pandas=True), one row per event. Coordinates are in the dataset's coordinate system -- kloppy's default is a 0-1 normalized pitch; pass coordinates="statsbomb" (etc.) to kloppy's loader to keep the provider's units.

col_nametypedescription
event_idcharacter
event_typecharacter
period_idinteger
timestampcharacter
end_timestampcharacter
ball_statecharacter
ball_owning_teamcharacter
team_idcharacter
player_idcharacter
coordinates_xdouble
coordinates_ydouble
resultcharacter
successlogical
end_coordinates_xdouble
end_coordinates_ydouble
receiver_player_idcharacter
set_piece_typecharacter
body_part_typecharacter
is_under_pressurelogical
pass_typecharacter
duel_typecharacter
goalkeeper_typecharacter
is_counter_attacklogical
card_typecharacter

Example

from kloppy import statsbomb
from sportsdataverse.soccer import soccer_events_to_frame
ds = statsbomb.load(event_data="8658.json", lineup_data="lineups_8658.json")
df = soccer_events_to_frame(ds)
print(df.shape)

# Useful parameter combination

df_pd = soccer_events_to_frame(ds, return_as_pandas=True)

# Pipeline next step (one line)

df.filter(pl.col("event_type") == "SHOT").select("player_id", "coordinates_x", "coordinates_y")

soccer_open_dataset​

soccer_open_dataset(provider: 'str', match_id: 'Union[int, str]', **kwargs: 'Any') -> 'Any'

Load one match of a provider's free open event data as a kloppy EventDataset.

provider="statsbomb" reads StatsBomb open data (https://github.com/statsbomb/open-data) through kloppy.statsbomb.load_open_data(match_id=...). That data is free for research and non-commercial use only, under StatsBomb's open-data license -- read it before publishing anything built on it. This is the input soccer_spadl expects; soccer_open_events is the same load flattened to a frame.

Parameters

ParameterTypeDefaultDescription
providerstrOpen-data provider key; currently "statsbomb".
match_idUnion[int, str]The provider's match id (StatsBomb: e.g. 8658 -- France v Croatia, 2018 World Cup final).

Returns

A kloppy EventDataset.

Example

from sportsdataverse.soccer import soccer_open_dataset, soccer_spadl
dataset = soccer_open_dataset("statsbomb", 8658)
actions = soccer_spadl(dataset)

# Pipeline next step (one line)

frame = dataset.to_df(engine="polars")

soccer_open_events​

soccer_open_events(provider: 'str', match_id: 'Union[int, str]', *, return_as_pandas: 'bool' = False, **kwargs: 'Any') -> 'Union[pl.DataFrame, pd.DataFrame]'

Load one match of a provider's free open event data as a tidy frame.

provider="statsbomb" reads StatsBomb open data (https://github.com/statsbomb/open-data) through kloppy.statsbomb.load_open_data(match_id=...). That data is free for research and non-commercial use only, under StatsBomb's open-data license -- read it before publishing anything built on it. Other kloppy open samples (Metrica, SkillCorner) follow the same shape and are added on request.

Parameters

ParameterTypeDefaultDescription
providerstrOpen-data provider key; currently "statsbomb".
match_idUnion[int, str]The provider's match id (StatsBomb: e.g. 8658 -- France v Croatia, 2018 World Cup final).
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

A polars DataFrame (pandas with return_as_pandas=True), one row per event; see soccer_events_to_frame for the columns.

col_nametypedescription
event_idcharacter
event_typecharacter
period_idinteger
timestampcharacter
end_timestampcharacter
ball_statecharacter
ball_owning_teamcharacter
team_idcharacter
player_idcharacter
coordinates_xdouble
coordinates_ydouble
resultcharacter
successlogical
end_coordinates_xdouble
end_coordinates_ydouble
receiver_player_idcharacter
set_piece_typecharacter
body_part_typecharacter
is_under_pressurelogical
pass_typecharacter
duel_typecharacter
goalkeeper_typecharacter
is_counter_attacklogical
card_typecharacter

Example

from sportsdataverse.soccer import soccer_open_events
df = soccer_open_events("statsbomb", 8658)
print(df.shape)

# Useful parameter combination

df_pd = soccer_open_events("statsbomb", 8658, coordinates="statsbomb", return_as_pandas=True)

# Pipeline next step (one line)

df.filter(pl.col("event_type") == "SHOT").group_by("team_id").len()

soccer_spadl​

soccer_spadl(dataset: 'Any', *, game_id: 'Optional[Union[int, str]]' = None, return_as_pandas: 'bool' = False) -> "Union[pl.DataFrame, 'pd.DataFrame']"

Convert a kloppy event dataset to SPADL actions on the 105 x 68 pitch.

Every action attacks left to right (kloppy ACTION_EXECUTING_TEAM orientation), so a frame from any provider kloppy reads is comparable. StatsBomb is the tested path.

Parameters

ParameterTypeDefaultDescription
datasetAnyA kloppy EventDataset (e.g. from soccer_open_dataset).
game_idOptional[Union[int, str]]NoneGame identifier when the dataset's metadata carries none.
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

One row per on-ball action with the SPADL columns (type_name, result_name, bodypart_name, start/end coordinates in meters, time_seconds from the period's kick-off). Empty dataset -> zero-row frame with the same schema.

col_nametypedescription
game_idcharacter
original_event_idcharacter
action_idinteger
period_idinteger
time_secondsdouble
team_idcharacter
player_idcharacter
start_xdouble
start_ydouble
end_xdouble
end_ydouble
bodypart_idinteger
bodypart_namecharacter
type_idinteger
type_namecharacter
result_idinteger
result_namecharacter

Example

from sportsdataverse.soccer import soccer_open_dataset, soccer_spadl
actions = soccer_spadl(soccer_open_dataset("statsbomb", 8658))
print(actions.shape)

# Pipeline next step (one line)

actions.filter(pl.col("type_name") == "shot").group_by("team_id").len()

soccer_xthreat_rate​

soccer_xthreat_rate(actions: 'pl.DataFrame', model: 'Optional[XThreat]' = None, *, return_as_pandas: 'bool' = False) -> 'Union[pl.DataFrame, pd.DataFrame]'

Append xt_value (Expected Threat added by each successful pass, dribble or cross) to a SPADL frame.

Parameters

ParameterTypeDefaultDescription
actionsDataFrameSPADL actions from soccer_spadl (needs type_name, result_name, start/end coordinates).
modelOptional[XThreat]NoneA fitted XThreat; None uses the bundled grid.
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

actions with a Float64 xt_value column (null for actions xT does not value).

col_nametypedescription
game_idcharacter
original_event_idcharacter
action_idinteger
period_idinteger
time_secondsdouble
team_idcharacter
player_idcharacter
start_xdouble
start_ydouble
end_xdouble
end_ydouble
bodypart_idinteger
bodypart_namecharacter
type_idinteger
type_namecharacter
result_idinteger
result_namecharacter
xt_valuedouble

Example

import polars as pl
from sportsdataverse.soccer import soccer_open_dataset, soccer_spadl, soccer_xthreat_rate
actions = soccer_xthreat_rate(soccer_spadl(soccer_open_dataset("statsbomb", 8658)))
print(actions.group_by("player_id").agg(pl.col("xt_value").sum()).sort("xt_value", descending=True).head())