<jupyter_start><jupyter_text>IntroductionFinally, we achieved our goal. We classified the candidate players' plays of 2020 season, by using the Logistic Regression Model on the pca features and then ranked them according to their % of plays which belong to the valuable `cluster_3`.After the Clustering Process took place, we are going to train several classifiers, in order to predict the testing dataset's clusters. Then, we will be able to evaluate the candidate players fo the Shooting Guard position.**Scope**: Classify the 'plays' of the testing dataset and assign them to the respective cluster. Validate the predicted clusters and then check each candidate player's ratio of `cluster_3` plays to total plays.In this section, we tried **Logistic Regression**, **Random Forest** and **XGBoost** classifying algorithms in order to effectively register each plays in the respective cluster. Indeed, we resulted in valid clusters, while also correctly predicted that Jrue Holiday is the winner!<jupyter_code># Import the libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn import ensemble
from sklearn.inspection import permutation_importance
import xgboost as xgb
from sklearn.metrics import f1_score
from sklearn.preprocessing import MinMaxScaler
from sklearn.feature_selection import RFE
from sklearn import model_selection
from pickle import dump
from pickle import load
pd.set_option('display.max_columns', None)
%load_ext blackcellmagic
%config InlineBackend.figure_format = 'retina'<jupyter_output><empty_output><jupyter_text>1. Useful Functions<jupyter_code>def run_model(df, folds, feats, model):
    """
    Takes in a dataframe of 'plays', the # folds,
    features list and model, prints and returns
    the mean score across all the folds
    Parameters
    ---------
    df : a dataframe object
            Contains the plays
    folds : int
            Number of foldsZ
    feats : a list object
            Contains the features' columns
    model : a string object
            The model name

    Returns
    -------
    np.mean(scores) : float
            Contains the average score
    """
    
    scores = []
    for fold in range(folds):

        # get training data using folds
        df_train = df[df.kfold != fold].reset_index(drop=True)

        # get validation data using folds
        df_valid = df[df.kfold == fold].reset_index(drop=True)

        # get training data
        x_train = df_train[feats].values

        # get validation data
        x_valid = df_valid[feats].values

        # initialize Logistic Regression model
        model =  model

        # fit model on training data (ohe)
        model.fit(x_train, df_train.gm_cluster.values)

        # predict on validation data
        valid_preds = model.predict(x_valid)

        # get f1_weighted score
        f1 = f1_score(df_valid.gm_cluster.values, valid_preds, average='weighted')

        # print f1
        print(f"Fold = {fold}, F1 = {f1}")

        # register the score in the score list
        scores.append(f1)

    # print total score
    print(f"Model {model} \n===================\nMean F1 Score = {np.mean(scores)}")
    return np.mean(scores)

def feat_importance_mdi(df, feats, model):
    """
    Takes in a dataframe of 'plays', the
    features list and model, plots and returns
    the importances
    Parameters
    ---------
    df : a dataframe object
            Contains the plays
    feats : a list object
            Contains the features' columns
    model : a string object
            The model name
    Returns
    -------
    importances : pd.Series object
            Contains the feature importances
    """
    # define the dataset features and target
    X = df[feats]
    y = df["gm_cluster"]

    # initialize Random Forest model
    model = model

    model.fit(X, y)

    importances = model.feature_importances_

    std = np.std([
        tree.feature_importances_ for tree in model.estimators_], axis=0)

    forest_importances = pd.Series(importances, index=feats)

    fig, ax = plt.subplots()
    forest_importances.plot.bar(yerr=std, ax=ax)
    ax.set_title("Feature importances using MDI")
    ax.set_ylabel("Mean decrease in impurity")
    fig.tight_layout()
    return importances
def feat_importance_logres(df, feats, model):
    """
    Takes in a dataframe of 'plays', the
    features list and model, plots and returns
    the importances
    Parameters
    ---------
    df : a dataframe object
            Contains the plays
    feats : a list object
            Contains the features' columns
    model : a string object
            The model name
    Returns
    -------
    importances : pd.Series object
            Contains the feature importances
    """
    # define the dataset features and target
    X = df[feats]
    y = df["gm_cluster"]

    # initialize Random Forest model
    model = model

    model.fit(X, y)

    #importances = model.feature_importances_
    importances = model.coef_[0]
    idxs = np.argsort(importances)
    logres_importances = pd.Series(importances, index=feats)

    # summarize feature importance
    for i, v in enumerate(importances):
        print('Feature: %0d, Score: %.5f' % (i, v))

    # plot feature importance
    plt.figure(figsize=(10, 8))
    plt.title('Feature Importances')
    plt.barh(range(len(idxs)), importances[idxs], align='center')
    plt.yticks(range(len(idxs)), [feats[i] for i in idxs])
    plt.xlabel('Random Forest Feature Importance')
    plt.show()
    
    return importances
def feat_permutation_importance(df, feats, model):
    """
    Takes in a dataframe of 'plays', features list
    and model, plots and returns the mean score across
    all the folds
    Parameters
    ---------
    df : a dataframe object
            Contains the plays
    feats : a list object
            Contains the features' columns
    model : a string object
            The model name

    Returns
    -------
    importances : pd.Series object
            Contains the importances of features
    """
    
    # define the dataset features and target
    X = df[feats]
    y = df["gm_cluster"]

    # initialize Random Forest model
    model = model

    model.fit(X, y)

    # perform permutation importance
    results = permutation_importance(model, X, y, scoring='f1_weighted')

    # get importance
    importance = results.importances_mean
    idxs = np.argsort(importance)
    importances = pd.Series(importance, index=feats)

    # plot feature importance
    plt.title('Permutation Feature Importance', fontsize=12)
    plt.barh(range(len(idxs)), importances[idxs], align='center')
    plt.yticks(range(len(idxs)), [feats[i] for i in idxs])
    plt.xlabel('Feature Importance')
    plt.show()

    return importances
def compare_lists(l1, l2):
    return [j for i, j in zip(l1, l2) if i == True]
def rfe_process(df, feats, num_feats, model, scores):
    """
    Takes in a dataframe of 'plays', features list,
    the # features, the model and scores, prints the
    features and scores and returns score
    Parameters
    ---------
    df : a dataframe object
            Contains the plays
    feats : a list object
            Contains the features' columns
    num_feats : int
            Contains the # features
    model : a string object
            The model name
    scores  : a dictionary object
            Contains the key:value # features:scores
    Returns
    -------
    importances : pd.Series object
            Contains the importances of features
    """
    # define the dataset features and target
    X = df[feats]
    y = df["gm_cluster"]

    rfe = RFE(
        estimator=model,
        n_features_to_select=num_feats
    )

    # fit RFE
    rfe.fit(X, y)

    new_feats =  compare_lists(rfe.support_, feats)

    mean_score = run_model(df, 5, new_feats, model)

    # print total score
    print(f"Features {new_feats} \n===================\n")

    scores[num_feats] = mean_score
    return scores<jupyter_output><empty_output><jupyter_text>2. Read in Data<jupyter_code># read in the data
train_df = pd.read_csv('../notebooks/plays_17_18_19_labeled_train_folds.csv',
                       converters={'GAME_ID': lambda x: str(x)})
train_df.head()
# define features
train_feats = [
    col
    for col in train_df.columns
    if col
    not in (
        "GAME_ID",
        "TEAM_ID",
        "TEAM_ABBREVIATION",
        "TEAM_CITY",
        "PLAYER_ID",
        "PLAYER_NAME",
        "NICKNAME",
        'gm_cluster',
        'pca_1',
        'pca_2',
        'pca_3',
        'pca_4',
        'NET_SCORE',
        'kfold',
        'GAME_DATE'
    )
]
train_feats

# drop redundant columns - be careful pca components belongs to
# the previous clustering process
train_df.drop(columns=['pca_1', 'pca_2', 'pca_3', 'pca_4'], inplace=True)<jupyter_output><empty_output><jupyter_text>3. Normalisation<jupyter_code>train_df[train_feats].shape
# define training data to be normalized
train_data = train_df[train_feats].values
# initialize scaler
scaler = MinMaxScaler()

# fit scaler
scaler.fit(train_data)

# transform data
train_data_stnd = scaler.transform(train_data)
train_norm_feats = [feat+'_n' for feat in train_feats]
train_norm_df = pd.concat([train_df.reset_index(drop=True), pd.DataFrame(
    data=train_data_stnd, columns=train_norm_feats)], axis=1)
train_norm_df.head()<jupyter_output><empty_output><jupyter_text>4. Try a Baseline Model<jupyter_code># initialize Logistic Regression model
logres = LogisticRegression(
    multi_class='multinomial',
    solver='lbfgs',
    n_jobs=-1)
run_model(train_norm_df, 5, train_norm_feats, logres)<jupyter_output>Fold = 0, F1 = 0.9954893044231355
Fold = 1, F1 = 0.9959608535672826
Fold = 2, F1 = 0.9964345770452878
Fold = 3, F1 = 0.9964386813805151
Fold = 4, F1 = 0.9956466870928642
Model LogisticRegression(multi_class='multinomial', n_jobs=-1) 
===================
Mean F1 Score = 0.9959940207018171<jupyter_text>We shouldn't be comfortable with such a tremendous accuracy from the very beggining. Let's check the features importance. You may call the `feat_immportance_mdi` or `feat_importance_logres` functions to do that. Yet, I choose the `feat_permutation_importance` to be able to use those conclusions to all the models - permutation importance is model agnostic.<jupyter_code>feat_permutation_importance(train_norm_df, train_norm_feats, logres)<jupyter_output><empty_output><jupyter_text>It is very suspicious for a feature to contribute so high importance, hence it has to be further examined.<jupyter_code>run_model(train_norm_df, 5, ['START_POSITION_n'], logres)<jupyter_output>Fold = 0, F1 = 0.8657678611548529
Fold = 1, F1 = 0.86574740253879
Fold = 2, F1 = 0.8660200754897905
Fold = 3, F1 = 0.8667443048794484
Fold = 4, F1 = 0.8656126928897967
Model LogisticRegression(multi_class='multinomial', n_jobs=-1) 
===================
Mean F1 Score = 0.8659784673905356<jupyter_text>Even by using `START_POSITION_n` itself exlusively, we achieve a F1 score of 0.87! We shouldn't be happy, as this is an indicator that the feature potentially fosters overfitting! Let's check the mean stats for the group_1 feats across all the `START_POSITION` values:<jupyter_code>group_1 = ['OFF_RATING', 'AST_PCT', 'AST_TOV', 'TM_TOV_PCT', 'EFG_PCT', 'TS_PCT', 'POSS']
train_norm_df.groupby(by='START_POSITION').agg(np.mean)[group_1]
# remember the encoding
""" mapping = {
    "NaN": 0,
    "G": 1,
    "F": 2, 
    "C": 3
    } """<jupyter_output><empty_output><jupyter_text>And that is; `OFF_RATING`, `AST_TOV`, `EFG_PCT`, `TS_PCT` & `POSS` get the minimum levels for the `START_POSITION` 0 or else NaN. Which means that this variable *betrays* that those players didn't start the game, hence there is high possibility for them to have played less time than the other and as a result have worse stats. To be more particular, the less you play the lower the chance to increase any records (pass, points, etc). In the same context, another variable may also be guilty; `MIN`. It precisely expresses the time a player spent in the court and so we have to ignore it, too.<jupyter_code># re-define norm_feats
train_norm_feats.remove('START_POSITION_n')
feat_permutation_importance(train_norm_df, train_norm_feats, logres)<jupyter_output><empty_output><jupyter_text>Indeed, the same applies for the case of `MIN` - it leaks information of the time spent in the court by the athlete. So, the model knows 'apriori' that the player with higher duration may have better stats.Now we have to repeat the normalization in order for the scaler to be fit in the new shape of data [: , 16] instead of [: , 18].<jupyter_code># re-define norm_feats
train_norm_feats.remove('MIN_n')
feat_permutation_importance(train_norm_df, train_norm_feats, logres)
# re-define feats
train_feats.remove('START_POSITION')
train_feats.remove('MIN')
train_df[train_feats].shape
# define training data to be normalized
train_data = train_df[train_feats].values
# initialize scaler
scaler = MinMaxScaler()

# fit scaler
scaler.fit(train_data)

# transform data
train_data_stnd = scaler.transform(train_data)
# save the scaler
dump(scaler, open('scaler.pkl', 'wb'))
train_norm_feats = [feat+'_n' for feat in train_feats]
# complete training dataset
train_norm_df = pd.concat([train_df.reset_index(drop=True), pd.DataFrame(
    data=train_data_stnd, columns=train_norm_feats)], axis=1)
train_norm_df.head()<jupyter_output><empty_output><jupyter_text>4. PCA<jupyter_code>train_data_pca = train_norm_df.loc[:, train_norm_feats].values
# PCA
pca = PCA()
pca.fit(train_data_pca)
pca.explained_variance_ratio_
plt.figure(figsize=(10, 8))
plt.plot(range(1, 17), pca.explained_variance_ratio_.cumsum(),
         marker='o', linestyle='--')<jupyter_output><empty_output><jupyter_text>We may choose any number of components which is above 7, as it explains at least 90% of the data variance.<jupyter_code># choose 9, retaining almost all the variance level
pca = PCA(n_components=9)
pca.fit(train_data_pca)
train_pca_scores = pca.transform(train_data_pca)
train_norm_pca_df = pd.concat([train_norm_df.reset_index(drop=True), pd.DataFrame(
    data=train_pca_scores, columns=['pca_1', 'pca_2', 'pca_3', 'pca_4', 'pca_5', 'pca_6', 'pca_7', 'pca_8', 'pca_9'])], axis=1)
train_pca_feats = ["pca_"+f"{i}" for i in range(1, 10)]
# save the pca object
dump(pca, open('pca.pkl', 'wb'))
train_norm_pca_df.head()
train_norm_pca_df.describe()<jupyter_output><empty_output><jupyter_text>5. Logistic Regression<jupyter_code># initialize Logistic Regression model this time to further explore its potential
logres = LogisticRegression(
    multi_class='multinomial',
    solver='lbfgs',
    n_jobs=-1)
feat_permutation_importance(train_norm_pca_df, train_norm_feats, logres)<jupyter_output><empty_output><jupyter_text>For the `POSS` we shouldn't think the same as in the case of `MIN`, though. That is because, having the ball for much time in his possesion, doesn't necessarily mean the player played more. There can be situations where one player contribute less by holding possesion and more by moving around the court to 'open' spaces for his teammates.<jupyter_code># drop completely insignificant feats
drop_norm_feats = ['OFF_RATING_n', 'AST_TOV_n', 'PACE_n', 'PACE_PER40_n', 'PIE_n']
drop_feats = ['OFF_RATING', 'AST_TOV', 'PACE', 'PACE_PER40', 'PIE']
# introduce the updated list of significant feats
train_sgnft_norm_feats = [feat for feat in train_norm_feats if feat not in drop_norm_feats]
train_sgnft_feats = [feat for feat in train_feats if feat not in drop_feats]
# run again for the updated feats list
run_model(train_norm_pca_df, 5, train_norm_feats, logres)
run_model(train_norm_pca_df, 5, train_sgnft_feats, logres)<jupyter_output><empty_output><jupyter_text>GridSearch<jupyter_code># define a grid of parameters
param_grid = {
    "C": [0.001, 0.01, 0.1, 1, 10, 100]
}
# initialize grid search
# estimator is the model that we have defined
# we use f1_weighted as our metric
# cv=5 means that we are using 5 fold cv
model = model_selection.GridSearchCV(
    estimator=logres,
    param_grid=param_grid,
    scoring="f1_weighted",
    verbose=10,
    n_jobs=1,
    cv=5
)
X = train_norm_pca_df[train_sgnft_norm_feats].values
y = train_norm_pca_df.gm_cluster.values
# fit model on training data
model.fit(X, y)

print(f"Best score: {model.best_score_}")

print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
    print(f"\t{param_name}: {best_parameters[param_name]}")<jupyter_output><empty_output><jupyter_text>Run Optimised Model<jupyter_code># initialize tuned Logistic Regression model
tuned_logres = LogisticRegression(
    multi_class='multinomial',
    C=10,
    solver='lbfgs',
    n_jobs=-1)
run_model(train_norm_pca_df, 5, train_sgnft_norm_feats, tuned_logres)
run_model(train_norm_pca_df, 5, train_norm_feats, tuned_logres)
run_model(train_norm_pca_df, 5, train_pca_feats, tuned_logres)
# rducd_norm_feats
""" Best score: 0.7129040085726503
Best parameters set:
	C: 100 """

# tunned + all_norm_feats
# 0.7200434027303044 | 0.7127641638161785 % 0.7142677011439387 | %% 0.7144416559579285

# tuned + sgnft_norm_feats
# 0.7129040085726503 | 0.7130101776176886 % 0.7154034911987911 | %% 0.7152999106187636

# tuned + 9_pca_feats
# 0.7097072881908655 | 0.709740666424608 % 0.7113750832534707 | %% 0.7111802053648798

# tuned + 7_pca_feats
# 0.7076065938445443 |<jupyter_output><empty_output><jupyter_text>5. Random Forest<jupyter_code># initialize Random Forest model
rf = ensemble.RandomForestClassifier(
    n_jobs=-1
    )
run_model(train_norm_pca_df, 5, train_feats, rf)
run_model(train_norm_pca_df, 5, train_sgnft_feats, rf)
X = train_norm_pca_df[train_sgnft_feats].values
y = train_norm_pca_df.gm_cluster.values<jupyter_output><empty_output><jupyter_text>Grid Search<jupyter_code># define a grid of parameters
param_grid = {
    "n_estimators": [100, 200, 250, 300, 400, 500],
    "max_depth": [1, 2, 5, 7, 11, 15],
    "criterion": ["gini", "entropy"]
}
# initialize grid search
# we use f1_weighted as our metric
# cv=5 means that we are using 5 fold cv
model = model_selection.GridSearchCV(
    estimator=rf,
    param_grid=param_grid,
    scoring="f1_weighted",
    verbose=10,
    n_jobs=1,
    cv=5
)
# fit model on training data
model.fit(X, y)

# get best score
print(f"Best score: {model.best_score_}")

# get best params
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
    print(f"\t{param_name}: {best_parameters[param_name]}")<jupyter_output><empty_output><jupyter_text>Run optimised model<jupyter_code># initialize tuned Random Forest model
tuned_rf = ensemble.RandomForestClassifier(
    criterion='gini',
    max_depth=15,
    n_estimators=500,
    n_jobs=-1
)
run_model(train_norm_pca_df, 5, train_sgnft_feats, tuned_rf)
run_model(train_norm_pca_df, 5, train_pca_feats, tuned_rf)
run_model(train_norm_pca_df, 5, train_feats, tuned_rf)
# grid % sgnft_feats
""" Best score: 0.7156545519496949
Best parameters set:
	criterion: gini
	max_depth: 15
	n_estimators: 500 """

# tunned + all_feats
# 0.7200434027303044  |  0.7192813050485303  | % 0.7210305851291254 | %% 0.7213350940732122

# tunned + sgnft_feats
# 0.7143466533806893  |  0.7127733551992773  | % 0.7150202352402397 | %% 0.7145480249098884

# tuned + 9_pca_feats
# 0.7081434070788928  |  0.7076707318212638  | % 0.7094585180763383 | %% 0.7100286485287438

# tunned + 7_pca_feats
# 0.7049798741665684<jupyter_output><empty_output><jupyter_text>6. XGBoost<jupyter_code># initialize XGboost Classifier model
xgb_clf = xgb.XGBClassifier(
    n_jobs=-1
)
run_model(train_norm_pca_df, 5, train_feats, xgb_clf)
run_model(train_norm_pca_df, 5, train_sgnft_feats, xgb_clf)<jupyter_output><empty_output><jupyter_text>GridSearch<jupyter_code># define a grid of parameters
param_grid = {
    "max_depth": [3, 5, 7],
    "colsample_bytree": [0.5, 1.0],
    "lambda": [0.1, 1.0],
    "n_estimators": [50, 100, 200]
}
# initialize grid search

# we use f1 as our metric
# cv=5 means that we are using 5 fold cv
model = model_selection.GridSearchCV(
    estimator=xgb_clf,
    param_grid=param_grid,
    scoring="f1_weighted",
    verbose=10,
    n_jobs=1,
    cv=5
)
# fit model on training data
model.fit(X, y)

# get best score
print(f"Best score: {model.best_score_}")

# get best params
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
    print(f"\t{param_name}: {best_parameters[param_name]}")
# initialize the best XGboost
tuned_xgb = xgb.XGBClassifier(
    colsample_bytree=1.0,
   	reg_lambda=0.1,
   	max_depth=3,
   	n_estimators=200,
    n_jobs=-1
)
run_model(train_norm_pca_df, 5, train_feats, tuned_xgb)
run_model(train_norm_pca_df, 5, train_sgnft_feats, tuned_xgb)
run_model(train_norm_pca_df, 5, train_pca_feats, tuned_xgb)
""" Best score: 0.718131949373753
Best parameters set:
	colsample_bytree: 1.0
	lambda: 0.1
	max_depth: 3
	n_estimators: 200 """
	

# tunned + all_feats
# 0.7237187801347641  |  0.723295078704803  | %0.7238411919855139 | %% 0.7238411919855139


# tunned + sgnft_feats
# 0.7179928505381048  |  0.7155588877728325 | %0.7174956985430958 | %% 0.7168115528132881


# tuned + 9_pca_feats
# 0.7096254803578999  |  0.7103574186976042 | % 0.7104575936280648 | %% 0.7104575936280648


# tuned + 7_pca_feats
# 0.7068758569674722<jupyter_output><empty_output><jupyter_text>7.  Prediction  7.1 Prepare Test Data<jupyter_code># prepare the testing dataset
test_df = pd.read_csv('../notebooks/plays_20_test.csv',
                  converters={'GAME_ID': lambda x: str(x)})
test_df.head()
# filter out non-plays
test_df.dropna(subset=["MIN"], inplace=True)
test_df.shape
# drop redundant columns
test_df.drop(
    columns=[
        "COMMENT",
        "E_OFF_RATING",
        "E_DEF_RATING",
        "E_NET_RATING",
        "E_USG_PCT",
        "NET_RATING",
        "E_PACE",
        "START_POSITION",
        "MIN"
    ],
    inplace=True,
)
test_df.head()
# check for nulls
test_df.isnull().sum()
# check for duplicate rows
test_df.duplicated().sum()
# final features
test_feats = [
    col
    for col in test_df.columns
    if col
    not in (
        "GAME_ID",
        "TEAM_ID",
        "TEAM_ABBREVIATION",
        "TEAM_CITY",
        "PLAYER_ID",
        "PLAYER_NAME",
        "NICKNAME",
        "GAME_DATE"
    )
]
test_sgnft_feats = train_sgnft_feats
test_df[test_feats].shape<jupyter_output><empty_output><jupyter_text>Calculate the NET_SCORE accross the testing dataset.<jupyter_code># classify features by domain importance
group_1 = ['OFF_RATING', 'AST_PCT', 'AST_TOV',
           'TM_TOV_PCT', 'EFG_PCT', 'TS_PCT', 'POSS']
group_2 = ['MIN', 'AST_RATIO', 'DREB_PCT']
group_3 = ['OREB_PCT', 'REB_PCT', 'USG_PCT', 'PACE', 'PACE_PER40', 'PIE']
group_4 = ['START_POSITION']
group_5 = ['DEF_RATING']
# allocate weights
wts = []

for col in test_feats:
    if col in group_1:
        wts.append(0.5)
    elif col in group_2:
        wts.append(0.3)
    elif col in group_3:
        wts.append(0.2)
    elif col in group_4:
        wts.append(0)
    else:
        wts.append(-0.3)
wts
# make a dictionary of key:value feature: weight
weights_dict = dict(zip(test_feats, wts))
weights_dict
# segment the eligible for score original dataset
scores_df = test_df.loc[:, test_feats].copy()
scores_df
# the calculations to be executed
test_df['NET_SCORE'] = scores_df.dot(pd.Series(weights_dict))
test_df.sample(20)
# define test data to be normalized
test_data = test_df[test_feats].values
# load the scaler
scaler = load(open('scaler.pkl', 'rb'))
# transform data
test_data_stnd = scaler.transform(test_data)
test_norm_feats = [feat+'_n' for feat in test_feats]
test_norm_df = pd.concat([test_df.reset_index(drop=True), pd.DataFrame(
    data=test_data_stnd, columns=test_norm_feats)], axis=1)
test_norm_df.head()
test_data_pca = test_norm_df.loc[:, test_norm_feats].values
# load the pca
pca = load(open('pca.pkl', 'rb'))
# transform test data
test_pca_scores = pca.transform(test_data_pca)
test_norm_pca_df = pd.concat([test_norm_df.reset_index(drop=True), pd.DataFrame(
    data=test_pca_scores, columns=['pca_1', 'pca_2', 'pca_3', 'pca_4', 'pca_5', 'pca_6', 'pca_7', 'pca_8', 'pca_9'])], axis=1)
test_pca_feats = ["pca_"+f"{i}" for i in range(1, 10)]
test_norm_pca_df.head()
# compare to the final training data
train_norm_pca_df.head()
# switch to the prediction data from 2020-01-01 to 2020-08-31
preds_df = test_norm_pca_df.loc[(
    test_norm_pca_df.GAME_DATE < '2020-11-01'), :].copy()
X_test = preds_df.loc[:, test_pca_feats].values
# Make prediction via the logres model, using the 9 pca_feats
y_pred = tuned_logres.predict(X_test)
preds_df.loc[:, 'pred_cluster'] = y_pred<jupyter_output><empty_output><jupyter_text>7.2 Evaluate clustersFor validation to happen, ground-truth labels are necessary. Howbeit, that is not our case as the testing dataset is not labelled. You may wonder why don't we label it via clustering, but that would lead us to the very same procedure, Cross Validation has already done 5 times-isolate a small portion of data and validate on that.Instead, we are going to further evaluate the classifier by conducting qualitative checks. We can either manually review the labels of a portion of data to ensure they are good or compare the predicted to the training clusters and check that any dominant descriptive statistics still hold. I prefer the latter and analyse next:<jupyter_code>preds_df.pred_cluster.value_counts()
preds_df.groupby(by='pred_cluster').agg(np.mean)[group_1 + ['NET_SCORE']]<jupyter_output><empty_output><jupyter_text>Indeed, `cluster_3` takes again the lead by outperforming the rest along most of the crucial features (`OFF_RATING`, `AST_PCT`, `AST_TOV` and `POSS`). At the same time, `NET_SCORE` proves the same - 5.89 units (109.35-103.46) above the `cluster_1` who follows. 7.3 Results<jupyter_code>candidates = ['<NAME>', '<NAME>', '<NAME>']
Bogdanovic_df = preds_df.loc[preds_df.PLAYER_NAME == '<NAME>', :].copy()
print(Bogdanovic_df.shape)
Bogdanovic_df.pred_cluster.value_counts()
green_df = preds_df.loc[preds_df.PLAYER_NAME == '<NAME>', :].copy()
print(green_df.shape)
green_df.pred_cluster.value_counts()
jrue_df = preds_df.loc[preds_df.PLAYER_NAME == '<NAME>', :].copy()
print(jrue_df.shape)
jrue_df.pred_cluster.value_counts()
Bogdanovic_df.loc[:, 'pred_cluster'].value_counts()

jrue_df.loc[:, 'pred_cluster'].value_counts()
green_df.loc[:, 'pred_cluster'].value_counts()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10676956505450867, 'fraction_numerical': 0.04746562769265351, 'mean_word_length': 4.242340926944226, 'pattern_counts': {'":': 12, '<': 106, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '314639', 'license': 'MIT', 'n_tokens_mistral': 10255, 'n_tokens_neox': 9300, 'n_words': 2418, 'path': 'notebooks/02_classifying[logres_rf_xgboost].ipynb', 'repo_name': 'makispl/ml-nba-transfer-suggestion-app', 'size': '26737'}