Who’s The MVP of NBA This Season? | by Yufeng | Towards Data Science

Don’t tell me it will be Giannis. Let’s find the answer from the data.
I will frame this problem to a machine learning project and finish the project using the general working flow of machine learning similar to that introduced by DEEP LEARNING with Python [1].
Let’s get started.
The question we want to ask is “Who is the MVP of this season in NBA?”
Who is or is not, which seems like a binary classification problem. So, my first attempt is to build a classifier to differentiate MVP players and non-MVP players.
However, I found building a classifier is not practical because I will face the problem of sample bias. Specifically, the number of non-MVP players is much larger than that of MVP players, which will result in the difficulties of training and evaluating the model.
Therefore, I frame it to a regression problem and the output is defined as the MVP voting share each year.
To note, I use my domain knowledge (I am very familiar with the NBA as a big fan) here to choose the correct direction for the project. To make full use of your domain knowledge is very important in conceiving a doable project.
Then, let’s look at the data we have in terms of the input X and output y. The X part of the data is the statistics of the players who have got votes for MVP from the season of 1989–1990 to the season of 2018–2019. The y part of the data is the voting share.
Then the data is separated to train and test dataset. And the test dataset will never be touched until we have our final model.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
This is an important step of a project, which defines the success of the model. In this regression problem, I use the mean squared error (MSE) as the evaluation metric. Specifically, the model is trying to minimize the MSE in the training process.
Since the sample size is small, I use the K-fold cross-validation instead of a one-time train-validation-test split as my evaluation process. When the sample size is small, the splitting of data can actually affect the performance of the model. K-fold cross-validation is usually used to solve the problem.
The preparation of the data includes missing value imputation and feature normalization.
my_imputer = SimpleImputer(strategy="median")my_scaler = StandardScaler()
The feature normalization is essential in regression problems, the different scale of features will be a disaster for the model training if pooled raw to the engine.
In the modeling part, it’s easy to forget to set a baseline model no matter you are a beginner or experienced data scientist.
Here we use a simple linear model as our baseline model and pack it together with the preprocessing of the data into a pipeline. For those who are interested in the pipeline usage, please refer to the Pipeline function in sklearn.
from sklearn.linear_model import LinearRegressionmodel_base = Pipeline([('imputer', my_imputer), ('scaler', my_scaler), ('ln_model',LinearRegression())])model_base.fit(X_train, y_train)
The baseline model now is ready to be compared with.
Next, we are going to develop a model that should perform better than the simple linear regression. I select three candidates, Elastic Net, Random Forest Regressor, and Deep Learning regression.
Elastic Net is the combination of LASSO and Ridge, which penalizes the complexity of the model. If interested, please refer to one of my previous posts below.
towardsdatascience.com
For the hyperparameter tuning process of these three candidates, I write a function that combines preprocessing, hyperparameter space definition and cross-validation process.
def train_hyper_tune(X,y):    # create the pre-processing component    my_scaler = StandardScaler()    my_imputer = SimpleImputer(strategy="median")        # define regressors    ## regressors 1: Logistic Regression    rgs_EN = ElasticNet(random_state=0)    ## regressors 2: Random Forest regressors    rgs_RF = RandomForestRegressor(random_state=0)    ## regressors 3: Deep Learning     rgs_DL = KerasRegressor(build_fn=my_DL)        # define pipeline for three regressors    ## rgs_LR    pipe1 = Pipeline([('imputer', my_imputer), ('scaler', my_scaler), ('en_model',rgs_EN)])    ## rgs_RF    pipe2 = Pipeline([('imputer', my_imputer), ('scaler', my_scaler), ('rf_model',rgs_RF)])    ## rgs_DL    pipe3 = Pipeline([('imputer', my_imputer), ('scaler', my_scaler), ('dl_model',rgs_DL)])        # create hyperparameter space of the three models    ## rgs_LR    param_grid1 = {        'en_model__alpha' : [1e-1,1,10],        'en_model__l1_ratio' : [0,0.5,1]    }    ## rgs_RF    param_grid2 = {        'rf_model__n_estimators' : [50,100],        'rf_model__max_features' : [0.8,"auto"],        'rf_model__max_depth' : [4,5]    }    ## rgs_DL    param_grid3 = {        'dl_model__epochs' : [6,12,18,24],        'dl_model__batchsize' : [256,512]    }        # set GridSearch via 5-fold cross-validation    ## rgs_LR    grid1 = GridSearchCV(pipe1, cv=5, param_grid=param_grid1)    ## rgs_RF    grid2 = GridSearchCV(pipe2, cv=5, param_grid=param_grid2)    ## rgs_DL    grid3 = GridSearchCV(pipe3, cv=5, param_grid=param_grid3)        # run the hyperparameter tunning    grid1.fit(X,y)    grid2.fit(X,y)    grid3.fit(X,y)        # return results of the tunning process    return grid1,grid2,grid3,pipe1,pipe2,pipe3
where the deep learning model my_DL is defined as below:
def my_DL(epochs=6,batchsize=512):    model = Sequential()    model.add(Dense(32,activation='relu'))    model.add(Dense(16,activation='relu'))    model.add(Dense(1))    model.compile(loss='mse',optimizer='rmsprop', metrics=['mae'])    return model
The classification and regression model in a neural network are usually different in the last layer. Here, for the regression modeling, we don’t use any activation function in the last layer: model.add(Dense(1)).
However, if it’s a classification problem, you need to add an activation function, such as sigmoid in the last layer. I have written some similar function in one of my other posts as below:
towardsdatascience.com
We then run the function of the hyperparameter tuning.
my_grid1,my_grid2,my_grid3,my_pipe1,my_pipe2,my_pipe3 = train_hyper_tune(X_train, y_train)
Now we’ve got the best set of hyperparameters for each of the three models. We then need to refit on the entire training dataset using the trained hyperparameters.
def train_on_entire(X,y,pipe,grid_res):    # fit pipeline    pipe.set_params(**grid_res.best_params_).fit(X, y)    # return the newly trained pipeline    return pipemodel1 = train_on_entire(X_train,y_train,my_pipe1,my_grid1)model2 = train_on_entire(X_train,y_train,my_pipe2,my_grid2)model3 = train_on_entire(X_train,y_train,my_pipe3,my_grid3)
We cannot say a model is better than the baseline model (linear regression) based on the training data performance. So, we need one more step evaluation on the test data that we have never touched before.
Now, we have the conclusion that model3 (the deep learning regressor) outperforms the other models by achieving the lowest MSE on the test dataset. So, model3 is our final model.
Usually, a machine learning model project ends here by having a model with pretty good performance.
However, in our project, this step is not sufficient yet to answer the question, “Who is the MVP?” That’s why we are going further.
We are going to apply our trained model to the target question, predicting the MVP. So, I extract the same feature space as our training data from the players’ stats in this season (before the NBA suspension due to COVID19).
We are only interested in the top players who have the chance to win the MVP. They are Giannis, Anthony, Luka, James (Harden), Lebron, and Kawhi.
If you are a basketball fan, you must know these people. If not, you don’t need to know them to understand this machine learning project. Either way, I will not waste time introducing them.
This is the prediction of their MVP vote share.
We got a different answer from the media, our predicted MVP is James Harden! No like, no hate, but all from data. (But personally I do agree with this result. 😀)
After predicting the result, I need to dig deeper into the data to interpret the result or to support the result. So, I compare these players’ stats (our feature space of the model) using the CoxComb Chart.
We can see clearly that James Harden’s stats are all among the best (Game played, points per game, field goal percentage, steal, and so on). That’s why the model predicts him as the MVP for this season.
For those who are interested in generating the CoxComb Chart, please refer to the following post.
towardsdatascience.com
That’s it. A question-driven machine learning project from the beginning to the end.
Applying domain knowledge is important to project design and data collection.Use K-fold Cross-validation when sample size is small.Don’t forget to normalize your features before modeling.Use the performance on the test data to evaluate your model.Use graphics to make sense of your prediction results.James Harden is the MVP.
Applying domain knowledge is important to project design and data collection.
Use K-fold Cross-validation when sample size is small.
Don’t forget to normalize your features before modeling.
Use the performance on the test data to evaluate your model.
Use graphics to make sense of your prediction results.
James Harden is the MVP.
François Chollet. Deep Learning with Python.