### Development installation Source: https://unravelsports.readthedocs.io/en/latest/getting_started/installation.html Commands to clone the repository and install in development mode. Use the appropriate extra based on your Python version. ```bash git clone https://github.com/unravelsports/unravelsports.git cd unravelsports pip install -e .[test-torch] # For Python 3.12+ # OR pip install -e .[test] # For Python 3.11 (includes Spektral) ``` -------------------------------- ### Install Spektral dependencies (Python 3.11 only) Source: https://unravelsports.readthedocs.io/en/latest/getting_started/installation.html Installation commands for Spektral and TensorFlow for graph neural networks. Only available on Python 3.11 and deprecated; use PyTorch Geometric for new projects. ```bash pip install spektral==1.2.0 pip install keras==2.14.0 # For Intel/AMD processors: pip install tensorflow>=2.14.0 # For Apple Silicon (M1/M2/M3): pip install tensorflow-macos>=2.14.0 ``` -------------------------------- ### Example: Convert soccer tracking data to PyTorch Geometric graphs Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/soccer/graphs/graph_converter.html Shows how to load tracking data, prepare it with KloppyPolarsDataset, and convert to graphs using SoccerGraphConverter. Requires kloppy and unravel.soccer modules. The example prints the number of graphs and feature dimensions. ```python >>> from unravel.soccer import KloppyPolarsDataset, SoccerGraphConverter >>> from kloppy import sportec >>> >>> # Load and prepare data >>> kloppy_dataset = sportec.load_open_tracking_data(only_alive=True) >>> polars_dataset = KloppyPolarsDataset(kloppy_dataset=kloppy_dataset) >>> polars_dataset.add_dummy_labels(by=["frame_id"]) >>> polars_dataset.add_graph_ids(by=["frame_id"]) >>> >>> # Create converter >>> converter = SoccerGraphConverter( ... dataset=polars_dataset, ... self_loop_ball=True, ... adjacency_matrix_connect_type="ball", ... adjacency_matrix_type="split_by_team", ... label_type="binary", ... ) >>> >>> # Convert to PyTorch Geometric format >>> graphs = converter.to_pytorch_graphs() >>> print(f"Created {len(graphs)} graphs") >>> print(f"Node features: {converter.n_node_features}") >>> print(f"Edge features: {converter.n_edge_features}") ``` -------------------------------- ### Development installation Source: https://unravelsports.readthedocs.io/en/latest/_sources/getting_started/installation.rst.txt Clones the repository and installs in editable mode with test dependencies. Use the appropriate command for your Python version: .[test-torch] for Python 3.12+ or .[test] for Python 3.11 (includes Spektral). ```bash git clone https://github.com/unravelsports/unravelsports.git cd unravelsports pip install -e .[test-torch] # For Python 3.12+ # OR pip install -e .[test] # For Python 3.11 (includes Spektral) ``` -------------------------------- ### Usage example for unravel.soccer.EFPI Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.soccer.EFPI.html Example demonstrating how to use EFPI with a KloppyPolarsDataset, including loading tracking data, initializing the detector, and fitting per frame, per possession, per 5-minute window, and with custom formations. ```python >>> from unravel.soccer.dataset import KloppyPolarsDataset >>> from unravel.soccer.models.formations import EFPI >>> from kloppy import datasets >>> >>> # Load tracking data >>> dataset = datasets.load(provider="skillcorner", match_id="123") >>> soccer_data = KloppyPolarsDataset(kloppy_dataset=dataset) >>> >>> # Initialize EFPI detector >>> efpi = EFPI(dataset=soccer_data) >>> >>> # Detect formations per frame >>> efpi.fit(every="frame") >>> print(efpi.output) >>> # Shows: frame_id, object_id, position, formation, is_attacking >>> >>> # Detect formations per possession >>> efpi.fit( ... every="possession", ... change_after_possession=True, # Re-detect when possession changes ... change_threshold=0.2 # Re-detect if cost improves by 20% ... ) >>> print(efpi.segments) >>> # Shows possession segments with start/end times >>> >>> # Detect formations per 5-minute window >>> efpi.fit(every="5m", substitutions="drop") >>> >>> # Use custom formation templates >>> efpi_custom = EFPI( ... dataset=soccer_data, ... formations=["4-3-3", "4-2-3-1", "3-5-2"] ... ) >>> efpi_custom.fit(every="possession") ``` -------------------------------- ### Add dummy labels with examples Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/soccer/dataset/kloppy_polars.html Method to add random binary labels for testing, with examples showing different grouping options and reproducible seeds. Also includes a note on replacing with real labels. ```python def add_dummy_labels( self, by: List[str] = ["game_id", "frame_id"], random_seed: Optional[int] = None ) -> pl.DataFrame: """Add a column of random binary labels for testing/demonstration purposes. This method adds a 'label' column with random 0/1 values to the dataset. Useful for testing graph neural network pipelines before you have real labels. Args: by: Column names to group by before assigning labels. Each unique combination gets the same random label. Defaults to ["game_id", "frame_id"]. random_seed: Random seed for reproducibility. If None, labels will be different each time. Defaults to None. Returns: pl.DataFrame: The updated DataFrame with 'label' column added. Example: >>> # Add random labels, one per frame >>> dataset.add_dummy_labels(by=["frame_id"]) >>> >>> # Add labels grouped by possession >>> dataset.add_dummy_labels(by=["ball_owning_team_id", "period_id"]) >>> >>> # Reproducible labels >>> dataset.add_dummy_labels(by=["frame_id"], random_seed=42) Note: In real applications, replace this with actual labels from your data: >>> import polars as pl >>> labels = pl.DataFrame({"frame_id": [...], "label": [...]}) >>> dataset.data = dataset.data.join(labels, on="frame_id") See Also: :func:`~unravel.utils.add_dummy_label_column`: Underlying utility function. """ self.data = add_dummy_label_column( self.data, by, self._label_column, random_seed ) return self.data ``` -------------------------------- ### Convert and use Kloppy data with `KloPskyPolarsDataset` in Python example from documentation example section titled 'Example'. Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.soccer.KloppyPolarsDataset.html This example demonstrates how to use the `KuskyPolarsLibrary ```python >>> from kloppy import sportec; import unravel.skeleton.KloPskyPALLY ``` -------------------------------- ### Install Spektral dependencies (Python 3.11 only) Source: https://unravelsports.readthedocs.io/en/latest/_sources/getting_started/installation.rst.txt Installs Spektral and its dependencies for graph neural networks. Only available on Python 3.11 and is deprecated. Includes separate commands for Intel/AMD and Apple Silicon processors. ```bash pip install spektral==1.2.0 pip install keras==2.14.0 # For Intel/AMD processors: pip install tensorflow>=2.14.0 # For Apple Silicon (M1/M2/M3): pip install tensorflow-macos>=2.14.0 ``` -------------------------------- ### Usage Examples Source: https://unravelsports.readthedocs.io/en/latest/api/classifiers.html Usage examples for PyTorch Geometric and Spektral classifiers. ```APIDOC ## Usage Examples ### PyTorch Geometric ```python from unravel.classifiers import PyGLightningCrystalGraphClassifier import pytorch_lightning as pyl from torch_geometric.loader import DataLoader # Initialize model model = PyGLightningCrystalGraphClassifier() # Train trainer = pyl.Trainer(max_epochs=50) trainer.fit(model, train_loader, val_loader) # Test trainer.test(model, test_loader) ``` ### Spektral ```python from unravel.classifiers import CrystalGraphClassifier from tensorflow.keras.metrics import AUC, BinaryAccuracy from tensorflow.keras.losses import BinaryCrossentropy from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping model = CrystalGraphClassifier() model.compile( loss=BinaryCrossentropy(), optimizer=Adam(), metrics=[AUC(), BinaryAccuracy()] ) model.fit( loader_tr.load(), steps_per_epoch=loader_tr.steps_per_epoch, epochs=5, use_multiprocessing=True, validation_data=loader_va.load(), callbacks=[EarlyStopping(monitor="loss", patience=5, restore_best_weights=True)], ) from tensorflow.keras.models import load_model model_path = "models/my-first-graph-classifier" model.save(model_path) loaded_model = load_model(model_path) loader_te = DisjointLoader(test, epochs=1, shuffle=False, batch_size=batch_size) results = model.evaluate(loader_te.load()) ``` ``` -------------------------------- ### Formations parameter examples Source: https://unravelsports.readthedocs.io/en/latest/tutorials/formation_detection.html Shows how to use the formations parameter of model.fit() to specify which formations to consider. Options include None for all 65 default formations, a list of specific formations, or a single formation for validation. ```python # Use all formations result = model.fit(formations=None) # Only common formations result = model.fit(formations=["442", "433", "4231", "352", "343"]) # Single formation (useful for validation) result = model.fit(formations=["442"]) ``` -------------------------------- ### Usage example for PyGLightningCrystalGraphClassifier Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/classifiers/crystal_graph_pyg.html Demonstrates how to initialize, train, test, predict, and save/load a PyTorch Lightning model for crystal graph classification. Requires pytorch-lightning and torchmetrics. ```python >>> from unravel.classifiers import PyGLightningCrystalGraphClassifier >>> import pytorch_lightning as pyl >>> from torch_geometric.loader import DataLoader >>> >>> # Initialize model >>> model = PyGLightningCrystalGraphClassifier( ... n_layers=3, ... channels=128, ... lr=0.001 ... ) >>> >>> # Train >>> trainer = pyl.Trainer(max_epochs=50, accelerator="auto") >>> trainer.fit(model, train_loader, val_loader) >>> >>> # Test >>> trainer.test(model, test_loader) >>> >>> # Predict >>> predictions = trainer.predict(model, pred_loader) >>> >>> # Save/load checkpoint >>> trainer.save_checkpoint("model.ckpt") >>> model = PyGLightningCrystalGraphClassifier.load_from_checkpoint("model.ckpt") ``` -------------------------------- ### Change after possession parameter examples Source: https://unravelsports.readthedocs.io/en/latest/tutorials/formation_detection.html Shows how to use the change_after_possession parameter of model.fit() to control whether formation changes are allowed mid-possession. True only changes at possession boundaries (recommended), False allows changes at any time. ```python # Only change formation when possession changes (more realistic) result = model.fit(change_after_possession=True) # Allow immediate changes (may detect temporary adjustments) result = model.fit(change_after_possession=False) ``` -------------------------------- ### Initialization of PyGLightningCrystalGraphClassifier Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/classifiers/crystal_graph_pyg.html Constructor for the PyTorch Lightning wrapper. Sets up the underlying model, loss function, and training/validation/test metrics. Raises ImportError if PyTorch Lightning is not installed. ```python def __init__( self, n_layers=3, channels=128, drop_out=0.5, n_out=1, lr=0.001, weight_decay=0.0, ): if not _HAS_PYTORCH_LIGHTNING: raise ImportError( "PyTorch Lightning is required for PyGLightningCrystalGraphClassifier. " "Install it using: pip install pytorch-lightning torchmetrics" ) super().__init__() self.save_hyperparameters() self.model = PyGCrystalGraphClassifier( n_layers=n_layers, channels=channels, drop_out=drop_out, n_out=n_out ) self.criterion = torch.nn.BCELoss() # Training metrics self.train_auc = AUROC(task="binary") self.train_acc = Accuracy(task="binary") # Validation metrics self.val_auc = AUROC(task="binary") self.val_acc = Accuracy(task="binary") # Test metrics (ADD THESE!) self.test_auc = AUROC(task="binary") self.test_acc = Accuracy(task="binary") ``` -------------------------------- ### load method: load data from tracking file and set up settings and some variables. Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/american_football/dataset/dataset.html Loads the settings and reads the tracking file using scipy. ```python self.settings = self.__apply_settings(); ``` ``` pitch_length = ``` ```code sample = ``` ```code pl.scan_csv ... ``` -------------------------------- ### Install unravelsports with pip Source: https://unravelsports.readthedocs.io/en/latest/_sources/getting_started/installation.rst.txt Installs the core unravelsports package via pip. This installs the core dependencies: kloppy>=3.18.0, polars[numpy]>=1.35.0, and scipy>=1.0.0. ```bash pip install unravelsports ``` -------------------------------- ### EFPI.fit examples with different every parameters Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/soccer/models/formations/efpi.html Shows how to use the fit method with various temporal aggregation settings: per-frame, per-possession with stability threshold, per-period, 5-minute rolling window, custom formations with time window, and analyzing formation changes during first half. The change_threshold parameter only applies when every is not 'frame'. ```python >>> # Per-frame detection (no temporal aggregation) >>> efpi = EFPI(dataset=soccer_data) >>> efpi.fit(every="frame") >>> print(efpi.output.head()) >>> # Shows formation for each frame >>> >>> # Per-possession detection with stability threshold >>> efpi.fit( ... every="possession", ... change_after_possession=True, ... change_threshold=0.15 # Only update if cost improves by 15% ... ) >>> # Formation changes only when possession changes or cost improves significantly >>> >>> # Per-period detection (one formation per half) >>> efpi.fit(every="period") >>> # Single formation assignment for each period >>> >>> # 5-minute rolling window detection >>> efpi.fit(every="5m", substitutions="drop") >>> print(efpi.segments) >>> # Shows 5-minute windows with start/end times >>> >>> # Custom formations with time window >>> efpi.fit( ... start_time=pl.duration(minutes=10), ... end_time=pl.duration(minutes=20), ... period_id=1, ... every="possession", ... formations=["4-3-3", "4-2-3-1"] ... ) >>> >>> # Analyze formation changes during first half >>> efpi.fit(every="possession", period_id=1) >>> formation_changes = ( ... efpi.output ... .group_by(["team_id", "possession_id"]) ... .agg(pl.col("formation").first()) ... ) >>> print(formation_changes) ``` -------------------------------- ### Example: Compute pressing intensity with PressingIntensity Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.soccer.PressingIntensity.html Shows how to load tracking data, initialize the PressingIntensity model, fit it with custom parameters, and access the results. Requires velocity data in the dataset. The fit method accepts start_time, end_time, period_id, speed_threshold, reaction_time, time_threshold, sigma, method, ball_method, orient, and line_method. ```python >>> from unravel.soccer.dataset import KloppyPolarsDataset >>> from unravel.soccer.models import PressingIntensity >>> from kloppy import datasets >>> >>> # Load tracking data >>> dataset = datasets.load( ... provider="skillcorner", ... match_id="123", ... competition="EPL" ... ) >>> soccer_data = KloppyPolarsDataset(kloppy_dataset=dataset) >>> >>> # Initialize pressing intensity model >>> pi = PressingIntensity(dataset=soccer_data) >>> >>> # Compute pressing intensity for all frames >>> pi.fit( ... method="teams", # 11x11 matrix (attackers × defenders) ... ball_method="max", # Merge ball and ball carrier ... reaction_time=0.7, # 0.7 second defender reaction time ... time_threshold=1.5, # 1.5 second pressing window ... sigma=0.45 # Sigmoid steepness parameter ... ) >>> >>> # Access results >>> print(pi.output) >>> # Shows time_to_intercept and probability_to_intercept matrices per frame >>> >>> # Compute pressing intensity for specific period >>> pi.fit( ... start_time=pl.duration(minutes=0), ... end_time=pl.duration(minutes=5), ... period_id=1, ... method="teams" ... ) ``` -------------------------------- ### Check PyTorch Geometric dependency Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/utils/objects/graph_dataset.html Raises an ImportError if PyTorch Geometric is not installed, with installation instructions. ```python if not _HAS_TORCH_GEOMETRIC: raise ImportError( "PyTorch Geometric is required. " "Install it using: pip install torch torch-geometric" ) ``` -------------------------------- ### Install PyTorch Geometric dependencies Source: https://unravelsports.readthedocs.io/en/latest/_sources/getting_started/installation.rst.txt Installs PyTorch and related libraries for graph neural networks with PyTorch Geometric. Recommended for GNN support. ```bash pip install torch>=2.5.0 pip install torch-geometric>=2.6.0 pip install torchmetrics>=1.0.0 pip install pytorch-lightning>=2.0.0 ``` -------------------------------- ### Helper method __apply_settings returns a DefaultSettings object with provider nfl and other settings. Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/american_football/dataset/dataset.html This private method returns a `DefaultSettings` object with provider ```code return DefaultSettings( ``` ```code provider= ``` ```code 'nfl ``` ```code '+ ``` ``` __settings = ``` -------------------------------- ### Add graph IDs with examples Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/soccer/dataset/kloppy_polars.html Method to add a graph_id column for grouping frames into graph samples, with examples for different grouping strategies and a warning about splitting by graph_id to avoid data leakage. ```python def add_graph_ids(self, by: List[str] = ["game_id", "period_id"]) -> pl.DataFrame: """Add a graph_id column for grouping frames into graph samples. This method adds a 'graph_id' column that groups tracking frames into distinct graph samples for GNN training. This is crucial for proper train/test splitting to avoid data leakage. Args: by: Column names to group by. Each unique combination gets a unique graph_id. Defaults to ["game_id", "period_id"]. Returns: pl.DataFrame: The updated DataFrame with 'graph_id' column added. Example: >>> # Each frame is a separate graph >>> dataset.add_graph_ids(by=["frame_id"]) >>> >>> # Group by possession (all frames in same possession = one graph) >>> dataset.add_graph_ids(by=["ball_owning_team_id", "period_id"]) >>> >>> # Group by 10-frame sequences >>> dataset.data = dataset.data.with_columns( ... (pl.col("frame_id") // 10).alias("sequence_id") ... ) >>> dataset.add_graph_ids(by=["sequence_id"]) Important: When splitting data for training, **always split by graph_id** to avoid data leakage. Never split by row index: >>> # CORRECT: Split by graph_id >>> train, test, val = dataset.split_test_train_validation(4, 1, 1) >>> >>> # WRONG: Don't split by index >>> train = dataset[:800] # May have same game in train and test! See Also: :func:`~unravel.utils.add_graph_id_column`: Underlying utility function. :meth:`~unravel.utils.GraphDataset.split_test_train_validation`: Splitting method. """ self.data = add_graph_id_column(self.data, by, self._graph_id_column) return self.data ``` -------------------------------- ### Initialization and data preparation in __post_init__ Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/soccer/graphs/graph_converter.html Validates the dataset type, sets up pitch dimensions, label and graph ID columns, applies feature functions, performs sport-specific checks, applies graph settings, and handles padding or removal of incomplete frames. Also samples and shuffles the data. ```python def __post_init__(self): if not isinstance(self.dataset, KloppyPolarsDataset): raise ValueError("dataset should be of type KloppyPolarsDataset...") self.pitch_dimensions: MetricPitchDimensions = ( self.dataset.settings.pitch_dimensions ) self._kloppy_settings = self.dataset.settings self.label_column: str = ( self.label_col if self.label_col is not None else self.dataset._label_column ) self.graph_id_column: str = ( self.graph_id_col if self.graph_id_col is not None else self.dataset._graph_id_column ) self.dataset = self.dataset.data if not self.edge_feature_funcs: self.edge_feature_funcs = self.default_edge_feature_funcs self._verify_feature_funcs(self.edge_feature_funcs, feature_type="edge") if not self.node_feature_funcs: self.node_feature_funcs = self.default_node_feature_funcs self._verify_feature_funcs(self.node_feature_funcs, feature_type="node") self._sport_specific_checks() self.settings = self._apply_graph_settings() if self.pad: self.dataset = self._apply_padding() else: self.dataset = self._remove_incomplete_frames() self._sample() self._shuffle() ``` -------------------------------- ### _GraphDatasetMixin __init__ method Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/utils/objects/graph_dataset.html Constructor to load parameters. Accepts pickle_folder, pickle_file, graphs, format, and sample_rate. Raises NotImplementedError if no data source is provided. ```python def __init__(self, **kwargs): """ Constructor to load parameters. Args: pickle_folder: Path to folder containing .pickle.gz files pickle_file: Path to single .pickle.gz file graphs: List of graph objects (Spektral Graph, PyG Data, or dicts) format: Optional explicit format specification ('spektral' or 'pyg') sample_rate: Sampling rate (1.0 = use all data) """ self._kwargs = kwargs self._explicit_format = kwargs.get("format", None) sample_rate = kwargs.get("sample_rate", 1.0) self.sample = 1.0 / sample_rate if kwargs.get("pickle_folder", None): pickle_folder = Path(kwargs["pickle_folder"]) self.graphs = None # Loop over all .pickle.gz files in the folder for pickle_file in pickle_folder.glob("*.pickle.gz"): data = load_pickle_gz(pickle_file) if not self.graphs: self.graphs = self.__convert(data) else: self.add(data) elif kwargs.get("pickle_file", None): pickle_file = Path(kwargs["pickle_file"]) self.graphs = None data = load_pickle_gz(pickle_file) if not self.graphs: self.graphs = self.__convert(data) else: self.add(data) elif kwargs.get("graphs", None): if not isinstance(kwargs["graphs"], list): raise NotImplementedError("""data should be of type list""") self.graphs = self.__convert(kwargs["graphs"]) else: raise NotImplementedError( "Please provide either 'pickle_folder', 'pickle_file' or 'graphs' as parameter to GraphDataset" ) # Only call super().__init__ if there's a parent class that needs it # For PyGGraphDataset, Sequence doesn't take kwargs # For SpektralGraphDataset, Dataset does take kwargs try: super().__init__(**kwargs) except TypeError: # If super().__init__() doesn't accept kwargs (like Sequence), call it without args super().__init__() ``` -------------------------------- ### Add graph ID column with add_graph_id_column Source: https://unravelsports.readthedocs.io/en/latest/_sources/api/utils/helpers.rst.txt Adds a graph ID column to a dataset using add_graph_id_column from unravel.utils. The first example groups by frame_id, treating each frame as a separate graph. The second example groups by ball_owning_team_id and period_id to group by possession. ```python from unravel.utils import add_graph_id_column # Each frame is a separate graph dataset.dataset = add_graph_id_column(dataset.dataset, by=["frame_id"]) # Group by possession dataset.dataset = add_graph_id_column( dataset.dataset, by=["ball_owning_team_id", "period_id"] ) ``` -------------------------------- ### Load tracking data with Kloppy and convert to Polars Source: https://unravelsports.readthedocs.io/en/latest/_sources/tutorials/soccer_gnn.rst.txt Load tracking data from Sportec or SkillCorner using Kloppy and convert it to a Polars dataset with KloppyPolarsDataset. The Sportec example limits to 500 frames and only alive frames; the SkillCorner example excludes empty frames. ```python from kloppy import sportec, skillcorner, tracab from unravel.soccer import KloppyPolarsDataset # Sportec (DFL Open Data) kloppy_dataset = sportec.load_open_tracking_data( only_alive=True, # Only frames with ball in play limit=500 # Limit to 500 frames for testing ) # SkillCorner kloppy_dataset = skillcorner.load_open_data( only_alive=True, include_empty_frames=False ) # Convert to Polars polars_dataset = KloppyPolarsDataset(kloppy_dataset=kloppy_dataset) ``` -------------------------------- ### EFPI.__init__() Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.soccer.EFPI.html Initializes the EFPI model with the given parameters. ```APIDOC ## EFPI.__init__() ### Description Initializes the EFPI model. ### Method Constructor ### Signature `EFPI.__init__(...)` ### Parameters No parameters are documented in the source. ### Returns None ``` -------------------------------- ### Substitutions parameter examples Source: https://unravelsports.readthedocs.io/en/latest/tutorials/formation_detection.html Shows how to use the substitutions parameter of model.fit() to handle player substitutions. Options are 'drop' to remove frames with substitutions (recommended), 'keep' to include all frames, and 'interpolate' to interpolate formations across substitution events. ```python # Drop substitution frames (cleanest results) result = model.fit(substitutions="drop") # Keep all frames result = model.fit(substitutions="keep") ``` -------------------------------- ### Get dataset length Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/utils/objects/graph_dataset.html Returns the number of graphs in the dataset. ```python return len(self.graphs) ``` -------------------------------- ### BigDataBowlDataset Source: https://unravelsports.readthedocs.io/en/latest/api/american_football/dataset.html Example usage of the BigDataBowlDataset class to load and access the dataset. ```APIDOC ## BigDataBowlDataset ### Description Example of instantiating the BigDataBowlDataset class and accessing the loaded DataFrame. ### Example ```python from unravel.american_football import BigDataBowlDataset bdb_dataset = BigDataBowlDataset( tracking_file_path="tracking_week_1.csv", players_file_path="players.csv", plays_file_path="plays.csv", ) # Access the DataFrame df = bdb_dataset.dataset print(df.head()) ``` ``` -------------------------------- ### class __init__: initialize with tracking, players, and plays file paths and various speed/acceleration parameters Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/american_football/dataset/dataset.html Initializes the dataset object with file paths and optional parameters like sample_rate max_player_sQ:max_ball_speed etc. It calls self.load() at the default sample rate of 1 if not specified, and stores all parameters for later use, including a flag for orientation. This matches the class's constructor signature documented in the source.us_schema enabled. ```python self.tracking_file_path = = ``` ```python self.players_file_path = players_file_path; ``` ```code self.plays_file ``` ```python self.sample_rate = | 1 if sample_rate is None else sample_rate; ``` ```code self._max_player_schema = max_player_speed; self._max_ball_space = = max_ ``` ```python and so on and so on and so forth and so on and so on and code ``` -------------------------------- ### Property to get number of graphs Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/utils/objects/graph_dataset.html Returns the number of graphs in the dataset. ```python return len(self) ``` -------------------------------- ### python: Importing and using GraphUtility: from unravel.utils import GraphDataset; dataset = GraphPlot() Source: https://unravelsports.readthedocs.io/en/latest/_sources/api/utils/objects.rst.txt Demonstrates importing the `unravel.utils.GraphDataset,Create dataset with graphs and format='pylandSoritrain/test/val split numbers 4, he1, /Val = dataset.split_test_train_actualization(4,1,1 train,, ```from unravel.utils import GraphDataset # Create dataset dataset = GraphDataset(graphs = graphs, FORMAT = 'pyg ) # Split data train,, test, val = dataset.split_test_train_coverability(4, actual 1, (invalid) ]]}]}, from __future__: from typing import List, Optional, Union import torch.utils Tensor, TensorFloat class GraphDataset: def __. _init__(self, graphs, **kwargs: Any) ->introduce a comment. # Create dataset dataset = GraphData(graphs=graphs, FORMAT='pyg' train, test, actual_val = dataset.split_test_train_(4,1,END) # Split data train, test, actual_val = dataset. split_test_train_ (4, ``` -------------------------------- ### AmericanFootballGraphConverter Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.american_football.AmericanFootballGraphConverter.html Constructor for the AmericanFootballGraphConverter class, including parameters, exceptions, and usage example. ```APIDOC ## AmericanFootballGraphConverter ### Description Converts NFL Big Data Bowl tracking data to graph structures for GNN training. Supports PyTorch Geometric and Spektral frameworks. ### Constructor #### Parameters - **dataset** (`BigDataBowlDataset`) - Required - Preprocessed NFL tracking data with player positions, velocities, and play information. - **chunk_size** (`int`, optional) - Number of frames to process per batch for memory efficiency. Defaults to 2000. - **attacking_non_qb_node_value** (`float`, optional) - Node feature value (0-1) assigned to offensive players who are not the quarterback. Defaults to 0.1. - **graph_feature_cols** (`Optional[List[str]]`, optional) - List of column names containing graph-level features to attach to the football node. Defaults to None. - **kwargs** - Additional parameters passed to DefaultGraphConverter, including adjacency_matrix_type, label_col, graph_id_col, prediction. #### Raises - **Exception** - If dataset is not an instance of BigDataBowlDataset. - **Exception** - If label_column or graph_id_column are not strings. - **Exception** - If label_column is missing when not in prediction mode. - **Exception** - If graph_id_column is missing from dataset. - **Exception** - If attacking_non_qb_node_value is not float or int. - **Exception** - If frames with missing football or insufficient players are detected. ### Example ```python >>> from unravel.american_football.dataset import BigDataBowlDataset >>> from unravel.american_football.graphs import AmericanFootballGraphConverter >>> >>> # Load Big Data Bowl data >>> dataset = BigDataBowlDataset( ... tracking_file_path="tracking.csv", ... players_file_path="players.csv", ... plays_file_path="plays.csv" ... ) >>> >>> # Add labels and graph IDs >>> dataset.add_dummy_labels() >>> dataset.add_graph_ids() >>> >>> # Initialize converter >>> converter = AmericanFootballGraphConverter( ... dataset=dataset, ... adjacency_matrix_type="delaunay", ... label_col="label", ... graph_id_col="graph_id" ... ) >>> >>> # Convert to PyTorch Geometric format >>> pyg_dataset = converter.to_pytorch_graphs() >>> print(f"Number of graphs: {len(pyg_dataset)}") >>> print(f"Node features: {pyg_dataset[0].x.shape}") >>> print(f"Edge features: {pyg_dataset[0].edge_attr.shape}") >>> >>> # Add graph-level features (e.g., expected points) >>> # First, join expected points to dataset >>> dataset.data = dataset.data.join( ... expected_points_df, ... on=["game_id", "play_id"], ... how="left" ... ) >>> converter = AmericanFootballGraphConverter( ... dataset=dataset, ... graph_feature_cols=["expected_points", "win_probability"] ... ) >>> pyg_dataset = converter.to_pytorch_graphs() ``` ``` -------------------------------- ### Get player by ID Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/soccer/graphs/graph_converter.html Returns the player from `self.settings.players` with the matching `player_id`, or `None` if not found. ```python def get_player_by_id(self, player_id): for player in self.settings.players: if player["player_id"] == player_id: return player return None ``` -------------------------------- ### BigDataBowlDataset.__init__ Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.american_football.BigDataBowlDataset.html Initializes the BigDataBowlDataset with paths to tracking, players, and plays CSV files, along with optional parameters for sampling, speed/acceleration filtering, and orientation normalization. ```APIDOC ## BigDataBowlDataset.__init__ ### Description Initializes the BigDataBowlDataset with paths to tracking, players, and plays CSV files, along with optional parameters for sampling, speed/acceleration filtering, and orientation normalization. ### Method Constructor ### Parameters - **tracking_file_path** (`str`) - Required - Path to tracking CSV file. Must contain columns: gameId, playId, nflId, frameId, x, y, s (speed), o (orientation), dir (direction), team (or club). - **players_file_path** (`str`) - Required - Path to players CSV file. Must contain: nflId, position (or officialPosition), height, weight. - **plays_file_path** (`str`) - Required - Path to plays CSV file. Must contain: gameId, playId, possessionTeam. - **sample_rate** (`float`, optional) - Sampling rate for downsampling frames. For example, 0.5 keeps every 2nd frame. Defaults to None (no downsampling). - **max_player_speed** (`float`, optional) - Maximum physically plausible player speed (m/s) for filtering outliers. Defaults to 12.0 m/s (~27 mph). - **max_ball_speed** (`float`, optional) - Maximum physically plausible ball speed (m/s). Defaults to 28.0 m/s (~63 mph). - **max_player_acceleration** (`float`, optional) - Maximum player acceleration (m/s²). Defaults to 6.0 m/s². - **max_ball_acceleration** (`float`, optional) - Maximum ball acceleration (m/s²). Defaults to 13.5 m/s². - **orient_ball_owning** (`bool`, optional) - Whether to normalize coordinate system so the offense always attacks left-to-right. Defaults to True (recommended). - ****kwargs** - Additional arguments passed to DefaultDataset. ### Raises - **NotImplementedError** - If orient_ball_owning=False (currently unsupported). ### Example ```python >>> from unravel.american_football.dataset import BigDataBowlDataset >>> >>> # Load Big Data Bowl 2024 data >>> dataset = BigDataBowlDataset( ... tracking_file_path="tracking_week_1.csv", ... players_file_path="players.csv", ... plays_file_path="plays.csv", ... sample_rate=1.0, # Use all frames ... orient_ball_owning=True ... ) ``` ``` -------------------------------- ### Get dataset dimensions Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/utils/objects/graph_dataset.html Returns a tuple of N, F, S, n_out, n representing the dataset dimensions. ```python N = max(data.num_nodes for data in self) F = self[0].num_node_features if len(self) > 0 else 0 S = self[0].num_edge_features if len(self) > 0 else 0 n_out = self[0].y.shape[0] if len(self) > 0 else 0 n = len(self) return (N, F, S, n_out, n) ``` -------------------------------- ### EFPI Class Source: https://unravelsports.readthedocs.io/en/latest/api/generated/unravel.soccer.EFPI.html Documentation for the EFPI class, including constructor, fit method, parameters, outputs, and example usage. ```APIDOC ## EFPI Class ### Description Detect soccer team formations using Expected Formation Positioning Inference (EFPI). ### Constructor `EFPI(dataset, chunk_size=2000)` #### Parameters - **dataset** (`KloppyPolarsDataset`) - Required - Soccer tracking dataset with player positions and ball ownership information. - **chunk_size** (`int`) - Optional - Chunk size for processing. Defaults to 2000. ### Methods #### fit `fit(start_time=None, end_time=None, period_id=None, every='frame', change_after_possession=False, change_threshold=None, substitutions='merge')` Detect team formations from player positions. ##### Parameters - **start_time** - Optional - Start time for detection window. - **end_time** - Optional - End time for detection window. - **period_id** - Optional - Period identifier. - **every** - Optional - Aggregation window: 'frame', 'possession', or time window (e.g., '5m'). Defaults to 'frame'. - **change_after_possession** (`bool`) - Optional - Re-detect when possession changes. Defaults to False. - **change_threshold** (`float`) - Optional - Re-detect if cost improves by this fraction. Defaults to None. - **substitutions** - Optional - How to handle substitutions: 'merge' or 'drop'. Defaults to 'merge'. ##### Returns - **output** (`pl.DataFrame`) - Detected formations with columns: object_id, team_id, position, formation, is_attacking, frame_id (if every='frame'), segment_id (if every != 'frame'). - **segments** (`pl.DataFrame`, optional) - Segment metadata when using temporal aggregation: segment_id, n_frames, start_timestamp, end_timestamp, start_frame_id, end_frame_id. ##### Raises - **ValueError** - If dataset is not of type KloppyPolarsDataset. - **ImportError** - If scipy is not installed (required for linear_sum_assignment). ### Example ```python >>> from unravel.soccer.dataset import KloppyPolarsDataset >>> from unravel.soccer.models.formations import EFPI >>> from kloppy import datasets >>> >>> # Load tracking data >>> dataset = datasets.load(provider="skillcorner", match_id="123") >>> soccer_data = KloppyPolarsDataset(kloppy_dataset=dataset) >>> >>> # Initialize EFPI detector >>> efpi = EFPI(dataset=soccer_data) >>> >>> # Detect formations per frame >>> efpi.fit(every="frame") >>> print(efpi.output) >>> # Shows: frame_id, object_id, position, formation, is_attacking >>> >>> # Detect formations per possession >>> efpi.fit( ... every="possession", ... change_after_possession=True, # Re-detect when possession changes ... change_threshold=0.2 # Re-detect if cost improves by 20% ... ) >>> print(efpi.segments) >>> # Shows possession segments with start/end times >>> >>> # Detect formations per 5-minute window >>> efpi.fit(every="5m", substitutions="drop") >>> >>> # Use custom formation templates >>> efpi_custom = EFPI( ... dataset=soccer_data, ... formations=["4-3-3", "4-2-3-1", "3-5-2"] ... ) >>> efpi_custom.fit(every="possession") ``` ``` -------------------------------- ### Specify Formations for EFPI Source: https://unravelsports.readthedocs.io/en/latest/_sources/tutorials/formation_detection.rst.txt Shows how to specify which formations to consider. Use formations=None for all 65 defaults, or provide a list of formation names to narrow the search space. ```python # Use all formations result = model.fit(formations=None) # Only common formations result = model.fit(formations=["442", "433", "4231", "352", "343"]) # Single formation (useful for validation) result = model.fit(formations=["442"]) ``` -------------------------------- ### Time granularity (every) parameter examples Source: https://unravelsports.readthedocs.io/en/latest/tutorials/formation_detection.html Shows how to use the every parameter of model.fit() to control detection frequency. Options include 'frame' for every frame, time-based intervals like '1m', '30s', '5m', possession-based, and period-based. The parameter uses Polars' group_by_dynamic syntax. ```python # Frame-by-frame (most accurate but slowest) result = model.fit(every="frame") # Time-based intervals result = model.fit(every="1m") # Every minute result = model.fit(every="30s") # Every 30 seconds result = model.fit(every="5m") # Every 5 minutes # Possession-based result = model.fit(every="possession") # Period-based (coarsest) result = model.fit(every="period") ``` -------------------------------- ### Get item by index Source: https://unravelsports.readthedocs.io/en/latest/_modules/unravel/utils/objects/graph_dataset.html Supports indexing with integers or lists/arrays. For lists, returns a new PyGGraphDataset with selected graphs. ```python if isinstance(idx, (list, np.ndarray)): selected_graphs = [self.graphs[i] for i in idx] return PyGGraphDataset(graphs=selected_graphs, sample_rate=1.0) else: return self.graphs[idx] ```