<jupyter_start><jupyter_text>Exploring Explainer Dashboards CHAPTER 09 - *Other popular XAI frameworks*From **Applied Machine Learning Explainability Techniques** by [**<NAME>**](https://www.linkedin.com/in/aditya-bhattacharya-b59155b6/), published by **Packt**  ObjectiveIn this notebook, we will explore the application of Explainer Dashboards https://github.com/oegedijk/explainerdashboard for explaining ML models  Installing the modules Install the following libraries in Google Colab or your local environment, if not already installed.<jupyter_code>!pip install --upgrade pandas numpy matplotlib seaborn scikit-learn graphviz==0.18 explainerdashboard<jupyter_output><empty_output><jupyter_text>Loading the modules<jupyter_code>import warnings
warnings.filterwarnings('ignore')

import pandas as pd
pd.set_option('display.max_columns', None)

import numpy as np

import sklearn
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error

from explainerdashboard import RegressionExplainer, ExplainerDashboard

import matplotlib.pyplot as plt
import seaborn as sns

from IPython import display

np.random.seed(123)<jupyter_output><empty_output><jupyter_text>About the data **FIFA Club Position Prediction dataset - [FIFA Club Position Prediction dataset](https://github.com/PacktPublishing/Applied-Machine-Learning-Explainability-Techniques/tree/main/Chapter09/datasets/FIFA_Club_Position)**In this tutorial we will use the FIFA Club Position Prediction dataset to predict the league position of the top football clubs playing in the top football leagues of the world. Please go through the data description to find out more.  Loading the data<jupyter_code># We will read the training data
df_train = pd.read_csv('datasets/FIFA_Club_Position/fifa_league_21_train.csv')
df_train.head()
# Reading the dataset description
with open('datasets/FIFA_Club_Position/data_description.txt') as f:
    contents = f.read()
    print(contents)
# Check dataset dimensions
print(df_train.shape)
df_train.columns
df_train.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 1584 entries, 0 to 1583
Data columns (total 82 columns):
 #   Column                       Non-Null Count  Dtype  
---  ------                       --------------  -----  
 0   short_name                   1584 non-null   object 
 1   overall                      1584 non-null   int64  
 2   potential                    1584 non-null   int64  
 3   value_eur                    1583 non-null   float64
 4   wage_eur                     1584 non-null   float64
 5   age                          1584 non-null   int64  
 6   club_name                    1584 non-null   object 
 7   league_name                  1584 non-null   object 
 8   club_position                1584 non-null   object 
 9   preferred_foot               1584 non-null   object 
 10  weak_foot                    1584 non-null   int64  
 11  skill_moves                  1584 non-null   int64  
 12  international_reputation     1584 non-null   int64  
 13  pace              [...]<jupyter_text>As we can see, the dataset contains close to 81 features, out of which input values like the name of the player, name of the club, name of the league and preferred foot might not be helpful to decide the team's position in the next season. The basic intuition is that the team's position is decided by the quality of the players and strength of the players in different playing positions. So, the player's playing position can be an important feature, as it also gives some indication about the depth of the squad in terms of reserves and substitutes, which can be a deciding factor for predicting the club's position. Let's do some data preprocessing based on this intuitions.  Data Pre-processing<jupyter_code># Drop un-necessary features
features_to_drop = ['short_name', 'club_name', 'league_name', 'preferred_foot']
df_train.drop(columns=features_to_drop, inplace=True)
print(df_train.shape)
# Let's check missing values

sns.displot(
    data=df_train.isna().melt(value_name="missing"),
    y="variable",
    hue="missing",
    multiple="fill",
    height=10,
    aspect=1.2
)
plt.show()<jupyter_output><empty_output><jupyter_text>As we analyze the missing values, we can see that goalkeeping speed has high percentage of missing values. Hence we can also drop the feature. But for the other missing values related to pace, shooting, passing, dribbling, defending and physic, mostly this data is not available for goal keepers. So, instead of dropping the records related to goal keepers, we will keep a constant value of 40 for all the goal keepers, as the intuitively thinking since these goal keepers are professional players, almost all of them would have a score of atleast 40. There is one missing value under value_eur. It is highly possible that the player has decided to retire and hence no value is assigned. So, we will just drop this record as an outlier.<jupyter_code># dropping the feature 'goalkeeping_speed'
df_train.drop(columns=['goalkeeping_speed'], inplace=True)
# data imputation for the features 'pace', 'shooting', 'passing', 'dribbling', 'defending' and 'physic' with a constant value of 40
for features in ['pace', 'shooting', 'passing', 'dribbling', 'defending', 'physic']:
    df_train[features].fillna(40, inplace=True)
# Consider all other missing values as outliers, so just drop them
df_train.dropna(inplace=True)
# Let's separate the labels from training data
labels = df_train['position']
df_train.drop(columns=['position'], inplace=True)
print(df_train.shape)<jupyter_output>(1583, 76)<jupyter_text>Next, for the feature `club_position` which indicates the playing position of the player for the club, we will keep this feature, but since it is a categorical feature, we will apply one-hot encoding. Let's first check the count of the possible playing positions.<jupyter_code>len(df_train['club_position'].value_counts())
# One-Hot Encoding
def encode_features(df, features):
    '''
    Method for one-hot encoding all selected categorical fields
    '''
    for f in features:
        if(f in df.columns):
            encoded = pd.get_dummies(df[f])
            encoded = encoded.add_prefix(f + '_')
            df = pd.concat([df, encoded], axis=1)
        else:
            #print('Feature not found')
            return df
        
    df.drop(columns=features, inplace = True)
    
    return df

encoded_train = encode_features(df_train, features = ['club_position'])
encoded_train.shape
encoded_train.head()<jupyter_output><empty_output><jupyter_text>Now, we have 104 features in total, which is actually alot! Also, data scaling and normalization definitely would have helped to get a better model, but we will avoid that, as like other exercises covered in previous chapters, the goal is to come up with a model explainability method and not with an highly accurate model! If you want to improve model accuracy, my suggestions will be to first select the important features and then train with only the important features. Also, instead of picking all players for each team, you can just select top 15-20 players as mostly they will be part of the playing 11. But we will not focus on getting better model results, we will focus on explaining any model (be it good or bad)! Let's proceed with training the model.  Training the model Currently, ExplainerDashboard only supports scikit-learn compatible models. So, for this example we will use Random Forest Regression algorithm. Please note that we are treating this problem as a regression problem rather than a classification problem as predicting club positions with high accuracy is practically not very feasible. Instead, if we consider this as a regression problem, we can measure the mean absolute error or deviation from the actuals to tune the model further.<jupyter_code>x_train,x_valid,y_train,y_valid = train_test_split(encoded_train,labels,test_size=0.2,random_state=123)
model_skl = RandomForestRegressor(n_estimators=260, random_state=123).fit(x_train, y_train)
y_pred = model_skl.predict(x_valid)
print('Mean absolute error is:')
mean_absolute_error(y_valid, y_pred)<jupyter_output>Mean absolute error is:<jupyter_text>Model evaluation on unseen data<jupyter_code>df_test = pd.read_csv('datasets/FIFA_Club_Position/fifa_league_22_test.csv')

def predict_club_position(df_test, model, club_name, num_player = 10):
    '''
    Predict club position from player quality
    '''
    df_test = encode_features(df_test, features = ['club_position'])
    df_test = df_test[df_test['club_name'] == club_name].head(num_player)
    # display.display(df_test)
    df_test.drop(columns=features_to_drop, inplace=True)
    df_test.drop(columns=['goalkeeping_speed'], inplace=True)
    for features in ['pace', 'shooting', 'passing', 'dribbling', 'defending', 'physic']:
            df_test[features].fillna(40, inplace=True)
    
    out_position = model.predict(df_test)
    return np.median(out_position)
position = predict_club_position(df_test, model_skl, club_name = 'Manchester United', num_player = 10)
print(f"Predicted position is: {round(position)}")
# Now let's get top clubs from every league according to our model for 2021-2022 season

def get_top_clubs(df_test, model, num_player = 15):
    '''
    Get top clubs across all leagues available in the dataset
    '''
    all_leagues = list(df_test['league_name'].unique())
    for league in all_leagues:
        df_test = encode_features(df_test, features = ['club_position'])
        df = df_test[df_test['league_name'] == league]
        all_clubs = list(df['club_name'].unique())
        club_pos_list = []
        for club in all_clubs:
            predicted_pos = predict_club_position(df, model, club_name = club, num_player = num_player)
            club_pos_list.append((club, predicted_pos))
        club_pos_df = pd.DataFrame(club_pos_list, columns = ['Club', 'Position'])
        club_pos_df['Position'] = club_pos_df['Position'].rank(method='first')
        club_pos_df = club_pos_df.sort_values(by=['Position'])
        print('---------------------------------------------------------------')
        print(f'Predicted positions for {league}')
        print('---------------------------------------------------------------')
        display.display(club_pos_df)
get_top_clubs(df_test, model_skl)<jupyter_output>---------------------------------------------------------------
Predicted positions for French Ligue 1
---------------------------------------------------------------<jupyter_text>So, based on the data about the quality of players, the best AutoML model has predicted the club position for the top football leagues of the world. In the real world setting, obviously there are much more variable that needs to be considered, but I think the results from the model is pretty reasonable. Now, let's use explainer dashboard to explain the model results.  Model Explainability using Explainer Dashboards Although explainerdashboards are quite easy to use, but I will recommend, you to go through the documentations https://explainerdashboard.readthedocs.io/en/latest/ to even customize your own explainerdashboard!<jupyter_code>explainer = RegressionExplainer(model_skl, x_valid, y_valid)

ExplainerDashboard(explainer).run()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09776879831342235, 'fraction_numerical': 0.015372452565003514, 'mean_word_length': 3.7457273864110046, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 35, 'https://': 4, 'lorem ipsum': 0, 'www.': 1, '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': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '386932', 'license': 'MIT', 'n_tokens_mistral': 3293, 'n_tokens_neox': 3091, 'n_words': 1152, 'path': 'Chapter09/Explainer_dashboard_example.ipynb', 'repo_name': 'PacktPublishing/Applied-Machine-Learning-Explainability-Techniques', 'size': '11397'}