Source code for floodlight.core.teamsheet

from dataclasses import dataclass
from typing import Dict
import warnings

import pandas as pd

from floodlight.core.definitions import essential_teamsheet_columns, protected_columns


[docs]@dataclass class Teamsheet: """Teamsheet storing player information. Core class of floodlight. Teamsheet data is stored in a `pandas` ``DataFrame``, where each row stores one player with their different properties organized in columns. Columns may contain any relevant information. A `"player"` column is required for instantiation to identify a player, and some particular column names are protected (see Notes). Parameters ---------- teamsheet: pd.DataFrame DataFrame containing rows of players and columns of respective properties. Attributes ---------- essential: list List of essential columns available for stored players. protected: list List of protected columns available for stored players. custom: list List of custom (i.e. non-essential and non-protected) columns available for stored players. essential_missing: list List of missing essential columns. essential_invalid: list List of essential columns that violate the definitions. protected_missing: list List of missing protected columns. protected_invalid: list List of protected columns that violate the definitions. Notes ----- Teamsheet data, particularly information available for players, may vary across data providers. To accommodate all data flavours, any column name or data type is permissible. However, one `essential` column is required (`"player"`). Other column names are `protected`. Using these names assumes that data stored in these columns follows conventions in terms of data types and value ranges. These are required for methods working with protected columns to assure correct calculations. Definitions for `essential` and `protected` columns can be found in :ref:`floodlight.core.definitions <definitions target>`. """ teamsheet: pd.DataFrame def __post_init__(self): # check for missing essential columns missing_columns = self.essential_missing if missing_columns: raise ValueError( f"Floodlight Teamsheet object is missing the essential " f"column(s) {missing_columns}!" ) # warn if value ranges are violated incorrect_columns = self.essential_invalid if incorrect_columns: for col in incorrect_columns: warnings.warn( f"The '{col}' column does not match the defined value range (from " f"floodlight.core.definitions). This may lead to unexpected " f"behavior of methods using this column." ) def __str__(self): return f"Floodlight Teamsheet object of shape {self.teamsheet.shape}" def __len__(self): return len(self.teamsheet) def __getitem__(self, key): return self.teamsheet[key] def __setitem__(self, key, value): self.teamsheet[key] = value @property def essential(self): essential = [ col for col in self.teamsheet.columns if col in essential_teamsheet_columns ] return essential @property def protected(self): protected = [col for col in self.teamsheet.columns if col in protected_columns] return protected @property def custom(self): custom = [ col for col in self.teamsheet.columns if col not in essential_teamsheet_columns and col not in protected_columns ] return custom @property def essential_missing(self): missing_columns = [ col for col in essential_teamsheet_columns if col not in self.essential ] return missing_columns @property def essential_invalid(self): invalid_columns = [ col for col in self.essential if not self.column_values_in_range(col, essential_teamsheet_columns) ] return invalid_columns @property def protected_missing(self): missing_columns = [ col for col in protected_columns if col not in self.protected ] return missing_columns @property def protected_invalid(self): invalid_columns = [ col for col in self.protected if not self.column_values_in_range(col, protected_columns) ] return invalid_columns
[docs] def column_values_in_range(self, col: str, definitions: Dict[str, Dict]) -> bool: """Check if values for a single column of the inner teamsheet DataFrame are in correct range using the specifications from :ref:`floodlight.core.definitions <definitions target>`. Parameters ---------- col: str Column name of the inner teamsheet DataFrame to be checked definitions: Dict Dictionary (from floodlight.core.definitions) containing specifications for the columns to be checked. The definitions need to contain an entry for the column to be checked and this entry needs to contain information about the value range in the form: ``definitions[col][value_range] = (min, max)``. Returns ------- bool True if the checks for value range pass and False otherwise Notes ----- Non-integer results of this computation will always be rounded to the next smaller integer. """ # skip if value range is not defined if definitions[col]["value_range"] is None: return True # skip values that are None or NaN col_nan_free = self.teamsheet[col].dropna() # retrieve value range from definitions min_val, max_val = definitions[col]["value_range"] # check value range for remaining values if not (min_val <= col_nan_free).all() & (col_nan_free <= max_val).all(): return False # all checks passed return True
[docs] def add_xIDs(self): """Adds the column "xID" as an increasing index over all players. The player index identifier ("xID") is used to enforce an order to the players within a team. This identifier is primarily used for locating players in respective XY objects, but can also be helpful iterating over or displaying all players of a team. This function assigns the "xID" as an increasing index that counts over all players in the inner teamsheet DataFrame, starting at 0 and ending at N_players - 1. Any existing entries for "xID" are overwritten by this function. """ self.teamsheet["xID"] = [i for i in range(len(self.teamsheet))]