Source code for sportsipy.fb.roster

import pandas as pd
import re
from .constants import ROSTER_SCHEME, SQUAD_URL
from ..decorators import float_property_decorator, int_property_decorator
from .fb_utils import _lookup_team
from .league_ids import LEAGUE_IDS
from pyquery import PyQuery as pq
from sportsipy.utils import (_get_stats_table,
                             _parse_field,
                             _remove_html_comment_tags)
from urllib.error import HTTPError


[docs]class SquadPlayer: """ Get player information and stats. Given a player ID and data, capture all relevant stats and information for the player including name, nationality, goals, assists, expected goal difference, nutmegs, and much more. Parameters ---------- player_data : PyQuery object A PyQuery object containing all fields of information for a single player, represented as one long row by concatenating all tables which hold values for the requested player. player_id : string A ``string`` representation of the player's unique 8-digit ID as shown on fbref.com. """ def __init__(self, player_data, player_id): self._name = None self._player_id = player_id self._nationality = None self._position = None self._age = None self._matches_played = None self._starts = None self._minutes = None self._goals = None self._assists = None self._penalty_kicks = None self._penalty_kick_attempts = None self._yellow_cards = None self._red_cards = None self._goals_per_90 = None self._assists_per_90 = None self._goals_and_assists_per_90 = None self._goals_non_penalty_per_90 = None self._goals_and_assists_non_penalty_per_90 = None self._expected_goals = None self._expected_goals_non_penalty = None self._expected_assists = None self._expected_goals_per_90 = None self._expected_assists_per_90 = None self._expected_goals_and_assists_per_90 = None self._expected_goals_non_penalty_per_90 = None self._expected_goals_and_assists_non_penalty_per_90 = None self._own_goals = None # Goalkeeping stats self._goals_against = None self._own_goals_against = None self._goals_against_per_90 = None self._shots_on_target_against = None self._saves = None self._save_percentage = None self._wins = None self._draws = None self._losses = None self._clean_sheets = None self._clean_sheet_percentage = None self._penalty_kicks_attempted = None self._penalty_kicks_allowed = None self._penalty_kicks_saved = None self._penalty_kicks_missed = None # Advanced goalkeeping stats self._free_kick_goals_against = None self._corner_kick_goals_against = None self._post_shot_expected_goals = None self._post_shot_expected_goals_per_shot = None self._post_shot_expected_goals_minus_allowed = None self._post_shot_expected_goals_minus_allowed_per_90 = None self._launches_completed = None self._launches_attempted = None self._launch_completion_percentage = None self._keeper_passes_attempted = None self._throws_attempted = None self._launch_percentage = None self._average_keeper_pass_length = None self._goal_kicks_attempted = None self._goal_kick_launch_percentage = None self._average_goal_kick_length = None self._opponent_cross_attempts = None self._opponent_cross_stops = None self._opponent_cross_stop_percentage = None self._keeper_actions_outside_penalty_area = None self._keeper_actions_outside_penalty_area_per_90 = None self._average_keeper_action_outside_penalty_distance = None # Shooting stats self._shots = None self._shots_on_target = None self._free_kick_shots = None self._shots_on_target_percentage = None self._shots_per_90 = None self._shots_on_target_per_90 = None self._goals_per_shot = None self._goals_per_shot_on_target = None self._expected_goals_non_penalty_per_shot = None self._goals_minus_expected = None self._non_penalty_minus_expected_non_penalty = None # Passing stats self._assists_minus_expected = None self._key_passes = None self._passes_completed = None self._passes_attempted = None self._pass_completion = None self._short_passes_completed = None self._short_passes_attempted = None self._short_pass_completion = None self._medium_passes_completed = None self._medium_passes_attempted = None self._medium_pass_completion = None self._long_passes_completed = None self._long_passes_attempted = None self._long_pass_completion = None self._left_foot_passes = None self._right_foot_passes = None self._free_kick_passes = None self._through_balls = None self._corner_kicks = None self._throw_ins = None self._final_third_passes = None self._penalty_area_passes = None self._penalty_area_crosses = None # Playing time stats self._minutes_per_match = None self._minutes_played_percentage = None self._nineties_played = None self._minutes_per_start = None self._subs = None self._minutes_per_sub = None self._unused_sub = None self._points_per_match = None self._goals_scored_on_pitch = None self._goals_against_on_pitch = None self._goal_difference_on_pitch = None self._goal_difference_on_pitch_per_90 = None self._net_difference_on_pitch_per_90 = None self._expected_goals_on_pitch = None self._expected_goals_against_on_pitch = None self._expected_goal_difference = None self._expected_goal_difference_per_90 = None self._net_expected_goal_difference_per_90 = None # Miscellaneous stats self._soft_reds = None self._fouls_committed = None self._fouls_drawn = None self._offsides = None self._crosses = None self._tackles_won = None self._interceptions = None self._penalty_kicks_won = None self._penalty_kicks_conceded = None self._successful_dribbles = None self._attempted_dribbles = None self._dribble_success_rate = None self._players_dribbled_past = None self._nutmegs = None self._dribblers_tackled = None self._dribblers_contested = None self._tackle_percentage = None self._times_dribbled_past = None self._parse_player_stats(player_data) def __str__(self): """ Return the string representation of the class. """ return f'{self.name} ({self.player_id})' def __repr__(self): """ Return the string representation of the class. """ return self.__str__() def _parse_nationality(self, player_data): """ Parse the player's nationality. If the nationality is listed for a player, it will contain a URI which includes the name of the country the player represents. For example, an English player would have a URI of the following: "/en/country/ENG/England-Football". Pulling out the country name and returning it as a string is a simple solution to pulling someone's nationality. Parameters ---------- player_data : PyQuery object A PyQuery object representing all of the player's stats fields combined as a singular row. Returns ------- string Returns a ``string`` of the player's home country, such as 'England'. """ country = player_data(ROSTER_SCHEME['nationality']) if not country: return None country = country.attr('href') country = re.sub(r'.*\/', '', country) country = country.replace('-Football', '') return country def _parse_player_stats(self, player_data): """ Parse a value for every attribute. This method looks through every class attribute with a few exceptions and retrieves the value according to the parsing scheme and index of the attribute from the passed HTML data. Once the value is retrieved, the attribute's value is updated with the returned result. Parameters ---------- player_data : string A ``string`` representation of all of the player's stats fields combined as a singular row. player_id : string A ``string`` of the player's unique 8-digit ID. """ for field in self.__dict__: # The short field truncates the leading '_' in the attribute name. short_field = str(field)[1:] if short_field == 'player_id': continue if short_field == 'nationality': value = self._parse_nationality(player_data) else: value = _parse_field(ROSTER_SCHEME, player_data, short_field) setattr(self, field, value) @property def dataframe(self): """ Returns a pandas ``DataFame`` containing all other class properties and values. The index for the DataFrame is the player ID. """ fields_to_include = { 'name': self.name, 'player_id': self.player_id, 'nationality': self.nationality, 'position': self.position, 'age': self.age, 'matches_played': self.matches_played, 'starts': self.starts, 'minutes': self.minutes, 'goals': self.goals, 'assists': self.assists, 'penalty_kicks': self.penalty_kicks, 'penalty_kick_attempts': self.penalty_kick_attempts, 'yellow_cards': self.yellow_cards, 'red_cards': self.red_cards, 'goals_per_90': self.goals_per_90, 'assists_per_90': self.assists_per_90, 'goals_and_assists_per_90': self.goals_and_assists_per_90, 'goals_non_penalty_per_90': self.goals_non_penalty_per_90, 'goals_and_assists_non_penalty_per_90': self.goals_and_assists_non_penalty_per_90, 'expected_goals': self.expected_goals, 'expected_goals_non_penalty': self.expected_goals_non_penalty, 'expected_assists': self.expected_assists, 'expected_goals_per_90': self.expected_goals_per_90, 'expected_assists_per_90': self.expected_assists_per_90, 'expected_goals_and_assists_per_90': self.expected_goals_and_assists_per_90, 'expected_goals_non_penalty_per_90': self.expected_goals_non_penalty_per_90, 'expected_goals_and_assists_non_penalty_per_90': self.expected_goals_and_assists_non_penalty_per_90, 'own_goals': self.own_goals, 'goals_against': self.goals_against, 'own_goals_against': self.own_goals_against, 'goals_against_per_90': self.goals_against_per_90, 'shots_on_target_against': self.shots_on_target_against, 'saves': self.saves, 'save_percentage': self.save_percentage, 'wins': self.wins, 'draws': self.draws, 'losses': self.losses, 'clean_sheets': self.clean_sheets, 'clean_sheet_percentage': self.clean_sheet_percentage, 'penalty_kicks_attempted': self.penalty_kicks_attempted, 'penalty_kicks_allowed': self.penalty_kicks_allowed, 'penalty_kicks_saved': self.penalty_kicks_saved, 'penalty_kicks_missed': self.penalty_kicks_missed, 'free_kick_goals_against': self.free_kick_goals_against, 'corner_kick_goals_against': self.corner_kick_goals_against, 'post_shot_expected_goals': self.post_shot_expected_goals, 'post_shot_expected_goals_per_shot': self.post_shot_expected_goals_per_shot, 'post_shot_expected_goals_minus_allowed': self.post_shot_expected_goals_minus_allowed, 'launches_completed': self.launches_completed, 'launches_attempted': self.launches_attempted, 'launch_completion_percentage': self.launch_completion_percentage, 'keeper_passes_attempted': self.keeper_passes_attempted, 'throws_attempted': self.throws_attempted, 'launch_percentage': self.launch_percentage, 'average_keeper_pass_length': self.average_keeper_pass_length, 'goal_kicks_attempted': self.goal_kicks_attempted, 'goal_kick_launch_percentage': self.goal_kick_launch_percentage, 'average_goal_kick_length': self.average_goal_kick_length, 'opponent_cross_attempts': self.opponent_cross_attempts, 'opponent_cross_stops': self.opponent_cross_stops, 'opponent_cross_stop_percentage': self.opponent_cross_stop_percentage, 'keeper_actions_outside_penalty_area': self.keeper_actions_outside_penalty_area, 'keeper_actions_outside_penalty_area_per_90': self.keeper_actions_outside_penalty_area_per_90, 'average_keeper_action_outside_penalty_distance': self.average_keeper_action_outside_penalty_distance, 'shots': self.shots, 'shots_on_target': self.shots_on_target, 'free_kick_shots': self.free_kick_shots, 'shots_on_target_percentage': self.shots_on_target_percentage, 'shots_per_90': self.shots_per_90, 'shots_on_target_per_90': self.shots_on_target_per_90, 'goals_per_shot': self.goals_per_shot, 'goals_per_shot_on_target': self.goals_per_shot_on_target, 'expected_goals_non_penalty_per_shot': self.expected_goals_non_penalty_per_shot, 'goals_minus_expected': self.goals_minus_expected, 'non_penalty_minus_expected_non_penalty': self.non_penalty_minus_expected_non_penalty, 'assists_minus_expected': self.assists_minus_expected, 'key_passes': self.key_passes, 'passes_completed': self.passes_completed, 'passes_attempted': self.passes_attempted, 'pass_completion': self.pass_completion, 'short_passes_completed': self.short_passes_completed, 'short_passes_attempted': self.short_passes_attempted, 'short_pass_completion': self.short_pass_completion, 'medium_passes_completed': self.medium_passes_completed, 'medium_passes_attempted': self.medium_passes_attempted, 'medium_pass_completion': self.medium_pass_completion, 'long_passes_completed': self.long_passes_completed, 'long_passes_attempted': self.long_passes_attempted, 'long_pass_completion': self.long_pass_completion, 'left_foot_passes': self.left_foot_passes, 'right_foot_passes': self.right_foot_passes, 'free_kick_passes': self.free_kick_passes, 'through_balls': self.through_balls, 'corner_kicks': self.corner_kicks, 'throw_ins': self.throw_ins, 'final_third_passes': self.final_third_passes, 'penalty_area_passes': self.penalty_area_passes, 'penalty_area_crosses': self.penalty_area_crosses, 'minutes_per_match': self.minutes_per_match, 'minutes_played_percentage': self.minutes_played_percentage, 'nineties_played': self.nineties_played, 'minutes_per_start': self.minutes_per_start, 'subs': self.subs, 'minutes_per_sub': self.minutes_per_sub, 'unused_sub': self.unused_sub, 'points_per_match': self.points_per_match, 'goals_scored_on_pitch': self.goals_scored_on_pitch, 'goals_against_on_pitch': self.goals_against_on_pitch, 'goal_difference_on_pitch': self.goal_difference_on_pitch, 'goal_difference_on_pitch_per_90': self.goal_difference_on_pitch_per_90, 'net_difference_on_pitch_per_90': self.net_difference_on_pitch_per_90, 'expected_goals_on_pitch': self.expected_goals_on_pitch, 'expected_goals_against_on_pitch': self.expected_goals_against_on_pitch, 'expected_goal_difference': self.expected_goal_difference, 'expected_goal_difference_per_90': self.expected_goal_difference_per_90, 'net_expected_goal_difference_per_90': self.net_expected_goal_difference_per_90, 'soft_reds': self.soft_reds, 'fouls_committed': self.fouls_committed, 'fouls_drawn': self.fouls_drawn, 'offsides': self.offsides, 'crosses': self.crosses, 'tackles_won': self.tackles_won, 'interceptions': self.interceptions, 'penalty_kicks_won': self.penalty_kicks_won, 'penalty_kicks_conceded': self.penalty_kicks_conceded, 'successful_dribbles': self.successful_dribbles, 'attempted_dribbles': self.attempted_dribbles, 'dribble_success_rate': self.dribble_success_rate, 'players_dribbled_past': self.players_dribbled_past, 'nutmegs': self.nutmegs, 'dribblers_tackled': self.dribblers_tackled, 'dribblers_contested': self.dribblers_contested, 'tackle_percentage': self.tackle_percentage, 'times_dribbled_past': self.times_dribbled_past } return pd.DataFrame([fields_to_include], index=[self.player_id]) @property def name(self): """ Returns a ``string`` of the player's full name, such as 'Harry Kane'. """ return self._name @property def player_id(self): """ Returns a ``string`` of the player's 8-digit ID, such as '21a66f6a' for Harry Kane. """ return self._player_id @property def nationality(self): """ Returns a ``string`` of the player's home country, such as 'England'. """ return self._nationality @property def position(self): """ Returns a ``string`` of the player's primary position(s). Multiple positions are separated by commas. """ return self._position @int_property_decorator def age(self): """ Returns an ``int`` of the player's age as of August 1 for winter leagues and February 1 for summer leagues for the given season. """ return self._age @int_property_decorator def matches_played(self): """ Returns an ``int`` of the number of matches the player has participated in. """ return self._matches_played @int_property_decorator def starts(self): """ Returns an ``int`` of the number of games the player has started. """ return self._starts @int_property_decorator def minutes(self): """ Returns an ``int`` of the number of minutes the player has spent on the field in all competitions. """ return self._minutes.replace(',', '') @int_property_decorator def goals(self): """ Returns an ``int`` of the number of goals the player has scored. """ return self._goals @int_property_decorator def assists(self): """ Returns an ``int`` of the number of goals the player has assisted. """ return self._assists @int_property_decorator def penalty_kicks(self): """ Returns an ``int`` of the number of penalty kicks the player has scored during regular play. """ return self._penalty_kicks @int_property_decorator def penalty_kick_attempts(self): """ Returns an ``int`` of the number of penalty kicks the player has attempted. """ return self._penalty_kick_attempts @int_property_decorator def yellow_cards(self): """ Returns an ``int`` of the number of yellow cards the player has accumulated during the season. """ return self._yellow_cards @int_property_decorator def red_cards(self): """ Returns an ``int`` of the number of red cards the player has accumulated during the season. """ return self._red_cards @float_property_decorator def goals_per_90(self): """ Returns a ``float`` of the number of goals the player has scored per 90 minutes on the field. """ return self._goals_per_90 @float_property_decorator def assists_per_90(self): """ Returns a ``float`` of the number of goals the player has assisted per 90 minutes on the field. """ return self._assists_per_90 @float_property_decorator def goals_and_assists_per_90(self): """ Returns a ``float`` of the number of goals the player has either scored or assisted per 90 minutes on the field. """ return self._goals_and_assists_per_90 @float_property_decorator def goals_non_penalty_per_90(self): """ Returns a ``float`` of the number of non-penalty goals the player has scored per 90 minutes on the field. """ return self._goals_non_penalty_per_90 @float_property_decorator def goals_and_assists_non_penalty_per_90(self): """ Returns a ``float`` of the number of non-penalty goals the player has either scored or assisted per 90 minutes on the field. """ return self._goals_and_assists_non_penalty_per_90 @float_property_decorator def expected_goals(self): """ Returns a ``float`` of the number of goals the player was expected to score based on the quality and quantity of shots taken. """ return self._expected_goals @float_property_decorator def expected_goals_non_penalty(self): """ Returns a ``float`` of the number of non-penalty goals the player was expected to score based on the quality and quantity of shots taken. """ return self._expected_goals_non_penalty @float_property_decorator def expected_assists(self): """ Returns a ``float`` of the number of goals the player was expected go assist based on the quality and quantity of teammate shots taken. """ return self._expected_assists @float_property_decorator def expected_goals_per_90(self): """ Returns a ``float`` of the player's expected goals per 90 minutes played. """ return self._expected_goals_per_90 @float_property_decorator def expected_assists_per_90(self): """ Returns a ``float`` of the player's expected assists per 90 minutes played. """ return self._expected_assists_per_90 @float_property_decorator def expected_goals_and_assists_per_90(self): """ Returns a ``float`` of the player's expected goals and assists per 90 minutes played. """ return self._expected_goals_and_assists_per_90 @float_property_decorator def expected_goals_non_penalty_per_90(self): """ Returns a ``float`` of the player's expected non-penalty goals per 90 minutes played. """ return self._expected_goals_non_penalty_per_90 @float_property_decorator def expected_goals_and_assists_non_penalty_per_90(self): """ Returns a ``float`` of the player's expected non-penalty goals and assists per 90 minutes played. """ return self._expected_goals_and_assists_non_penalty_per_90 @int_property_decorator def own_goals(self): """ Returns an ``int`` of the number of own goals the player has conceded. """ return self._own_goals @int_property_decorator def goals_against(self): """ Returns an ``int`` of the number of goals a keeper has conceded. """ return self._goals_against @int_property_decorator def own_goals_against(self): """ Returns an ``int`` of the number of own goals the team scored on a keeper. """ return self._own_goals_against @float_property_decorator def goals_against_per_90(self): """ Returns a ``float`` of the number of goals a keeper has coneceded per 90 minutes played. """ return self._goals_against_per_90 @int_property_decorator def shots_on_target_against(self): """ Returns an ``int`` of the number of shots on target a keeper has faced. """ return self._shots_on_target_against @int_property_decorator def saves(self): """ Returns an ``int`` of the number of shots a keeper has saved. """ return self._saves @float_property_decorator def save_percentage(self): """ Returns a ``float`` of the percentage of shots the keeper saved. Percentage ranges from 0-1. """ return self._save_percentage @int_property_decorator def wins(self): """ Returns an ``int`` of the number of games a keeper has won. """ return self._wins @int_property_decorator def draws(self): """ Returns an ``int`` of the number of games a keeper has drawn. """ return self._draws @int_property_decorator def losses(self): """ Returns an ``int`` of the number of games a keeper has lost. """ return self._losses @int_property_decorator def clean_sheets(self): """ Returns an ``int`` of the number of clean sheets a keeper has registered. """ return self._clean_sheets @float_property_decorator def clean_sheet_percentage(self): """ Returns a ``float`` of the percentage of games a keeper has participated in that resulted in a clean sheet. """ return self._clean_sheet_percentage @int_property_decorator def penalty_kicks_attempted(self): """ Returns an ``int`` of the number of penalty kicks a keeper has faced during regular play. """ return self._penalty_kicks_attempted @int_property_decorator def penalty_kicks_allowed(self): """ Returns an ``int`` of the number of penalty kicks a keeper has conceded during regular play. """ return self._penalty_kicks_allowed @int_property_decorator def penalty_kicks_saved(self): """ Returns an ``int`` of the number of penalty kicks a keeper has saved during regular play. """ return self._penalty_kicks_saved @int_property_decorator def penalty_kicks_missed(self): """ Returns an ``int`` of the number of penalty kicks a keeper has faced where the opponent missed the goal. """ return self._penalty_kicks_missed @int_property_decorator def free_kick_goals_against(self): """ Returns an ``int`` of the number of goals a keeper conceded as a result of an opponent's free kick. """ return self._free_kick_goals_against @int_property_decorator def corner_kick_goals_against(self): """ Returns an ``int`` of the number of goals a keeper conceded as a result of an opponent's corner kick. """ return self._corner_kick_goals_against @float_property_decorator def post_shot_expected_goals(self): """ Returns a ``float`` of the number of goals a keeper was expected to concede. """ return self._post_shot_expected_goals @float_property_decorator def post_shot_expected_goals_per_shot(self): """ Returns a ``float`` of the number of goals a keeper was expected to concede per shot faced. """ return self._post_shot_expected_goals_per_shot @float_property_decorator def post_shot_expected_goals_minus_allowed(self): """ Returns a ``float`` of the number of goals a keeper was expected to concede minus the number of goals they actually conceded. """ return self._post_shot_expected_goals_minus_allowed @float_property_decorator def post_shot_expected_goals_minus_allowed_per_90(self): """ Returns a ``float`` of the number of goals a keeper was expected to concede minus the number of goals they actually conceded, per 90 minutes played. """ return self._post_shot_expected_goals_minus_allowed_per_90 @int_property_decorator def launches_completed(self): """ Returns an ``int`` of the number of passes longer than 40 yards a keeper completed. """ return self._launches_completed @int_property_decorator def launches_attempted(self): """ Returns an ``int`` of the number of passes longer than 40 yards a keeper attempted. """ return self._launches_attempted @float_property_decorator def launch_completion_percentage(self): """ Returns a ``float`` of the percentage of passes longer than 40 yards a keeper completed. Percentage ranges from 0-100. """ return self._launch_completion_percentage @int_property_decorator def keeper_passes_attempted(self): """ Returns an ``int`` of the number of non-goal kick passes a keeper attempted. """ return self._keeper_passes_attempted @int_property_decorator def throws_attempted(self): """ Returns an ``int`` of the number of throws a keeper attempted. """ return self._throws_attempted @float_property_decorator def launch_percentage(self): """ Returns a ``float`` of the percentage of passes a keeper makes longer than 40 yards excluding goal kicks. Percentage ranges from 0-100. """ return self._launch_percentage @float_property_decorator def average_keeper_pass_length(self): """ Returns a ``float`` of the average pass length for a keeper in yards excluding goal kicks. """ return self._average_keeper_pass_length @int_property_decorator def goal_kicks_attempted(self): """ Returns an ``int`` of the number of goal kicks a keeper attempted. """ return self._goal_kicks_attempted @float_property_decorator def goal_kick_launch_percentage(self): """ Returns a ``float`` of the percentage of goal kicks a keeper has launched further than 40 yards. Percentage ranges from 0-100. """ return self._goal_kick_launch_percentage @float_property_decorator def average_goal_kick_length(self): """ Returns a ``float`` of the average pass length for goal kicks in yards for a keeper. """ return self._average_goal_kick_length @int_property_decorator def opponent_cross_attempts(self): """ Returns an ``int`` of the number of crosses a keeper has faced. """ return self._opponent_cross_attempts @int_property_decorator def opponent_cross_stops(self): """ Returns an ``int`` of the number of crosses a keeper has successfully stopped. """ return self._opponent_cross_stops @float_property_decorator def opponent_cross_stop_percentage(self): """ Returns a ``float`` of the percentage of crosses the keeper has successfully stopped. Percentage ranges from 0-100. """ return self._opponent_cross_stop_percentage @int_property_decorator def keeper_actions_outside_penalty_area(self): """ Returns an ``int`` of the number of defensive actions a keeper made outside the penalty area. """ return self._keeper_actions_outside_penalty_area @float_property_decorator def keeper_actions_outside_penalty_area_per_90(self): """ Returns a ``float`` of the number of defensive actions a keeper made outside the penalty area per 90 minutes played. """ return self._keeper_actions_outside_penalty_area_per_90 @float_property_decorator def average_keeper_action_outside_penalty_distance(self): """ Returns a ``float`` of the average distance from goal in yards a keeper performed a defensive action outside the penalty area. """ return self._average_keeper_action_outside_penalty_distance @int_property_decorator def shots(self): """ Returns an ``int`` of the number of shots the player has taken. """ return self._shots @int_property_decorator def shots_on_target(self): """ Returns an ``int`` of the number of shots on target the player has taken. """ return self._shots_on_target @int_property_decorator def free_kick_shots(self): """ Returns an ``int`` of the number of shots the player has taken from free kicks. """ return self._free_kick_shots @float_property_decorator def shots_on_target_percentage(self): """ Returns a ``float`` of the percentage of shots taken by the player that were on target. Percentage ranges from 0-100. """ return self._shots_on_target_percentage @float_property_decorator def shots_per_90(self): """ Returns a ``float`` of the number of shots the player has taken per 90 minutes played. """ return self._shots_per_90 @float_property_decorator def shots_on_target_per_90(self): """ Returns a ``float`` of the number of shots on target the player has taken per 90 minutes played. """ return self._shots_on_target_per_90 @float_property_decorator def goals_per_shot(self): """ Returns a ``float`` of the average number of goals scored per shot taken by the player. """ return self._goals_per_shot @float_property_decorator def goals_per_shot_on_target(self): """ Returns a ``float`` of the average number of goals scored per shot on target by the player. """ return self._goals_per_shot_on_target @float_property_decorator def expected_goals_non_penalty_per_shot(self): """ Returns a ``float`` of the nuber of non-penalty goals the player was expected to score per shot. """ return self._expected_goals_non_penalty_per_shot @float_property_decorator def goals_minus_expected(self): """ Returns a ``float`` of the number of goals scored minus the number of goals the player was expected to score. """ return self._goals_minus_expected @float_property_decorator def non_penalty_minus_expected_non_penalty(self): """ Returns a ``float`` of the number of non-penalty goals scored minus the number of non-penalty goals the player was expected to score. """ return self._non_penalty_minus_expected_non_penalty @float_property_decorator def assists_minus_expected(self): """ Returns a ``float`` of the number of assists the player registered minus the actual number of assists the player tallied. """ return self._assists_minus_expected @int_property_decorator def key_passes(self): """ Returns an ``int`` of the number of passes the player made that directly lead to a shot. """ return self._key_passes @int_property_decorator def passes_completed(self): """ Returns an ``int`` of the total number of passes the player has completed. """ return self._passes_completed @int_property_decorator def passes_attempted(self): """ Returns an ``int`` of the total number of passes the player has attempted. """ return self._passes_attempted @float_property_decorator def pass_completion(self): """ Returns a ``float`` of the player's overall pass completion rating. Percentage ranges from 0-100. """ return self._pass_completion @int_property_decorator def short_passes_completed(self): """ Returns an ``int`` of the total number of passes under 5 yards the player has completed. """ return self._short_passes_completed @int_property_decorator def short_passes_attempted(self): """ Returns an ``int`` of the total number of passes under 5 yards the player has attempted. """ return self._short_passes_attempted @float_property_decorator def short_pass_completion(self): """ Returns a ``float`` of the player's overall pass completion rating for passes under 5 yards. Percentage ranges from 0-100. """ return self._short_pass_completion @int_property_decorator def medium_passes_completed(self): """ Returns an ``int`` of the total number of passes between 5 and 25 yards the player has completed. """ return self._medium_passes_completed @int_property_decorator def medium_passes_attempted(self): """ Returns an ``int`` of the total number of passes between 5 and 25 yards the player has attempted. """ return self._medium_passes_attempted @float_property_decorator def medium_pass_completion(self): """ Returns a ``float`` of the player's overall pass completion rating for passes between 5 and 25 yards. Percentage ranges from 0-100. """ return self._medium_pass_completion @int_property_decorator def long_passes_completed(self): """ Returns an ``int`` of the total number of passes greater than 25 yards the player has completed. """ return self._long_passes_completed @int_property_decorator def long_passes_attempted(self): """ Returns an ``int`` of the total number of passes greater than 25 yards the player has attempted. """ return self._long_passes_attempted @float_property_decorator def long_pass_completion(self): """ Returns a ``float`` of the player's overall pass completion rating for passes greater than 25 yards. Percentage ranges from 0-100. """ return self._long_pass_completion @int_property_decorator def left_foot_passes(self): """ Returns an ``int`` of the number of passes the player made with their left foot. """ return self._left_foot_passes @int_property_decorator def right_foot_passes(self): """ Returns an ``int`` of the number of passes the player made with their right foot. """ return self._right_foot_passes @int_property_decorator def free_kick_passes(self): """ Returns an ``int`` of the number of passes the player made from a free kick. """ return self._free_kick_passes @int_property_decorator def through_balls(self): """ Returns an ``int`` of the number of passes the player made between the last line of defenders into open space. """ return self._through_balls @int_property_decorator def corner_kicks(self): """ Returns an ``int`` of the number of corner kicks the player has taken. """ return self._corner_kicks @int_property_decorator def throw_ins(self): """ Returns an ``int`` of the number of throw-ins the player took. """ return self._throw_ins @int_property_decorator def final_third_passes(self): """ Returns an ``int`` of the number of passes the player made into the final third. """ return self._final_third_passes @int_property_decorator def penalty_area_passes(self): """ Returns an ``int`` of the number of passes the player made into the opposing penalty area. """ return self._penalty_area_passes @int_property_decorator def penalty_area_crosses(self): """ Returns an ``int`` of the number of non-set piece crosses the player made into the penalty area. """ return self._penalty_area_crosses @int_property_decorator def minutes_per_match(self): """ Returns an ``int`` of the average number of minutes the player played per match. """ return self._minutes_per_match @float_property_decorator def minutes_played_percentage(self): """ Returns a ``float`` of the percentage of time the player has been on the field for all games the team participated in. Percentage ranges from 0-100. """ return self._minutes_played_percentage @float_property_decorator def nineties_played(self): """ Returns a ``float`` of number of the number of minutes the player has played divided by 90. """ return self._nineties_played @int_property_decorator def minutes_per_start(self): """ Returns an ``int`` of the number of minutes the player plays on average per game started. """ return self._minutes_per_start @int_property_decorator def subs(self): """ Returns an ``int`` of the number of times the player has come on as a sub. """ return self._subs @int_property_decorator def minutes_per_sub(self): """ Returns an ``int`` of the average number of minutes the player has played per game after coming in as a sub. """ return self._minutes_per_sub @int_property_decorator def unused_sub(self): """ Returns an ``int`` of the number of times the player was an unused sub and spent the entirety of the game on the bench. """ return self._unused_sub @float_property_decorator def points_per_match(self): """ Returns a ``float`` of the average number of points the team has gained per game in which the player participated. """ return self._points_per_match @int_property_decorator def goals_scored_on_pitch(self): """ Returns an ``int`` of the number of goals the team has scored while the player was on the field. """ return self._goals_scored_on_pitch @int_property_decorator def goals_against_on_pitch(self): """ Returns an ``int`` of the number of goals the team has conceded while the player was on the field. """ return self._goals_against_on_pitch @int_property_decorator def goal_difference_on_pitch(self): """ Returns an ``int`` of the team's goal difference while the player is on the field. """ return self._goal_difference_on_pitch @float_property_decorator def goal_difference_on_pitch_per_90(self): """ Returns a ``float`` of the team's average goal difference while the player is on the field, per 90 minutes played. """ return self._goal_difference_on_pitch_per_90 @float_property_decorator def net_difference_on_pitch_per_90(self): """ Returns a ``float`` of the team's goal difference while the player is on the pitch minus the team's goal difference while the player is off the pitch, per 90 minutes. """ return self._net_difference_on_pitch_per_90 @float_property_decorator def expected_goals_on_pitch(self): """ Returns a ``float`` of the number of goals the team was expected to score while the player was on the pitch. """ return self._expected_goals_on_pitch @float_property_decorator def expected_goals_against_on_pitch(self): """ Returns a ``float`` of the number of goals the team was expected to concede while the player was on the pitch. """ return self._expected_goals_against_on_pitch @float_property_decorator def expected_goal_difference(self): """ Returns a ``float`` of the difference between expected team goals scored and conceded while the player was on the pitch. """ return self._expected_goal_difference @float_property_decorator def expected_goal_difference_per_90(self): """ Returns a ``float`` of the difference between expected team goals scored and conceded while the player was on the pitch, per 90 minutes. """ return self._expected_goal_difference_per_90 @float_property_decorator def net_expected_goal_difference_per_90(self): """ Returns a ``float`` of the team's expected goal difference while the player is on the pitch minus the team's exepcted goal difference while the player is off the pitch, per 90 minutes. """ return self._net_expected_goal_difference_per_90 @int_property_decorator def soft_reds(self): """ Returns an ``int`` of the number of games where the player received two yellow cards, resulting in a red, or a "soft red". """ return self._soft_reds @int_property_decorator def fouls_committed(self): """ Returns an ``int`` of the number of fouls the player has committed. """ return self._fouls_committed @int_property_decorator def fouls_drawn(self): """ Returns an ``int`` of the number of fouls the player has been the victim of. """ return self._fouls_drawn @int_property_decorator def offsides(self): """ Returns an ``int`` of the number of times the player has been called offside. """ return self._offsides @int_property_decorator def crosses(self): """ Returns an ``int`` of the number of times the player has crossed the ball. """ return self._crosses @int_property_decorator def tackles_won(self): """ Returns an ``int`` of the number of tackles the player has won. """ return self._tackles_won @int_property_decorator def interceptions(self): """ Returns an ``int`` of the number of times the player has intercepted the ball. """ return self._interceptions @int_property_decorator def penalty_kicks_won(self): """ Returns an ``int`` of the number of times the player has won a penalty kick for the team. """ return self._penalty_kicks_won @int_property_decorator def penalty_kicks_conceded(self): """ Returns an ``int`` of the number of times the player has conceded a penalty kick to the opposition. """ return self._penalty_kicks_conceded @int_property_decorator def successful_dribbles(self): """ Returns an ``int`` of the number of dribbles the player has completed successfully. """ return self._successful_dribbles @int_property_decorator def attempted_dribbles(self): """ Returns an ``int`` of the number of times the player has attempted a dribble. """ return self._attempted_dribbles @float_property_decorator def dribble_success_rate(self): """ Returns a ``float`` of the percentage of attempted dribbles the player has successfully completed. Percentage ranges from 0-100. """ return self._dribble_success_rate @int_property_decorator def players_dribbled_past(self): """ Returns an ``int`` of the number of opponents the player dribbled past. """ return self._players_dribbled_past @int_property_decorator def nutmegs(self): """ Returns an ``int`` of the number of opponents the player has nutmegged. """ return self._nutmegs @int_property_decorator def dribblers_tackled(self): """ Returns an ``int`` of the number of opponents who were attempting a dribble that the player tackled. """ return self._dribblers_tackled @int_property_decorator def dribblers_contested(self): """ Returns an ``int`` of the number of opponents who were attempting a dribble that the player contested. """ return self._dribblers_contested @float_property_decorator def tackle_percentage(self): """ Returns a ``float`` of the percentage of opposing dribblers the player has successfully tackled. Percentage ranges from 0-100. """ return self._tackle_percentage @int_property_decorator def times_dribbled_past(self): """ Returns an ``int`` of the number of times the player has been dribbled past. """ return self._times_dribbled_past
[docs]class Roster: """ Get stats for all players on a roster. Request a team's roster for a given season and create instances of the Player class for each player, containing a detailed list of the player's statistics and information for the season. Parameters ---------- squad_id : string The team's 8-digit squad ID or the team's name, such as '361ca564' or 'Tottenham Hotspur'. doc : PyQuery object (optional) If passed to the class instantiation, this will be used to pull all information instead of making another request to the website. If the document is not provided, it will be pulled during a later step. """ def __init__(self, squad_id, doc=None): self._players = [] self._squad_id = _lookup_team(squad_id) player_data_dict = self._pull_stats(doc) if not player_data_dict: return None self._instantiate_players(player_data_dict) def __call__(self, player): """ Return a specified player on the roster. Returns a specific player as requested by the passed name or player ID. The input string must either match a player's 8-digit unique ID or the named listed on fbref.com for the player. Parameters ---------- player : string A ``string`` of either the player's 8-digit unique ID or the name listed on fbref.com for the player. Returns ------- Player instance If the requested player can be found, their Player instance is returned. Raises ------ ValueError If the requested player cannot be matched with a player in the squad. """ for player_instance in self._players: if not player_instance.name or not player_instance.player_id: continue # pragma: no cover if player.lower() == player_instance.player_id.lower(): return player_instance if player.lower().strip() == player_instance.name.lower().strip(): return player_instance raise ValueError('No player found with the requested name or ID') def __str__(self): """ Return the string representation of the class. """ players = [f'{x.name} ({x.player_id})'.strip() for x in self._players] return '\n'.join(players) def __repr__(self): """ Return the string representation of the class. """ return self.__str__() def __iter__(self): """ Returns an iterator of all of the players on the given team's roster. """ return iter(self._players) def __len__(self): """ Returns the number of player on the given team's roster. """ return len(self._players) def _player_id(self, player_data): """ Parse the player's ID from a row. The player ID is embedded within the header column of each individual player's row within a stats table. The specific ID is in a URL and can be easily parsed and returned. Parameters ---------- player_data : PyQuery object A PyQuery object representing a single row in a stats table for a player. Returns ------- string Returns a ``string`` of the player's unique 8-digit player ID. """ player = player_data('th[data-stat="player"]') player_id = player('a').attr('href') try: player_id = re.sub(r'.*\/players\/', '', player_id) player_id = re.sub(r'\/.*', '', player_id) except TypeError: player_id = None return player_id def _add_stats_data(self, stats_table, player_data_dict): """ Add each player's stats rows to a dictionary. Given the player stats are spread throughout many tables, they should be combined by player for a single reference for each player for easier lookups. Parameters ---------- stats_table : generator A generator of all row items in a given table. player_data_dict : {str: {'data': str}} dictionary A dictionary where every key is the player's ID and every value is another dictionary with a 'data' key which contains the string version of the row data for the matched player. Returns ------- dictionary An updated version of the player_data_dict with the passed table row information included. """ for player_data in stats_table: if 'class="thead"' in str(player_data): continue # pragma: no cover player_id = self._player_id(player_data) if not player_id: continue try: player_data_dict[player_id]['data'] += player_data except KeyError: player_data_dict[player_id] = {'data': player_data} return player_data_dict def _pull_stats(self, doc): """ Download the team page and pull all stats. Download the requested team's season page and pull all of the relevant stats tables for later parsing. Parameters ---------- doc : PyQuery object If passed to the class instantiation, this will be used to pull all information instead of making another request to the website. If the document is not provided, this value will be None. Returns ------- dictionary Returns a ``dictionary`` where every key is the player's ID and every value is another dictionary with a 'data' key which contains the string version of the row data for the matched player. """ if not doc: try: doc = pq(SQUAD_URL % self._squad_id) doc = pq(_remove_html_comment_tags(doc)) except HTTPError: return None stats_table = [] player_data_dict = {} # Some leagues have a special ID for the tables. First lookup that ID # if it exists, but if not, use 'ks_combined' as the default. postfix = LEAGUE_IDS.get(self._squad_id, 'ks_combined') for table_id in ['table#stats_standard_', 'table#stats_keeper_', 'table#stats_keeper_adv_', 'table#stats_shooting_', 'table#stats_passing_', 'table#stats_playing_time_', 'table#stats_misc_']: table = _get_stats_table(doc, table_id + 'ks_combined') if not table: table = _get_stats_table(doc, table_id + postfix) if not table: continue player_data_dict = self._add_stats_data(table, player_data_dict) return player_data_dict def _instantiate_players(self, player_data_dict): """ Create Player instances for each squad member. Given the stats information for all players, an instance of the Player class should be created and appended to the overall list of players for easy future reference. Parameters ---------- player_data_dict : {str: {'data': str}} dictionary A dictionary where every key is the player's ID and every value is another dictionary with a 'data' key which contains the string version of the row data for the matched player. """ for player_id, player_data in player_data_dict.items(): player = SquadPlayer(player_data['data'], player_id) self._players.append(player)