<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Modelling
Step1:  Data Preparation
For the first iteration, we'll only use data after 2009. This is when most modern statistics began to be kept (though not all of them did).
Step2:  To keep model simple, exclude draws. Mark them as victories for the away team instead.
Step3:  We want to split the data into test and train in a stratified manner, i.e. we don't want to favour a certain season, or a part of the season. So we'll take a portion (25%) of games from each round.
Step4:  Create test and training data
Step5:  Capture all of the 'diff' columns in the model, too
Step6:  Define features
Step7:  Set up test and train datasets
Step8:  Fill the NaN values
Step9:  Modelling
Model 1
Step10:  Model 2
Step11:  Training model on all of the data
Generating predictions
Now that we have a model, we need to ingest data for that model to make a prediction on.
Start by reading in the fixture.
Step12:  We'll then prepare the data for the round we're interested in. We'll do this by
Step13:  Get the IDs for the players we'll be using
Step14:  Try including Bachar Houli
Now we can collect the data for each player and aggregate it to the team level, as we would with the training data
Step15:  Can now use this to make predictions
Step16:  Glue these together and sort
Step17:  Training model on all data
Step19:  Visualisation
Step32:  Metadata and functions
<ASSISTANT_TASK:>
Python Code:
# match data with aggregated individual data
import pandas as pd
match_path = '/Users/t_raver9/Desktop/projects/aflengine/analysis/machine_learning/src/player_data/data/matches_with_player_agg.csv'
players_path = '/Users/t_raver9/Desktop/projects/aflengine/analysis/machine_learning/src/player_data/data/players_with_player_stat_totals.csv'
matches = pd.read_csv(match_path)
players = pd.read_csv(players_path)
Explanation: Modelling
End of explanation
model_data = matches[matches['season'] >= 2010]
Explanation: Data Preparation
For the first iteration, we'll only use data after 2009. This is when most modern statistics began to be kept (though not all of them did).
End of explanation
for idx, row in model_data.iterrows():
    if row['winner'] == 'draw':
        model_data.at[idx,'winner'] = 'away'
Explanation: To keep model simple, exclude draws. Mark them as victories for the away team instead.
End of explanation
# How many games do we get per round?
round_counts = {}
curr_round = 1
matches_in_round = 0
for idx,row in model_data.iterrows():
    
    if curr_round != row['round']:
        
        if matches_in_round not in round_counts:
            round_counts[matches_in_round] = 1
        else:
            round_counts[matches_in_round] += 1
            
        curr_round = row['round']
        matches_in_round = 1
        continue
        
    else:
        matches_in_round += 1
        
round_counts
# Taking a minimum 25% of each round
from math import ceil
test_sample_size = {}
for num_games in round_counts:
    test_sample_size[num_games] = ceil(num_games/4)
rounds_in_season = get_season_rounds(model_data)
teams_in_season = get_season_teams(model_data)
Explanation: We want to split the data into test and train in a stratified manner, i.e. we don't want to favour a certain season, or a part of the season. So we'll take a portion (25%) of games from each round.
End of explanation
# test set
from copy import deepcopy
test_data = pd.DataFrame()
for season, max_round in rounds_in_season.items():
    for rnd in range(1, max_round):
        round_matches = model_data[(model_data['season']==season) & (model_data['round']==rnd)]
        num_test = test_sample_size[len(round_matches)]
        round_test_set = round_matches.sample(num_test)
        test_data = test_data.append(round_test_set)
        
# training set
training_data = model_data.drop(test_data.index)
Explanation: Create test and training data
End of explanation
diff_cols = [col for col in model_data.columns if col[0:4] == 'diff']
Explanation: Capture all of the 'diff' columns in the model, too
End of explanation
features = [col 
            for col 
            in ['h_career_' + col for col in player_cols_to_agg] + \
            ['h_season_' + col for col in player_cols_to_agg] + \
            ['a_career_' + col for col in player_cols_to_agg] + \
            ['a_season_' + col for col in player_cols_to_agg] + \
            ['h_' + col for col in ladder_cols] + \
            ['h_' + col + '_form' for col in ladder_cols] + \
            ['a_' + col for col in ladder_cols] + \
            ['a_' + col + '_form' for col in ladder_cols] + \
            ['h_career_' + col for col in misc_columns] + \
            ['h_season_' + col for col in misc_columns] + \
            ['a_career_' + col for col in misc_columns] + \
            ['a_season_' + col for col in misc_columns] + \
            diff_cols
            ]
# REMOVE PERCENTAGE FOR NOW
features.remove('h_percentage')
features.remove('a_percentage')
features.remove('diff_percentage')
target = 'winner'
Explanation: Define features
End of explanation
X_train = training_data[features]
y_train = training_data[target]
X_test = test_data[features]
y_test = test_data[target]
Explanation: Set up test and train datasets
End of explanation
X_train.fillna(0,inplace=True)
y_train.fillna(0,inplace=True)
X_test.fillna(0,inplace=True)
y_test.fillna(0,inplace=True)
Explanation: Fill the NaN values
End of explanation
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV
import numpy as np
log_reg = LogisticRegression()
param_grid = {
                 'tol': [.0001, .001, .01],
                 'C': [.1, 1, 10],
                 'max_iter': [50,100,200]
             }
grid_log_reg = GridSearchCV(log_reg, param_grid, cv=5)
grid_log_reg.fit(X_train, y_train)
grid_log_reg.score(X_train,y_train)
grid_log_reg.score(X_test,y_test)
# Confirm that it's not just picking the home team
print(sum(grid_log_reg.predict(X_test)=='away'))
print(sum(grid_log_reg.predict(X_test)=='home'))
Explanation: Modelling
Model 1: Logistic regression
End of explanation
diff_cols = [col for col in model_data.columns if col[0:4] == 'diff']
features = diff_cols
# REMOVE PERCENTAGE FOR NOW
diff_cols.remove('diff_percentage')
target = 'winner'
X_train_2 = training_data[diff_cols]
y_train_2 = training_data[target]
X_test_2 = test_data[diff_cols]
y_test_2 = test_data[target]
#X_train_2 = X_train_2[features]
#y_train_2 = y_train_2[features]
#X_test_2 = X_test_2[features]
#y_test_2 = y_test_2[features]
X_train_2.fillna(0,inplace=True)
y_train_2.fillna(0,inplace=True)
X_test_2.fillna(0,inplace=True)
y_test_2.fillna(0,inplace=True)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV
import numpy as np
log_reg_2 = LogisticRegression()
param_grid = {
                 'tol': [.0001, .001, .01],
                 'C': [.1, 1, 10],
                 'max_iter': [50,100,200]
             }
grid_log_reg_2 = GridSearchCV(log_reg_2, param_grid, cv=5)
grid_log_reg_2.fit(X_train_2, y_train_2)
grid_log_reg_2.score(X_train_2,y_train_2)
grid_log_reg_2.score(X_test_2,y_test_2)
training_data[(training_data['round']==1) & (training_data['season']==2018)]
Explanation: Model 2: using less features
End of explanation
fixture_path = '/Users/t_raver9/Desktop/projects/aflengine/tipengine/fixture2020.csv'
fixture = pd.read_csv(fixture_path)
fixture[fixture['round']==2]
Explanation: Training model on all of the data
Generating predictions
Now that we have a model, we need to ingest data for that model to make a prediction on.
Start by reading in the fixture.
End of explanation
next_round_matches = get_upcoming_matches(matches,fixture,round_num=2)
next_round_matches
Explanation: We'll then prepare the data for the round we're interested in. We'll do this by:
- getting the team-level data, such as ladder position and form
- getting the player-level data and aggregating it up to the team level
To get the player-level data, we also need to choose who is playing for each team.
End of explanation
import cv2 
import pytesseract
custom_config = r'--oem 3 --psm 6'
import pathlib
names_dir = '/Users/t_raver9/Desktop/projects/aflengine/analysis/machine_learning/src/OCR/images'
# Initialise the dictionary
player_names_dict = {}
for team in matches['hteam'].unique():
    player_names_dict[team] = []
    
# Fill out the dictionary
for path in pathlib.Path(names_dir).iterdir():
    print(path)
    if path.name.split('.')[0] in player_names_dict:
        path_str = str(path)
        image_obj = cv2.imread(path_str)
        image_string = pytesseract.image_to_string(image_obj, config=custom_config)
        names = get_player_names(image_string)
        player_names_dict[path.name.split('.')[0]].extend(names)
Explanation: Get the IDs for the players we'll be using
End of explanation
from copy import deepcopy
players_in_rnd = []
for _, v in player_names_dict.items():
    players_in_rnd.extend(v)
player_data = get_player_data(players_in_rnd)
players_in_rnd
aggregate = player_data[player_cols].groupby('team').apply(lambda x: x.mean(skipna=False))
# Factor in any missing players
num_players_per_team = player_data[player_cols].groupby('team').count()['Supercoach']
for team in num_players_per_team.index:
    aggregate.loc[team] = aggregate.loc[team] * (22/num_players_per_team[team])
    
aggs_h = deepcopy(aggregate)
aggs_a = deepcopy(aggregate)
aggs_h.columns = aggregate.columns.map(lambda x: 'h_' + str(x))
aggs_a.columns = aggregate.columns.map(lambda x: 'a_' + str(x))
combined = next_round_matches.merge(aggs_h, left_on='hteam', right_on='team')
combined = combined.merge(aggs_a, left_on='ateam', right_on='team')
combined = get_diff_cols(combined)
pd.set_option('max_columns',500)
Explanation: Try including Bachar Houli
Now we can collect the data for each player and aggregate it to the team level, as we would with the training data
End of explanation
X = combined[features]
X['diff_wins_form']
grid_log_reg.decision_function(X)
grid_log_reg.predict_proba(X)
grid_log_reg.predict(X)
Z = combined[diff_cols]
grid_log_reg_2.predict_proba(Z)
grid_log_reg_2.predict(Z)
combined[['ateam','hteam']]
combined[['h_percentage_form','a_percentage_form']]
combined[['h_career_games_played','a_career_games_played']]
combined[['h_wins_form','a_wins_form']]
model_coef = grid_log_reg.best_estimator_.coef_
X['diff_season_Supercoach']
Explanation: Can now use this to make predictions
End of explanation
coef = []
for i in model_coef:
    for j in i:
        coef.append(abs(j))
zipped = list(zip(features,coef))
zipped.sort(key = lambda x: x[1],reverse=True)
zipped
Explanation: Glue these together and sort
End of explanation
features = [col 
            for col 
            in ['h_career_' + col for col in player_cols_to_agg] + \
            ['h_season_' + col for col in player_cols_to_agg] + \
            ['a_career_' + col for col in player_cols_to_agg] + \
            ['a_season_' + col for col in player_cols_to_agg] + \
            ['h_' + col for col in ladder_cols] + \
            ['h_' + col + '_form' for col in ladder_cols] + \
            ['a_' + col for col in ladder_cols] + \
            ['a_' + col + '_form' for col in ladder_cols] + \
            ['h_career_' + col for col in misc_columns] + \
            ['h_season_' + col for col in misc_columns] + \
            ['a_career_' + col for col in misc_columns] + \
            ['a_season_' + col for col in misc_columns] + \
            diff_cols
            ]
# REMOVE PERCENTAGE FOR NOW
features.remove('h_percentage')
features.remove('a_percentage')
features.remove('diff_percentage')
target = 'winner'
X = model_data[features]
y = model_data[target]
X.fillna(0,inplace=True)
y.fillna(0,inplace=True)
grid_log_reg_2.predict_proba(Z)
combined[['ateam','hteam']]
Explanation: Training model on all data
End of explanation
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
category_names = ['Home','Away']
results = {
    'Collingwood v Richmond': [50.7,49.3],
    'Geelong v Hawthorn': [80.4,19.5],
    'Brisbane Lions v Fremantle': [57.3,42.7],
    'Carlton v Melbourne': [62.4,37.6],
    'Gold Coast v West Coast': [9.9,90.1],
    'Port Adelaide v Adelaide': [58.0,42.0],
    'GWS v North Melbourne': [62.6,37.4],
    'Sydney v Essendon': [75.2,24.8],
    'St Kilda v Footscray': [61.2,38.8]
}
def survey(results, category_names):
    
    Parameters
    ----------
    results : dict
        A mapping from question labels to a list of answers per category.
        It is assumed all lists contain the same number of entries and that
        it matches the length of *category_names*.
    category_names : list of str
        The category labels.
    
    labels = list(results.keys())
    data = np.array(list(results.values()))
    data_cum = data.cumsum(axis=1)
    category_colors = plt.get_cmap('RdYlGn')(
        np.linspace(0.15, 0.85, data.shape[1]))
    fig, ax = plt.subplots(figsize=(18, 10))
    fig.suptitle('Win Probabilities', fontsize=20)
    ax.invert_yaxis()
    ax.xaxis.set_visible(False)
    ax.set_xlim(0, np.sum(data, axis=1).max())
    for i, (colname, color) in enumerate(zip(category_names, category_colors)):
        widths = data[:, i]
        starts = data_cum[:, i] - widths
        ax.barh(labels, widths, left=starts, height=0.5,
                label=colname, color=color)
        xcenters = starts + widths / 2
        r, g, b, _ = color
        text_color = 'white' if r * g * b < 0.5 else 'darkgrey'
        for y, (x, c) in enumerate(zip(xcenters, widths)):
            ax.text(x, y, str(int(c)), ha='center', va='center',
                    color=text_color,fontsize=15)
    ax.legend(ncol=len(category_names), bbox_to_anchor=(0, 1),
              loc='lower left', fontsize=15)
    return fig, ax
survey(results, category_names)
plt.show()
plt.show()
Explanation: Visualisation
End of explanation
from typing import Dict
import numpy as np
def get_season_rounds(matches: pd.DataFrame) -> Dict:
    
    Return a dictionary with seasons as keys and number of games
    in season as values
    
    seasons = matches['season'].unique()
    rounds_in_season = dict.fromkeys(seasons,0)
    
    for season in seasons:
        rounds_in_season[season] = max(matches[matches['season']==season]['round'])
    
    return rounds_in_season
# What teams participated in each season?
def get_season_teams(matches: pd.DataFrame) -> Dict:
    
    Return a dictionary with seasons as keys and a list of teams who played
    in that season as values
    
    seasons = matches['season'].unique()
    teams_in_season = {}
    for season in seasons:
        teams = list(matches[matches['season']==season]['hteam'].unique())
        teams.extend(list(matches[matches['season']==season]['ateam'].unique()))
        teams = np.unique(teams)
        teams_in_season[season] = list(teams)
        
    return teams_in_season
player_cols_to_agg = [
    'AFLfantasy',
    'Supercoach',
    'behinds',
    'bounces',
    'brownlow',
    'clangers',
    'clearances',
    'contested_marks',
    'contested_poss',
    'disposals',
    'frees_against',
    'frees_for',
    'goal_assists',
    'goals',
    'handballs',
    'hitouts',
    'inside50',
    'kicks',
    'marks',
    'marks_in_50',
    'one_percenters',
    'rebound50',
    'tackles',
    'tog',
    'uncontested_poss',
    'centre_clearances',
    'disposal_efficiency',
    'effective_disposals',
    'intercepts',
    'metres_gained',
    'stoppage_clearances',
    'score_involvements',
    'tackles_in_50',
    'turnovers'
]
match_cols = [
    'odds',
    'line'
]
ladder_columns = [
    'wins',
    'losses',
    'draws',
    'prem_points',
    'played',
    'points_for',
    'points_against',
    'percentage',
    'position'
]
misc_columns = [
    'games_played'
]
diff_cols = [
    
]
def get_upcoming_matches(matches, fixture, round_num=None):
    
    if round_num == None: # Get the latest populated round
        round_num = matches['round'].iloc[-1] + 1
        
    fixture['round'] = fixture['round'].astype(str)
    next_round = fixture[fixture['round']==str(round_num)]
    
    # Get list of home and away
    matches.sort_values(by=['season','round'],ascending=False,inplace=True)
    teams = list(next_round['hometeam'])
    teams = list(zip(teams,list(next_round['awayteam']))) # (home, away)
    
    # Initialise upcoming round
    df = pd.DataFrame()
    output = pd.DataFrame(columns = h_ladder_cols + h_ladder_form_cols + a_ladder_cols + a_ladder_form_cols)
    
    # For each team, find the data that is relevant to them
    for team in teams:
        h_last_match = matches[(matches['hteam'] == team[0]) | (matches['ateam'] == team[0])].iloc[0]
        a_last_match = matches[(matches['hteam'] == team[1]) | (matches['ateam'] == team[1])].iloc[0]
        
        # Home team conditions, and use the 'game_cols' to update the ladder and ladder form for that team
        if team[0] == h_last_match['hteam']: # Home team was home team last game
            h_last_match_rel_cols = h_last_match[h_ladder_cols + h_ladder_form_cols + game_cols]
            h_last_match_rel_cols = update_ladder(h_last_match_rel_cols,'home')
        elif team[0] == h_last_match['ateam']: # Home team was away team last game
            h_last_match_rel_cols = h_last_match[a_ladder_cols + a_ladder_form_cols + game_cols]
            h_last_match_rel_cols = update_ladder(h_last_match_rel_cols,'away')
            
        # Away team conditions
        if team[1] == a_last_match['hteam']: # Away team was home team last game
            a_last_match_rel_cols = a_last_match[h_ladder_cols + h_ladder_form_cols + game_cols]
            a_last_match_rel_cols = update_ladder(a_last_match_rel_cols,'home')
        elif team[1] == a_last_match['ateam']: # Away team was away team last game
            a_last_match_rel_cols = a_last_match[a_ladder_cols + a_ladder_form_cols + game_cols]
            a_last_match_rel_cols = update_ladder(a_last_match_rel_cols,'away')
            
        h_last_match_rel_cols['hteam'] = team[0]
        a_last_match_rel_cols['ateam'] = team[1]
        
        # Make sure the columns are the right format
        h_col_final = []
        for col in h_last_match_rel_cols.index:
            if col[0] == 'h':
                h_col_final.append(col)
            else:
                col = 'h' + col[1:]
                h_col_final.append(col)
                
        a_col_final = []
        for col in a_last_match_rel_cols.index:
            if col[0] == 'a':
                a_col_final.append(col)
            else:
                col = 'a' + col[1:]
                a_col_final.append(col)   
        
        h_last_match_rel_cols.index = h_col_final
        a_last_match_rel_cols.index = a_col_final
            
        # Add all of these to the output.
        joined = pd.concat([h_last_match_rel_cols,a_last_match_rel_cols]).to_frame().T
        joined.drop('hscore',axis=1,inplace=True)
        joined.drop('ascore',axis=1,inplace=True)
        output = output.append(joined)
            
    matches.sort_values(by=['season','round'],ascending=True,inplace=True)
    return output
def update_ladder(last_match_rel_cols, last_game_h_a):
    if last_game_h_a == 'home':
        
        # Update wins, losses, draws and prem points
        if last_match_rel_cols['hscore'] > last_match_rel_cols['ascore']:
            last_match_rel_cols['h_wins'] = last_match_rel_cols['h_wins'] + 1
            last_match_rel_cols['h_wins_form'] = last_match_rel_cols['h_wins_form'] + 1
            last_match_rel_cols['h_prem_points'] = last_match_rel_cols['h_prem_points'] + 4
            last_match_rel_cols['h_prem_points_form'] = last_match_rel_cols['h_prem_points_form'] + 4
        elif last_match_rel_cols['hscore'] < last_match_rel_cols['ascore']:
            last_match_rel_cols['h_losses'] = last_match_rel_cols['h_losses'] + 1
            last_match_rel_cols['h_losses_form'] = last_match_rel_cols['h_losses_form'] + 1
        else:
            last_match_rel_cols['h_draws'] = last_match_rel_cols['h_draws'] + 1
            last_match_rel_cols['h_prem_points'] = last_match_rel_cols['h_prem_points'] + 2
            last_match_rel_cols['h_prem_points_form'] = last_match_rel_cols['h_prem_points_form'] + 2
            
        # Update points for and against
        last_match_rel_cols['h_points_for'] = last_match_rel_cols['h_points_for'] + last_match_rel_cols['hscore']
        last_match_rel_cols['h_points_against'] = last_match_rel_cols['h_points_against'] + last_match_rel_cols['ascore']
        last_match_rel_cols['h_points_for_form'] = last_match_rel_cols['h_points_for_form'] + last_match_rel_cols['hscore']
        last_match_rel_cols['h_points_against_form'] = last_match_rel_cols['h_points_against_form'] + last_match_rel_cols['ascore']
        
        # Update percentage
        last_match_rel_cols['h_percentage'] = (last_match_rel_cols['h_points_for'] / last_match_rel_cols['h_points_against']) * 100
        last_match_rel_cols['h_percentage_form'] = (last_match_rel_cols['h_points_for_form'] / last_match_rel_cols['h_points_against_form']) * 100
        
                
    if last_game_h_a == 'away':
        # Update wins, losses, draws and prem points
        if last_match_rel_cols['hscore'] > last_match_rel_cols['ascore']:
            last_match_rel_cols['a_losses'] = last_match_rel_cols['a_losses'] + 1
            last_match_rel_cols['a_losses_form'] = last_match_rel_cols['a_losses_form'] + 1
        elif last_match_rel_cols['hscore'] < last_match_rel_cols['ascore']:
            last_match_rel_cols['a_wins'] = last_match_rel_cols['a_wins'] + 1
            last_match_rel_cols['a_wins_form'] = last_match_rel_cols['a_wins_form'] + 1
            last_match_rel_cols['a_prem_points'] = last_match_rel_cols['a_prem_points'] + 4
            last_match_rel_cols['a_prem_points_form'] = last_match_rel_cols['a_prem_points_form'] + 4
        else:
            last_match_rel_cols['a_draws'] = last_match_rel_cols['a_draws'] + 1
            last_match_rel_cols['a_prem_points'] = last_match_rel_cols['a_prem_points'] + 2
            last_match_rel_cols['a_prem_points_form'] = last_match_rel_cols['a_prem_points_form'] + 2
            
        # Update points for and against
        last_match_rel_cols['a_points_for'] = last_match_rel_cols['a_points_for'] + last_match_rel_cols['ascore']
        last_match_rel_cols['a_points_against'] = last_match_rel_cols['a_points_against'] + last_match_rel_cols['hscore']
        last_match_rel_cols['a_points_for_form'] = last_match_rel_cols['a_points_for_form'] + last_match_rel_cols['ascore']
        last_match_rel_cols['a_points_against_form'] = last_match_rel_cols['a_points_against_form'] + last_match_rel_cols['hscore']
        
        # Update percentage
        last_match_rel_cols['a_percentage'] = (last_match_rel_cols['a_points_for'] / last_match_rel_cols['a_points_against']) * 100
        last_match_rel_cols['a_percentage_form'] = (last_match_rel_cols['a_points_for_form'] / last_match_rel_cols['a_points_against_form']) * 100
                
    return last_match_rel_cols
ladder_columns = {
    ('wins',0),
    ('losses',0),
    ('draws',0),
    ('prem_points',0),
    ('played',0),
    ('points_for',0),
    ('points_against',0),
    ('percentage',100),
    ('position',1)
}
ladder_cols = [i for i,j in ladder_columns]
h_ladder_cols = ['h_' + i for i,j in ladder_columns]
a_ladder_cols = ['a_' + i for i,j in ladder_columns]
h_ladder_form_cols = ['h_' + i + '_form' for i,j in ladder_columns]
a_ladder_form_cols = ['a_' + i + '_form' for i,j in ladder_columns]
h_ladder_form_cols_mapping = dict(zip(ladder_cols,h_ladder_form_cols))
a_ladder_form_cols_mapping = dict(zip(ladder_cols,a_ladder_form_cols))
game_cols = [
    'hscore',
    'ascore'
]
def update_last_game(df):
    for idx,row in df.iterrows():
        
        for col in cols_to_update:
            single_game_col = col[7:] # This is the non-aggregate column, e.g. 'Supercoach' instead of 'career_Supercoach'
            if col[0:7] == 'career_':
                df.at[idx,col] = (df.at[idx,single_game_col] + (df.at[idx,col] * (df.at[idx,'career_games_played']))) / df.at[idx,'career_games_played']
            elif col[0:7] == 'season_':
                df.at[idx,col] = (df.at[idx,single_game_col] + (df.at[idx,col] * (df.at[idx,'season_games_played']))) / df.at[idx,'season_games_played']
            else:
                raise Exception('Column not found, check what columns you\'re passing')
                
    return df
def get_player_names(image_string):
    
    Returns the names of players who are named in a team
    
    names = []
    name = ''
    i = 0
    while i <= len(image_string):
        if image_string[i] == ']':
            name = ''
            i += 2 # Skip the first space
        else:
            i += 1
            continue
        name = ''
        while (image_string[i] != ',') & (image_string[i] != '\n'):
            name += image_string[i]
            i += 1
            if i == len(image_string):
                break
        name = name.replace(' ','_')
        names.append(name)
        i += 1
    return names
def get_player_data(player_ids):
    last_games = pd.DataFrame(columns = players.columns)
    for player in player_ids:
        last_game_row = players[(players['playerid']==player) & (players['next_matchid'].isna())]
        last_games = last_games.append(last_game_row)
    return last_games
player_cols = ['AFLfantasy',
 'Supercoach',
 'behinds',
 'bounces',
 'brownlow',
 'clangers',
 'clearances',
 'contested_marks',
 'contested_poss',
 'disposals',
 'frees_against',
 'frees_for',
 'goal_assists',
 'goals',
 'handballs',
 'hitouts',
 'inside50',
 'kicks',
 'marks',
 'marks_in_50',
 'one_percenters',
 'rebound50',
 'tackles',
 'tog',
 'uncontested_poss',
 'centre_clearances',
 'disposal_efficiency',
 'effective_disposals',
 'intercepts',
 'metres_gained',
 'stoppage_clearances',
 'score_involvements',
 'tackles_in_50',
 'turnovers',
 'matchid',
 'next_matchid',
 'team',
 'career_AFLfantasy',
 'career_Supercoach',
 'career_behinds',
 'career_bounces',
 'career_brownlow',
 'career_clangers',
 'career_clearances',
 'career_contested_marks',
 'career_contested_poss',
 'career_disposals',
 'career_frees_against',
 'career_frees_for',
 'career_goal_assists',
 'career_goals',
 'career_handballs',
 'career_hitouts',
 'career_inside50',
 'career_kicks',
 'career_marks',
 'career_marks_in_50',
 'career_one_percenters',
 'career_rebound50',
 'career_tackles',
 'career_tog',
 'career_uncontested_poss',
 'career_centre_clearances',
 'career_disposal_efficiency',
 'career_effective_disposals',
 'career_intercepts',
 'career_metres_gained',
 'career_stoppage_clearances',
 'career_score_involvements',
 'career_tackles_in_50',
 'career_turnovers',
 'season_AFLfantasy',
 'season_Supercoach',
 'season_behinds',
 'season_bounces',
 'season_brownlow',
 'season_clangers',
 'season_clearances',
 'season_contested_marks',
 'season_contested_poss',
 'season_disposals',
 'season_frees_against',
 'season_frees_for',
 'season_goal_assists',
 'season_goals',
 'season_handballs',
 'season_hitouts',
 'season_inside50',
 'season_kicks',
 'season_marks',
 'season_marks_in_50',
 'season_one_percenters',
 'season_rebound50',
 'season_tackles',
 'season_tog',
 'season_uncontested_poss',
 'season_centre_clearances',
 'season_disposal_efficiency',
 'season_effective_disposals',
 'season_intercepts',
 'season_metres_gained',
 'season_stoppage_clearances',
 'season_score_involvements',
 'season_tackles_in_50',
 'season_turnovers',
 'career_games_played',
 'season_games_played']
def get_diff_cols(matches: pd.DataFrame) -> pd.DataFrame:
    
    Function to take the columns and separate between home and away teams. Each
    metric will have a "diff" column which tells the difference between home
    and away for this metric. i.e. there's a diff_percentage column which tells
    the difference between home and away for the percentage
    
    print('Creating differential columns')
    for col in matches.columns:
        if col[0:2] == 'h_':
            try:
                h_col = col
                a_col = 'a_' + col[2:]
                diff_col = 'diff_' + col[2:]
                matches[diff_col] = matches[h_col] - matches[a_col]
            except TypeError:
                pass
    return matches
from typing import Type
import pandas as pd
class TeamLadder:
    def __init__(self, team: str):
        self.team = team
        for column, init_val in ladder_columns:
            setattr(self, column, init_val)
    def add_prev_round_team_ladder(self, prev_round_team_ladder):
        for col,val in prev_round_team_ladder.items():
            self.__dict__[col] = val
    def update_home_team(self, match):
        self.played += 1
        if match.hscore > match.ascore:
            self.wins += 1
            self.prem_points += 4
        elif match.hscore == match.ascore:
            self.draws += 1
            self.prem_points += 2
        else:
            self.losses += 1    
        self.points_for += match.hscore
        self.points_against += match.ascore
        self.percentage = 100 * (self.points_for / self.points_against)
    def update_away_team(self, match):
        self.played += 1
        if match.hscore < match.ascore:
            self.wins += 1
            self.prem_points += 4
        elif match.hscore == match.ascore:
            self.draws += 1
            self.prem_points += 2
        else:
            self.losses += 1    
        self.points_for += match.ascore
        self.points_against += match.hscore
        self.percentage = 100 * (self.points_for / self.points_against)
    def update_ladder(self, match):
        
        Update the ladder for the team based on the outcome of the game. There
        will be two possibilites - the team can be the home or the away team
        in the provided match.
        
        if self.team == match.teams['home']:
            self.update_home_team(match)
        else:
            self.update_away_team(match)
class Ladder:
    
    Each round object holds the ladder details for that round for each team
    
    def __init__(self, teams_in_season):
        self.teams_in_season = teams_in_season
        self.team_ladders = {}
    def add_team_ladder(self, team_ladder):
        self.team_ladders[team_ladder.team.team] = team_ladder
class Team:
    
    Holds team-level data for a particular match
    
    def __init__(self, generic_team_columns, home_or_away: str):
        self.home_or_away = home_or_away
        for column in generic_team_columns:
            setattr(self, column, None)
    def add_data(self, data: pd.DataFrame):
        if self.home_or_away == 'home':
            for home_col, generic_col in home_cols_mapped.items():
                self.__dict__[generic_col] = data[home_col]
        if self.home_or_away == 'away':
            for away_col, generic_col in away_cols_mapped.items():
                self.__dict__[generic_col] = data[away_col]
class Match:
    
    Holds data about a match, as well as an object for each team
    
    def __init__(self, match_columns):
        self.teams = {
            'home': None,
            'away': None
        }
        for column in match_columns:
            setattr(self, column, None)
    def add_data(self, data: pd.DataFrame):
        for column in self.__dict__.keys():
            try:
                self.__dict__[column] = data[column]
            except KeyError:
                continue
    
    def add_home_team(self, team):
        self.teams['home'] = team
    
    def add_away_team(self, team):
        self.teams['away'] = team
class Round:
    
    Contains match and ladder data for each round
    
    def __init__(self, round_num: int):
        self.round_num = round_num
        self.matches = []
        self.bye_teams = []
        self.ladder = None
    def add_match(self, match):
        self.matches.append(match)
    def add_ladder(self, ladder):
        self.ladder = ladder
class Season:
    
    Contains the rounds for a season, and which teams competed
    
    def __init__(self, year: int, teams):
        self.year = year
        self.teams = teams
        self.rounds = {}
        
    def add_round(self, round_obj: Type[Round]):
        self.rounds[round_obj.round_num] = round_obj
class History:
    
    Holds all season objects
    
    def __init__(self):
        self.seasons = {}
        
    def add_season(self, season):
        self.seasons[season.year] = season
from typing import Dict
def get_season_num_games(matches: pd.DataFrame) -> Dict:
    
    Return a dictionary with seasons as keys and number of games
    in season as values
    
    seasons = matches['season'].unique()
    rounds_in_season = dict.fromkeys(seasons,0)
    
    for season in seasons:
        rounds_in_season[season] = max(matches[matches['season']==season]['h_played']) + 1
    
    return rounds_in_season
Explanation: Metadata and functions
End of explanation
<END_TASK>