<jupyter_start><jupyter_text>Defining the Research Question  a) Specifying the Question > The goal of this analysis is to come up with a model that can predict the results of a football game between two teams. Some of the considerations that will form the basis of the prediction model include the team that is home, the team that is playing away and the nature of game/tournament.  b) Defining the Metric for Success > This project will be considered a success when are able to develop a reliable prediction model that has a high accuracy level. The model should also not suffer from any instances of overfitting or underfitting.  c) Understanding the Context > Football is currently the most popular sport in the world. This is according to various sports ranking criteria such as global fan base and audience, viewership on t.v., popularity on the internet, sponsorship deals among other financial and social factors. Among football fans and even professional football analysts, predicting the outcome of a match is quite common.> There are several factors that affect the outcome of a football game. Some teams generally perform better than others as classified in the FIFA rankings. These rankings rate national teams based on their performance on FIFA-recognized international matches.The ranking method has changed overtime since it's inception in 1993.> In general, it would be presumed that a high ranking team would be better suited to win a game against a lower ranking opponent. However, there are other factors that could come into play. Some of these factors include, which team is playing at home versus away and whether or not the match is friendly. Using some of these factors, this analysis aims to take advantage of machine learning algorithms and develop a model that can predict the outcome of a football match. More specifically, I will be using a polynomial and logistic regression approaches to come up with a suitable model.  d) Recording the Experimental Design > Below are the steps that will be conducted in this analysis in order to respond to the research question comprehensively:>* Load data and preview preliminary characteristics >* Data Cleaning (check for and deal with outliers, anomalies and missing data)>* Exploratory Analysis>* Implementing the Solution>* Challenging the Solution >* Follow Up Questions  e) Data Relevance > There are two datasets available for this project. The first dataset contains records of various football matches since 1872 to 2019. Other attributes available in this dataset include the home team, away team, goals scored by home team, goals by away team, location of match and the tournament type.> The second dataset contains the annual FIFA ranking records since 1993 through 2018. Some of the attributes included in the second dataset include country name and code, country rank, date of ranking, previous and total points among other variables.> Both datasets contain relevant information that can allow us to respond to the research question. The available features and records are also sufficient and up to date enough to allow us build a reliable prediction model.  Importing Relevant Libraries<jupyter_code>import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import scipy as sp

from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV
from sklearn import linear_model
from sklearn import metrics
from sklearn.metrics import accuracy_score
from sklearn.model_selection import LeaveOneOut<jupyter_output>/usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
  import pandas.util.testing as tm<jupyter_text>Reading the Data<jupyter_code># reading the two datasets

fifa_ranking = pd.read_csv('fifa_ranking.csv')
match_results = pd.read_csv('results.csv')<jupyter_output><empty_output><jupyter_text>Checking the Data > a) Fifa Rankings Dataset<jupyter_code># previewing a random sample of the data

fifa_ranking.sample(n=4)
# previewing the top 5 records

fifa_ranking.head(5)
# previewing the bottom 5 records 

fifa_ranking.tail(5)
# checking the number of rows and columns

fifa_ranking.shape
# checking datatypes of each attribute and previewing the existense of null values

fifa_ranking.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 57793 entries, 0 to 57792
Data columns (total 16 columns):
 #   Column                   Non-Null Count  Dtype  
---  ------                   --------------  -----  
 0   rank                     57793 non-null  int64  
 1   country_full             57793 non-null  object 
 2   country_abrv             57793 non-null  object 
 3   total_points             57793 non-null  float64
 4   previous_points          57793 non-null  int64  
 5   rank_change              57793 non-null  int64  
 6   cur_year_avg             57793 non-null  float64
 7   cur_year_avg_weighted    57793 non-null  float64
 8   last_year_avg            57793 non-null  float64
 9   last_year_avg_weighted   57793 non-null  float64
 10  two_year_ago_avg         57793 non-null  float64
 11  two_year_ago_weighted    57793 non-null  float64
 12  three_year_ago_avg       57793 non-null  float64
 13  three_year_ago_weighted  57793 non-null  float64
 14  confederation         [...]<jupyter_text>>* The are no nulls in this dataframe. The rank_date column needs to be changed into a datetime type. > b) Match Results Dataset<jupyter_code># previewing a random sample of the second dataset

match_results.sample(n=4)
# previewing the top 5 records in the data

match_results.head(5)
# previewing the bottom 5 records

match_results.tail(5)
# checking the number of records and attributes 

match_results.shape
# checking datatypes of each attribute and previewing the existense of null values

match_results.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 40839 entries, 0 to 40838
Data columns (total 9 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   date        40839 non-null  object
 1   home_team   40839 non-null  object
 2   away_team   40839 non-null  object
 3   home_score  40839 non-null  int64 
 4   away_score  40839 non-null  int64 
 5   tournament  40839 non-null  object
 6   city        40839 non-null  object
 7   country     40839 non-null  object
 8   neutral     40839 non-null  bool  
dtypes: bool(1), int64(2), object(6)
memory usage: 2.5+ MB<jupyter_text>>* This second dataframe also has no missing values. Date column needs to be updated to appropriate data type.>* The neutral column should also be an object type and not a bool.  Data Cleaning > We will perform some preliminary data cleaning on the two dataframes then merge the two datasets using an inner join so that we have all results of past matches as well as the national team rankings in one dataframe. We can then perform additional data cleaning procedures on the merged dataset.<jupyter_code># First, convert boolean type to string

booleandf = match_results.select_dtypes(include=[bool])
booleanDictionary = {True: 'TRUE', False: 'FALSE'}

for column in booleandf:
    match_results['neutral'] = match_results['neutral'].map(booleanDictionary)<jupyter_output><empty_output><jupyter_text>>* We need to have records of matches where the teams are actually true home away teams by dropping the records where teams are playing in neutral grounds<jupyter_code># Drop records where neutral is true

match_results = match_results.drop(match_results[match_results.neutral == 'TRUE'].index)

match_results.neutral.value_counts()
# Need to convert date column from object to date type

fifa_ranking['rank_date'] = pd.to_datetime(fifa_ranking['rank_date'])
match_results['date'] = pd.to_datetime(match_results['date'])

# Combined month and year column

fifa_ranking['month_year'] = pd.to_datetime(fifa_ranking['rank_date']).dt.to_period('M')
match_results['month_year'] = pd.to_datetime(match_results['date']).dt.to_period('M')
# Dropping unneccessary columns from the fifa ranking dataset

fifa_ranking = fifa_ranking.drop(columns=['country_abrv', 'total_points', 'previous_points','rank_change', 'cur_year_avg',
       'cur_year_avg_weighted', 'last_year_avg', 'last_year_avg_weighted',
       'two_year_ago_avg', 'two_year_ago_weighted', 'three_year_ago_avg',
       'three_year_ago_weighted'])
# We can now use an inner join to merge the two datasets

# First we merge on the home team to get the home team ranks

home_team = pd.merge(match_results, fifa_ranking, left_on= ['home_team', 'month_year'], 
                     right_on= ['country_full', 'month_year'], how = 'inner')

# renaming the rank colum to home_team_rank

home_team.rename(columns={'rank' : 'home_team_rank'}, inplace=True)
home_team
# Next, we merge on the away team to get their ranks

away_team = pd.merge(match_results, fifa_ranking, left_on=['away_team', 'month_year'],
                     right_on= ['country_full', 'month_year'], how = 'inner')

away_team.rename(columns={'rank' : 'away_team_rank'}, inplace=True)
away_team<jupyter_output><empty_output><jupyter_text>>* The away_team and home_team dataframe have similar columns so we can get only the unique column from the away_team to merge with full home_team dataframe in order to produce the final dataframe<jupyter_code># extract unique colums for away_team

final_away_team = away_team[['date','home_team', 'away_team', 'away_team_rank']]
final_away_team
# Merge home team dataframe and final away team dataframe

final = pd.merge(home_team, final_away_team, left_on = ['home_team', 'away_team', 'date'],
                 right_on = ['home_team', 'away_team', 'date'], how = 'inner')

final
# Drop unnecessary columns in the final dataframe

final = final.drop(columns=['country_full', 'confederation', 'rank_date', 'month_year', 'city',
                            'country'])
final
# Checking for duplicate records

final.duplicated().any()
# Drop duplicates 

final.drop_duplicates(keep = 'first', inplace=True)
# Verify there are no more duplicates

final.duplicated().any()
# Number of records after dropping duplicates

final.shape<jupyter_output><empty_output><jupyter_text>> Outliers<jupyter_code># Checking for outliers using boxplots

fig, ax = plt.subplots(1,2, figsize=(20,8))
fig.suptitle('Boxplots')
sns.boxplot(data=final, y = "home_score", ax=ax[0])
ax[0].set_title('Box Plot of Home Team Scores')
sns.boxplot(data=final, y = "away_score", ax=ax[1])
ax[1].set_title('Box Plot of Away Team Scores')

plt.show()<jupyter_output><empty_output><jupyter_text>>* Since the above outliers could be valid observations, we will not remove them.<jupyter_code># boxplot for rank attribute

fig, ax = plt.subplots(1,2, figsize=(20,8))
fig.suptitle('Boxplots')
sns.boxplot(data=final, y = "home_team_rank", ax=ax[0])
ax[0].set_title('Box Plot of Home Team Scores')
sns.boxplot(data=final, y = "away_team_rank", ax=ax[1])
ax[1].set_title('Box Plot of Away Team Scores')

plt.show()
# Checking for missing values

final.isnull().sum()
# extracting year column from the final dataframe

final['year'] = final['date'].dt.year<jupyter_output><empty_output><jupyter_text>Exploratory Data Analysis  Univariate Analysis<jupyter_code># frequency table for home teams

final.home_team.value_counts()
# frequency table for away_team

final.away_team.value_counts()
# frequency table for tournament type

final.tournament.value_counts()
# histograms to show distribution of goals scores

fig, ax=plt.subplots(1,2, figsize=(20,10))
final['away_score'].plot.hist(ax=ax[0], bins=10, edgecolor='black', color='darkorange')
ax[0].set_title('Distribution of Scores by Away Teams')

final['home_score'].plot.hist(ax=ax[1], bins=10, edgecolor='royalblue')
ax[1].set_title('Distribution of Scores by Home Teams')


plt.show()
# Frequency distribution plots

col_names = ['home_team_rank','away_team_rank']
fig, ax = plt.subplots(len(col_names), figsize=(10,20))

for i, col_val in enumerate(col_names):

    sns.distplot(final[col_val], hist=True, ax=ax[i])
    ax[i].set_title('Frequency distribution of '+col_val, fontsize=10)
    ax[i].set_xlabel(col_val, fontsize=8)
    ax[i].set_ylabel('Count', fontsize=8)

plt.show()<jupyter_output><empty_output><jupyter_text>> Measures of Central Tendency<jupyter_code># mean, mode and median of home scores

print('Mean score by home team is ' + str(round(final.home_score.mean(),0)))
print('Median score by home team is ' + str(final.home_score.median()))
print('Mode score by home team is ' + str(final.home_score.mode()))
# mean, mode and median of away scores

print('Mean score by away team is ' + str(round(final.away_score.mean(),0)))
print('Median score by away team is ' + str(final.away_score.median()))
print('Mode score by away team is ' + str(final.away_score.mode()))<jupyter_output>Mean score by away team is 1.0
Median score by away team is 1.0
Mode score by away team is 0    0
dtype: int64<jupyter_text>>* The most occuring score for home teams is 1 while the most occuring score for away teams is 0. In addition, the average score for home teams is 2 while the average score for away teams is 1. These two observations 'loosely' point towards a likely advantage for teams playing at home. > Measures of Dispersion<jupyter_code># summary of standard deviation, range, and interquatile range

final.describe()<jupyter_output><empty_output><jupyter_text>Bivariate Analysis<jupyter_code># Checking the pearson correlation coefficient 

sns.heatmap(final.corr(),annot=True)
plt.show()<jupyter_output><empty_output><jupyter_text>>* The home team rank has a slight positive correlation with the away team rank. >* It is also the case that as the opponent team ranks poorly (in this case a larger rank number) the likelihood of a team scoring more goes up slightly. This is the case for both home and away teams.<jupyter_code># Checking the total number of matches played by year

final['year'].value_counts().plot.bar(figsize=(20,12))
# Bar chart showing the top 10 highest ranking home teams by total scores

total_home_score = final.groupby('home_team')['home_score'].sum().reset_index()
total_home_score.columns = ['team', 'scores'] 
total_home_goals = total_home_score.sort_values('scores', ascending=False)

plt.figure(figsize=(12,8))
ax= sns.barplot("scores","team",data=total_home_goals[:10])
# Bar chart showing the top 10 highest ranking away teams by total scores

total_away_score = final.groupby('away_team')['away_score'].sum().reset_index()
total_away_score.columns = ['team', 'scores'] 
total_away_goals = total_away_score.sort_values('scores', ascending=False)

plt.figure(figsize=(12,8))
ax= sns.barplot("scores","team",data=total_away_goals[:10])<jupyter_output><empty_output><jupyter_text>Implementing the Solution > Below are the steps I follow in this section of the analysis:>* Perform any necessary feature engineering>* Check for multicollinearity>* Develop model>* Cross-validate the model>* Compute RMSE>* Create residual plots for your models, and assess their heteroscedasticity using Bartlett’s test  Feature Engineering > Before we begin regression analysis, we first need to create an additional column showing whether a match was a win, loss or draw from the home team's perspective.<jupyter_code># creating a list of our conditions

conditions = [final['home_score'] > final['away_score'],
              final['home_score'] < final['away_score'],
              final['home_score'] == final['away_score']]

# create a list of the values to assign each condition

values = ['Win', 'Loss', 'Draw']

# creating a new columnn status column using the conditions and values above

final['status'] = np.select(conditions, values)

final.head()<jupyter_output><empty_output><jupyter_text>Approach 1: Polynomial Regression<jupyter_code># Dropping all columns that are no longer needed for the polynomial regression analysis

poly_final = final.drop(columns=['date', 'neutral', 'home_team','away_team', 'status', 'year'])

poly_final.head()
# encoding the tournament column with numeric codes

# import labelencoder
from sklearn.preprocessing import LabelEncoder

# apply label encoder to appropriate column

le = LabelEncoder()
poly_final['tournament'] = le.fit_transform(poly_final['tournament'])

poly_final.head()<jupyter_output><empty_output><jupyter_text>> Checking for multicollinearity<jupyter_code># Remove the dependent columns so as to focus on independent variables only.

independent_only = poly_final.drop(columns=['home_score', 'away_score'])

# Display the correlations between the independent variables

correlations = independent_only.corr()

correlations
# Use calculated correlations to compute the VIF score for each variable

pd.DataFrame(np.linalg.inv(correlations.values), index = correlations.index, columns=correlations.columns)<jupyter_output><empty_output><jupyter_text>>* From the result above, the VIF score for the tournament type is 1.05 and that for home team rank and away team rank are 1.20 and 1.21 respectively. (The VIF score is found along the diagonal from top left to bottom left). >* Since all the VIF scores are below 5, we can say that there is no multicollinearity thus no correlation between the independent variables.  Model 1: Predict home team goals<jupyter_code># define the target variable and features
mycols = [col for col in poly_final.columns if col not in ['home_score', 'away_score']]

x = poly_final[mycols]
y = poly_final['home_score']

# splitting the dataset into train and test sets
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state = 13, test_size = 0.3)

# scaling the train and test sets
sc = StandardScaler()
x_train = sc.fit_transform(x_train)
x_test = sc.transform(x_test)

# fitting the polynomial regression to the dataset
poly_features = PolynomialFeatures(degree = 2,include_bias = False)
x_train = poly_features.fit_transform(x_train)
x_test = poly_features.fit_transform(x_test)

# training the model
regressor = LinearRegression()
regressor.fit(x_train, y_train)

# making predictions
y_pred = regressor.predict(x_test)
# checking a sample of the predictions 
df_pred = pd.DataFrame({'y_predicted': y_pred, 'y_test': y_test})
df_pred.sample(5)
# assess the accuraccy of the model

print('Mean Absolute Error:', metrics.mean_absolute_error(y_test, y_pred))  
print('Mean Squared Error:', metrics.mean_squared_error(y_test, y_pred))  
print('Root Mean Squared Error:', np.sqrt(metrics.mean_squared_error(y_test, y_pred)))<jupyter_output>Mean Absolute Error: 1.0280736713667429
Mean Squared Error: 1.8727988557485344
Root Mean Squared Error: 1.368502413497519<jupyter_text>>* The RMSE score is 1.36 which is quite high considering that the mean score by home teams is 2. We need to cross validate the model to identify potential biases. > Cross Validation using LeaveOneOut K-fold Cross-Validation Method<jupyter_code># define the features and target
x = poly_final[["tournament", "home_team_rank", "away_team_rank"]].values
y = poly_final["home_score"].values

# define the k-fold cross validation method
folds = LeaveOneOut()

#assess cross validation model
RMSES = []
count = 0
for train_index, test_index in folds.split(x):
  # set up train and test based on split determined by k-fold
  x_train, x_test = x[train_index], x[test_index]
  y_train, y_test = y[train_index], y[test_index]

  # fitting the polynomial regression model
  poly_regressor = PolynomialFeatures(degree = 1)    
  x_reg = poly_regressor.fit_transform(x_train)      

  #applying polynomial feature to linear regression
  pol_reg = LinearRegression()                          
  pol_reg.fit(x_reg, y_train)

  # making predictions
  y_pred = pol_reg.predict(poly_regressor.fit_transform(x_test))

  # assessing the accuracy of the model
  rmse_value = np.sqrt(metrics.mean_squared_error(y_test, y_pred))
  RMSES.append (rmse_value)

  count = count + 1

print('Trained '+str(count) + ' models')
print('The Mean of the RMSES is: ' + str (np.mean(RMSES)))<jupyter_output>Trained 12916 models
The Mean of the RMSES is: 1.0438161606444116<jupyter_text>>* We get a slightly lower RMSE here which means that this is a better model to use given our current data set. > Residuals and Heteroskedasticity testing<jupyter_code># Create the residuals

residuals = np.subtract(y_pred, y_test)

# Describe the residuals

pd.DataFrame(residuals).describe()<jupyter_output><empty_output><jupyter_text>>* The results above show that the residuals are centered around the mean.<jupyter_code># Heteroskedasticity test

test_result, p_value = sp.stats.bartlett(y_pred, residuals)

# Computing a critical value of the chi squared distribution

degree_of_freedom = len(y_pred)-1
probability = 1 - p_value

critical_value = sp.stats.chi2.ppf(probability, degree_of_freedom)
print(critical_value)
print(test_result)


if (test_result > critical_value):
  print('the variances are unequal, and the model should be reassessed')
else:
  print('the variances are homogeneous!')<jupyter_output>nan
nan
the variances are homogeneous!<jupyter_text>Model 2: Predict away team goals<jupyter_code># define the target variable and features
mycols = [col for col in poly_final.columns if col not in ['home_score', 'away_score']]

x = poly_final[mycols]
y = poly_final['away_score']


# splitting the dataset into train and test sets
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state = 13, test_size = 0.3)


# scaling the train and test sets
sc = StandardScaler()
x_train = sc.fit_transform(x_train)
x_test = sc.transform(x_test)

# fitting the polynomial regression to the dataset
poly_features = PolynomialFeatures(degree = 1,include_bias = False)
x_train = poly_features.fit_transform(x_train)
x_test = poly_features.fit_transform(x_test)

# training the model
regressor = LinearRegression()
regressor.fit(x_train, y_train)

# making predictions
y_pred = regressor.predict(x_test)
# checking a sample of the predictions 
df_pred = pd.DataFrame({'y_predicted': y_pred, 'y_test': y_test})
df_pred.sample(5)
# assess the accuraccy of the model

print('Mean Absolute Error:', metrics.mean_absolute_error(y_test, y_pred))  
print('Mean Squared Error:', metrics.mean_squared_error(y_test, y_pred))  
print('Root Mean Squared Error:', np.sqrt(metrics.mean_squared_error(y_test, y_pred)))<jupyter_output>Mean Absolute Error: 0.7985568191516141
Mean Squared Error: 1.0827411947997405
Root Mean Squared Error: 1.0405485067019895<jupyter_text>>* The RMSE score is 1.04 which is high considering that the mean score by away teams is 1. This means we need to cross validate the model to identify potential biases. > Cross Validation using LeaveOneOut Method<jupyter_code># define the features and target
x = poly_final[["tournament", "home_team_rank", "away_team_rank"]].values
y = poly_final["away_score"].values

# define the k-fold cross validation method
folds = LeaveOneOut()

#assess cross validation model
RMSES = []
count = 0
for train_index, test_index in folds.split(x):
  # set up train and test based on split determined by k-fold
  x_train, x_test = x[train_index], x[test_index]
  y_train, y_test = y[train_index], y[test_index]

  # fitting the polynomial regression model
  poly_regressor = PolynomialFeatures(degree = 1)    
  x_reg = poly_regressor.fit_transform(x_train)      

  #applying polynomial feature to linear regression
  pol_reg = LinearRegression()                          
  pol_reg.fit(x_reg, y_train)

  # making predictions
  y_pred = pol_reg.predict(poly_regressor.fit_transform(x_test))

  # assessing the accuracy of the model
  rmse_value = np.sqrt(metrics.mean_squared_error(y_test, y_pred))
  RMSES.append (rmse_value)

  count = count + 1

print('Trained '+str(count) + ' models')
print('The mean of the RMSES is: ' + str (np.mean(RMSES)))<jupyter_output>Trained 12916 models
The mean of the RMSES is: 0.8061536906156043<jupyter_text>> Residual plots and Heteroskedasticity testing<jupyter_code># Calculate the residuals

residuals2 = np.subtract(y_pred, y_test)

# Let's describe our residual:
pd.DataFrame(residuals).describe()<jupyter_output><empty_output><jupyter_text>>* The results above show that the residuals are centered around the mean.<jupyter_code># Heteroskedasticity test

test_result, p_value = sp.stats.bartlett(y_pred, residuals)

# To interpret the results we must also compute a critical value of the chi squared distribution
degree_of_freedom = len(y_pred)-1
probability = 1 - p_value

critical_value = sp.stats.chi2.ppf(probability, degree_of_freedom)
print(critical_value)
print(test_result)


if (test_result > critical_value):
  print('the variances are unequal, and the model should be reassessed')
else:
  print('the variances are homogeneous!')<jupyter_output>nan
nan
the variances are homogeneous!<jupyter_text>Approach 2: Logistic Regression<jupyter_code># Creating dataframe to use for this regression analysis
log_final =final.drop(columns=['date', 'home_team', 'away_team', 'year', 'neutral'])
log_final
# encoding the tournament column with numeric codes

le = LabelEncoder()
log_final['tournament'] = le.fit_transform(log_final['tournament'])

log_final.head()
# Checking the distribution of the target variable

sns.countplot(x='status',data=log_final, palette='hls')
# encoding the status column with numeric codes

log_final['status'] = le.fit_transform(log_final['status'])

log_final.head()
# Declaring the features and target
x = log_final.drop("status",axis=1)
y = log_final["status"]

# creating the training and test sets
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = .3, random_state=25)

# fitting the model
LogReg = LogisticRegression()
LogReg.fit(x_train, y_train)

# Using our model to make a prediction
y_predicted = LogReg.predict(x_test)
# checking a sample of the predictions 
df_predicted = pd.DataFrame({'y_predicted': y_predicted, 'y_test': y_test})
df_predicted.sample(5)
# Evaluating the model

confusion_matrix = confusion_matrix(y_test, y_predicted)
print(confusion_matrix)

# Checking the model accuracy

accuracy = accuracy_score(y_test, y_predicted)
print('The accuracy of the model is', accuracy)<jupyter_output>[[ 929    0    0]
 [   0 1000    0]
 [   0    0 1946]]
The accuracy of the model is 1.0<jupyter_text>>* The results from the confusion matrix tell us that 929, 1000 and 1946 are the correct predictions. There are no incorrect predictions as confirmed by the accuracy model of 1.>* This is a clear case of overfitting in the model. We can perform hyperparameter tuning to assess the model further. > Hyperparameter tuning<jupyter_code># Method 1: Grid Search

# Declaring the features and target
x = log_final.drop("status",axis=1)
y = log_final["status"]

# Creating the logistic regression
logistic = linear_model.LogisticRegression()

# Creating regularization penalty space
penalty = ['l1', 'l2']

# Creating regularization hyperparameter space
C = np.logspace(0, 4, 10)

# Creating hyperparameter options
hyperparameters = dict(C=C, penalty=penalty)

# Creating grid search using 5-fold cross validation
clf = GridSearchCV(logistic, hyperparameters, cv=3, verbose=0)

# Fitting grid search
best_model = clf.fit(x, y)
# Viewing best hyperparameters
print('Best Penalty:', best_model.best_estimator_.get_params()['penalty'])
print('Best C:', best_model.best_estimator_.get_params()['C'])
# Define features, target and train and test sets

X = log_final.iloc[:, 0:5].values
y = log_final.iloc[:, 5].values

X_train, X_test, y_train, y_test = train_test_split(X, y)

# Scaling our Data
from sklearn.preprocessing import StandardScaler
feature_scaler = StandardScaler()
X_train = feature_scaler.fit_transform(X_train)
X_test = feature_scaler.transform(X_test)

# Training and Cross Validation
from sklearn.ensemble import RandomForestClassifier
classifier = RandomForestClassifier(n_estimators=300, random_state=0)

from sklearn.model_selection import cross_val_score
all_accuracies = cross_val_score(estimator=classifier, X=X_train, y=y_train, cv=5)

print(all_accuracies.mean())
# dictionary of all parameters and the values we want to test for best performance

grid_param = {
    'n_estimators': [100, 300, 500, 800, 1000],
    'criterion': ['gini', 'entropy'],
    'bootstrap': [True, False]
}
#Instantiating GridSearchCV object
from sklearn.model_selection import GridSearchCV
gd_sr = GridSearchCV(estimator=classifier,
                     param_grid=grid_param,
                     scoring='accuracy',
                     cv=5,
                     n_jobs=-1)
#Calling the fit method
gd_sr.fit(X_train, y_train)
# Checking the parameters that return the highest accuracy

best_parameters = gd_sr.best_params_
print(best_parameters)

# Finding the obtained accuracy

best_result = gd_sr.best_score_
print(best_result)
# Method 2: Random Search

# specify parameters and distributions to sample from
from scipy.stats import randint as sp_randint
param_dist = {"max_depth": [3, None],
              "max_features": sp_randint(1, 11),
              "min_samples_split": sp_randint(2, 11),
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"]}
# Instantiating RandomizedSearchCV object 

from sklearn.model_selection import RandomizedSearchCV 
random_sr = RandomizedSearchCV(classifier, param_dist, cv = 5) 
# Calling the fit method

random_sr.fit(X_train, y_train)
# Checking the parameters that return the highest accuracy

best_parameters = random_sr.best_params_
print(best_parameters)
# Finding the obtained accuracy

best_result = random_sr.best_score_
print(best_result)<jupyter_output>0.9995870967466953
{'fraction_non_alphanumeric': 0.08746832066159797, 'fraction_numerical': 0.020041349873282648, 'mean_word_length': 5.198635799917321, 'pattern_counts': {'":': 5, '<': 100, '<?xml version=': 0, '>': 155, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '157768', 'license': 'MIT', 'n_tokens_mistral': 9662, 'n_tokens_neox': 8961, 'n_words': 3095, 'path': 'Football_Match_Results_Prediction.ipynb', 'repo_name': 'JoanYego/Soccer-Match-Prediction-Model', 'size': '29988'}