Now that the NBA season is done, we have complete data from this year’s NBA rookies. In the past I have tried to predict NBA rookies’ future performance using regressionmodels. In this post I am again trying to predict rookies’ future performance, but now using using a classification approach. When using a classification approach, I predict whether player X will be a “great,” “average,” or “poor” player rather than predicting exactly how productive player X will be.
In the past I used k-means to group players according to their performance (see my post on grouping players for more info). Here, I use a gaussian mixture model (GMM) to group the players. I use the GMM model because it assigns each player a “soft” label rather than a “hard” label. By soft label I mean that a player simultaneously belongs to several groups. For instance, Russell Westbrook belongs to both my “point guard” group and my “scorers” group. K-means uses hard labels where each player can only belong to one group. I think the GMM model provides a more accurate representation of players, so I’ve decided to use it in this post. Maybe in a future post I will spend more time describing it.
For anyone wondering, the GMM groupings looked pretty similar to the k-means groupings.
对于任何想知道的人,GMM分组看起来都非常类似于k-means分组。
1122334455667788991010111112121313
In this past I have attempted to predict win shares per 48 minutes. I am using win shares as a dependent variable again, but I want to categorize players.
在过去,我试图预测每48分钟的获胜份额。 我再次将获胜份额用作因变量,但我想对球员进行分类。
Below I create a histogram of players’ win shares per 48.
下面,我创建了每48个玩家获胜份额的直方图。
I split players into 4 groups which I will refer to as “bad,” “below average,” “above average,” and “great”: Poor players are the bottom 10% in win shares per 48, Below average are the 10-50th percentiles, Above average and 50-90th percentiles, Great are the top 10%. This assignment scheme is relatively arbitrary; the model performs similarly with different assignment schemes.
My goal is to use rookie year performance to classify players into these 4 categories. I have a big matrix with lots of data about rookie year performance, but the reason that I grouped player using the GMM is because I suspect that players in the different groups have different “paths” to success. I am including the groupings in my classification model and computing interaction terms. The interaction terms will allow rookie performance to produce different predictions for the different groups.
By including interaction terms, I include quite a few predictor features. I’ve printed the number of predictor features and the number of predicted players below.
通过包括交互项,我包括了很多预测器功能。 我在下面打印了预测功能的数量和预测玩家的数量。
fromsklearnimportpreprocessingdf_drop=df[df['Year']>1980]forxinnp.unique(new_labels):Label='Category%d'%xrookie_df_drop[Label]=df_drop[Label]#give rookies the groupings produced by the GMM modelX=rookie_df_drop.as_matrix()#take data out of dataframe poly=preprocessing.PolynomialFeatures(2,interaction_only=True)#create interaction terms.X=poly.fit_transform(X)Career_data=df[df['Year']>1980]Y=Career_data['perf_cat']#get predictor dataprint(np.shape(X))print(np.shape(Y))from sklearn import preprocessing
df_drop = df [ df [ 'Year' ] > 1980 ]
for x in np . unique ( new_labels ):
Label = 'Category %d ' % x
rookie_df_drop [ Label ] = df_drop [ Label ] #give rookies the groupings produced by the GMM model
X = rookie_df_drop . as_matrix () #take data out of dataframe
poly = preprocessing . PolynomialFeatures ( 2 , interaction_only = True ) #create interaction terms.
X = poly . fit_transform ( X )
Career_data = df [ df [ 'Year' ] > 1980 ]
Y = Career_data [ 'perf_cat' ] #get predictor data
print ( np . shape ( X ))
print ( np . shape ( Y ))
Now that I have all the features, it’s time to try and predict which players will be poor, below average, above average, and great. To create these predictions, I will use a logistic regression model.
Because I have so many predictors, correlation between predicting features and over-fitting the data are major concerns. I use regularization and cross-validation to combat these issues.
Specifically, I am using l2 regularization and k-fold 5 cross-validation. Within the cross-validation, I am trying to estimate how much regularization is appropriate.
Some important notes – I am using “balanced” weights which tells the model that worse to incorrectly predict the poor and great players than the below average and above average players. I do this because I don’t want the model to completely ignore the less frequent classifications. Second, I use the multi_class multinomial because it limits the number of models I have to fit.
Okay, the model did pretty well, but lets look at where the errors are coming from. To visualize the models accuracy, I am using a confusion matrix. In a confusion matrix, every item on the diagnonal is a correctly classified item. Every item off the diagonal is incorrectly classified. The color bar’s axis is the percent correct. So the dark blue squares represent cells with more items.
Lets look at what the model predicts for this year’s rookies. Below I modified two functions that I wrote for a previous post. The first function finds a particular year’s draft picks. The second function produces predictions for each draft pick.
defgather_draftData(Year):importurllib2frombs4importBeautifulSoupimportpandasaspdimportnumpyasnpdraft_len=30defconvert_float(val):try:returnfloat(val)exceptValueError:returnnp.nanurl='http://www.basketball-reference.com/draft/NBA_'+str(Year)+'.html'html=urllib2.urlopen(url)soup=BeautifulSoup(html,"lxml")draft_num=[soup.findAll('tbody')[0].findAll('tr')[i].findAll('td')[0].textforiinrange(draft_len)]draft_nam=[soup.findAll('tbody')[0].findAll('tr')[i].findAll('td')[3].textforiinrange(draft_len)]draft_df=pd.DataFrame([draft_num,draft_nam]).Tdraft_df.columns=['Number','Name']df.index=range(np.size(df,0))returndraft_dfdefplayer_prediction__regressionModel(PlayerName):clust_df=pd.read_pickle('nba_bballref_career_stats_2016_Apr_15.pkl')clust_df=clust_df[clust_df['Name']==PlayerName]clust_df=clust_df.drop(['Year','Name','G','GS','MP','FG','FGA','FG%','3P','2P','FT','TRB','PTS','ORtg','DRtg','PER','TS%','3PAr','FTr','ORB%','DRB%','TRB%','AST%','STL%','BLK%','TOV%','USG%','OWS','DWS','WS','WS/48','OBPM','DBPM','BPM','VORP'],1)new_vect=ScaleModel.transform(clust_df.as_matrix().reshape(1,-1))reduced_data=reduced_model.transform(new_vect)predictions=g.predict_proba(reduced_data)forxinnp.unique(new_labels):Label='Category%d'%xclust_df[Label]=predictions[:,x]Predrookie_df=pd.read_pickle('nba_bballref_rookie_stats_2016_Apr_16.pkl')Predrookie_df=Predrookie_df[Predrookie_df['Name']==PlayerName]Predrookie_df=Predrookie_df.drop(['Year','Career Games','Name'],1)forxinnp.unique(new_labels):Label='Category%d'%xPredrookie_df[Label]=clust_df[Label]#give rookies the groupings produced by the GMM modelpredX=Predrookie_df.as_matrix()#take data out of dataframepredX=poly.fit_transform(predX)predictions2=est.predict_proba(predX)return{'Name':PlayerName,'Group':predictions,'Prediction':predictions2[0]}def gather_draftData ( Year ):
import urllib2
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
draft_len = 30
def convert_float ( val ):
try :
return float ( val )
except ValueError :
return np . nan
url = 'http://www.basketball-reference.com/draft/NBA_' + str ( Year ) + '.html'
html = urllib2 . urlopen ( url )
soup = BeautifulSoup ( html , "lxml" )
draft_num = [ soup . findAll ( 'tbody' )[ 0 ] . findAll ( 'tr' )[ i ] . findAll ( 'td' )[ 0 ] . text for i in range ( draft_len )]
draft_nam = [ soup . findAll ( 'tbody' )[ 0 ] . findAll ( 'tr' )[ i ] . findAll ( 'td' )[ 3 ] . text for i in range ( draft_len )]
draft_df = pd . DataFrame ([ draft_num , draft_nam ]) . T
draft_df . columns = [ 'Number' , 'Name' ]
df . index = range ( np . size ( df , 0 ))
return draft_df
def player_prediction__regressionModel ( PlayerName ):
clust_df = pd . read_pickle ( 'nba_bballref_career_stats_2016_Apr_15.pkl' )
clust_df = clust_df [ clust_df [ 'Name' ] == PlayerName ]
clust_df = clust_df . drop ([ 'Year' , 'Name' , 'G' , 'GS' , 'MP' , 'FG' , 'FGA' , 'FG%' , '3P' , '2P' , 'FT' , 'TRB' , 'PTS' , 'ORtg' , 'DRtg' , 'PER' , 'TS%' , '3PAr' , 'FTr' , 'ORB%' , 'DRB%' , 'TRB%' , 'AST%' , 'STL%' , 'BLK%' , 'TOV%' , 'USG%' , 'OWS' , 'DWS' , 'WS' , 'WS/48' , 'OBPM' , 'DBPM' , 'BPM' , 'VORP' ], 1 )
new_vect = ScaleModel . transform ( clust_df . as_matrix () . reshape ( 1 , - 1 ))
reduced_data = reduced_model . transform ( new_vect )
predictions = g . predict_proba ( reduced_data )
for x in np . unique ( new_labels ):
Label = 'Category %d ' % x
clust_df [ Label ] = predictions [:, x ]
Predrookie_df = pd . read_pickle ( 'nba_bballref_rookie_stats_2016_Apr_16.pkl' )
Predrookie_df = Predrookie_df [ Predrookie_df [ 'Name' ] == PlayerName ]
Predrookie_df = Predrookie_df . drop ([ 'Year' , 'Career Games' , 'Name' ], 1 )
for x in np . unique ( new_labels ):
Label = 'Category %d ' % x
Predrookie_df [ Label ] = clust_df [ Label ] #give rookies the groupings produced by the GMM model
predX = Predrookie_df . as_matrix () #take data out of dataframe
predX = poly . fit_transform ( predX )
predictions2 = est . predict_proba ( predX )
return { 'Name' : PlayerName , 'Group' : predictions , 'Prediction' : predictions2 [ 0 ]}
Below I create a plot depicting the model’s predictions. On the y-axis are the four classifications. On the x-axis are the players from the 2015 draft. Each cell in the plot is the probability of a player belonging to one of the classifications. Again, dark blue means a cell or more likely. Good news for us T-Wolves fans! The model loves KAT.
深圳新秀租房贵吗Now that the NBA season is done, we have complete data from this year’s NBA rookies. In the past I have tried to predict NBA rookies’ future performance using regression models. In this post I...