Source code for fussballgott.league

# Copyright (C) 2021 Silvan Fischbacher

from itertools import combinations, permutations

import numpy as np
import pandas as pd
from tqdm.auto import trange

from fussballgott import fussball


[docs]def simulate( teams, schedule=2, table=None, missing_games=None, n_sim=1, include_goals_against=True, sorting="standard", progressbar=True, tournament_mode=False, ): """ Simulate a league with given teams and schedule. :param teams: Dictionary with teams as keys and team objects as values :param schedule: Schedule of the league. If int, all teams play against each other schedule times. If pd.DataFrame, the schedule is given as a table with columns "Home" and "Away". :param table: Table of the league. If None, a new table is created :param missing_games: List of games that are not played :param n_sim: Number of simulations :param include_goals_against: If True, the average goals against are included :param sorting: Sorting of the table :param progressbar: If True, a progressbar is shown :param tournament_mode: If True, the table is returned, if False, the ranking table is returned :return: table (if tournament_mode) or ranking table (if not tournament_mode) """ team_list = teams.keys() # no schedule given, create one if isinstance(schedule, int): if schedule % 2 == 0: one_round = pd.DataFrame( list(permutations(teams, 2)), columns=["Home", "Away"] ) sch = one_round while schedule > 2: sch = pd.concat([sch, one_round], ignore_index=True) schedule -= 2 else: one_round = pd.DataFrame( list(combinations(teams, 2)), columns=["Home", "Away"] ) sch = one_round while schedule > 1: sch = pd.concat([sch, one_round], ignore_index=True) schedule -= 1 schedule = sch missing_games = np.ones(schedule.shape[0], dtype=bool) # table not given, create one if table is None: table = pd.DataFrame(columns=["Team", "Played", "GF", "GA", "GD", "Points"]) table["Team"] = team_list table = table.fillna(0) table.index = np.arange(1, len(team_list) + 1) n_sim = int(n_sim) n_teams = len(teams) sched, tab, dict_num2team = pd_to_np(schedule, table) ranking_table = np.zeros((n_teams, n_teams)) # simulate the whole league n_sim times for _ in trange(n_sim, disable=not progressbar): changed_table = simulate_once( sched, tab, teams, missing_games, dict_num2team, include_goals_against, ) changed_table, ranking = fussball.sort(changed_table, sorting=sorting) for j in range(n_teams): ranking_table[int(ranking[j]), j] += 1 if tournament_mode: return changed_table, dict_num2team else: return np_to_pd(ranking_table / n_sim, dict_num2team)
[docs]def simulate_once( schedule, table, teams, missing_games, dict_num2team, include_goals_against=True, ): """ Simulate a league with given teams and schedule. :param schedule: numpy array with schedule :param table: numpy array with table :param teams: Dictionary with teams as keys and team objects as values :param missing_games: List of games that are not played :param dict_num2team: Dictionary that maps indices to teams :param include_goals_against: If True, the average goals against are included :return: changed table """ changed_table = table.copy() index = np.arange(len(missing_games)) for i in index[missing_games]: s1 = schedule[i, 0] s2 = schedule[i, 1] t1 = dict_num2team[s1] t2 = dict_num2team[s2] h, a = fussball.simulate_game( teams[t1].AvGoalsF, teams[t2].AvGoalsF, teams[t1].AvGoalsA, teams[t2].AvGoalsA, include_goals_against=include_goals_against, ) changed_table[s1, 0] += 1 # Played changed_table[s2, 0] += 1 changed_table[s1, 1] += h # GF changed_table[s2, 1] += a changed_table[s1, 2] += a # GA changed_table[s2, 2] += h if h > a: changed_table[s1, 3] += 3 # Points elif h < a: changed_table[s2, 3] += 3 else: changed_table[s1, 3] += 1 changed_table[s2, 3] += 1 return changed_table
[docs]def pd_to_np(schedule, table): """ Convert pandas schedule and table to numpy arrays. :param schedule: Schedule of the league :param table: Table of the league :return: schedule and table as numpy arrays """ new_schedule = schedule.copy() dict_n2t = {} np_table = table[["Played", "GF", "GA", "Points"]].values a, b = np.shape(np_table) new_table = np.zeros((a, b + 1)) new_table[:, :-1] = np_table for i in range(len(table)): team = table["Team"][i + 1] new_schedule = new_schedule.replace(team, i) dict_n2t[team] = i dict_n2t[i] = team new_table[i, -1] = i return new_schedule.values, new_table, dict_n2t
[docs]def np_to_pd(table, dict_num2team): """ Convert numpy table to pandas table. :param table: Table of the league :param dict_num2team: Dictionary that maps indices to teams :return: Table of the league as pandas table """ n = np.shape(table)[0] t = [] for i in range(n): t.append(dict_num2team[i]) pd_table = pd.DataFrame(index=t, columns=np.arange(1, n + 1), data=table) return pd_table