因为疫情原因导致NBA2019-2020赛季没有进行完,所以我们使用NBA2018-2019赛季的数据进行预测,数据获取方式有下面两种:
通过网站获取
我们可以通过网站去获取任意一年的所有场次比赛数据,获取方式如下:
同时我们还需要获取上赛季积分榜的数据,获取方式如下:
通过GitHub获取
本文中的数据和代码已经上传到GitHub上,链接如下:https://github.com/bigdatavalley/NBA_predict
在进行预测的算法设计之前,我们需要先进行数据的查看和整合,由于原有数据中很多的特征名称设定的不是很好,所以我们在加载数据的时候重新设定一下列名称,代码如下:
import pandas as pd
path = 'basketball.csv'
dataset = pd.read_csv(path, parse_dates=['Date'])
dataset.columns = [
'Date', 'Start(ET)', 'Visitor Team', 'VisitorPts', 'Home Team', 'HomePts',
'OT?', 'Score Type', 'Notes'
]
dataset.head()
运行结果如下:
我们在建立预测模型的时候通常会有一个标准,当我们的预测效果超过这个标准的时候我们就可以认为预测模型起到了作用,接下来我们来建立一个关于预测球队获胜的标准。
在NBA比赛中,通常分为主客场,当球队在主场比赛的时候往往会占据一定的优势,那么如果我们每次都预测主场球队获胜,应该也能得到不错的准确率,那么我们就可以用这个数值来当作我们的标准,代码如下:
# 提取新特征
# 统计客队得分小于主队的场次
dataset['HomeWin'] = dataset['VisitorPts'] < dataset['HomePts']
# 保留成label
y_true = dataset['HomeWin'].values
# y_true
dataset['HomeWin'].mean()
运行结果如下:
0.5907012195121951
现在我们就有了一个0.59作为标准,后面我们要保证建立的模型不低于这个准确率就可以了。
构造特征一点一点进行,后面我们再陆续添加新的特征。
进行模型建立之前,还有一个重要的过程就是构建我们要使用的特征,第一个我们要思考的就是两只球队是否赢得了上一场的比赛,通常我们会认为赢得了上一场比赛的球队就是更厉害的。我们建立两个新的特征分别是主队/客队是否赢了上一场比赛。代码如下:
from collections import defaultdict
won_last = defaultdict(int)
dataset['HomeLastWin'] = 0
dataset['VisitorLastWin'] = 0
# 时间是无序的时候dataset.sort('Date').iterrows()
for index, row in dataset.iterrows():
home_team = row['Home Team']
visitor_team = row['Visitor Team']
dataset.at[index, 'HomeLastWin'] = won_last[home_team]
dataset.at[index, 'VisitorLastWin'] = won_last[visitor_team]
won_last[home_team] = int(row['HomeWin'])
won_last[visitor_team] = 1 - int(row['HomeWin'])
X_previouswins = dataset[['HomeLastWin', 'VisitorLastWin']].values
X_previouswins
运行结果如下:
array([[0, 0],
[0, 0],
[0, 0],
...,
[0, 1],
[1, 0],
[1, 0]])
我们尝试使用简单的决策树算法进行简单的训练,并且通过交叉验证的方式获取评分。
决策树的原理:
决策树(上)
决策树(下)
代码如下:
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
clf = DecisionTreeClassifier(random_state=42)
score = cross_val_score(clf, X_previouswins, y_true, scoring = 'accuracy')
print('score is:{0:.1f}%'.format(np.mean(score)*100))
运行结果如下:
score is:59.1%
根据结果可以发现,当前的特征下,并没有使得我们的预测效果提高,接下来我们继续对数据和模型进行改进。
考虑到球队获胜可能和上个赛季的战绩也有一定的关系,我们尝试加入上个赛季的数据来进行预测。
# 加入上赛季数据
standings = pd.read_excel('standings.xls')
# 建立新的特征(排名情况)
dataset['HomeTeamRanksHigher'] = 0
for index, row in dataset.iterrows():
home_team = row['Home Team']
visitor_team = row['Visitor Team']
home_rank = standings[standings['Team'] == home_team]['Rk'].values[0]
visitor_rank = standings[standings['Team'] == visitor_team]['Rk'].values[0]
row['HomeTeamRanksHigher'] = int(home_rank > visitor_rank)
dataset.at[index, 'HomeTeamRanksHigher'] = int(home_rank < visitor_rank)
clf = DecisionTreeClassifier(random_state=42)
X_homehigher = dataset[[
'HomeLastWin', 'VisitorLastWin', 'HomeTeamRanksHigher'
]].values
score = cross_val_score(clf, X_homehigher, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))
运行结果如下:
score is:62.2%
很明显,我们加入上个赛季的战绩的时候,预测的准确率提升了3个百分点。
接下来我们继续加入交手的两只球队中哪一个在上一次交手中胜出的数据,代码如下:
last_match_winner = defaultdict(int)
dataset['HomeTeamWonLast'] = 0
for index, row in dataset.iterrows():
home_team = row['Home Team']
visitor_team = row['Visitor Team']
teams = tuple(sorted([home_team, visitor_team]))
home_team_won_last = 1 if last_match_winner[teams] == row[
'Home Team'] else 0
dataset.at[index, 'HomeTeamWonLast'] = home_team_won_last
winner = row['Home Team'] if row['HomeWin'] else row['Visitor Team']
last_match_winner[teams] = winner
clf = DecisionTreeClassifier(random_state=42)
X_lastwinner = dataset[[
'HomeLastWin', 'VisitorLastWin', 'HomeTeamRanksHigher', 'HomeTeamWonLast'
]].values
score = cross_val_score(clf, X_lastwinner, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))
运行结果如下:
score is:61.7%
结果和上面差异不大,没有关系,我们继续尝试使用其他的策略来进行改进。
我们之前做的工作都是根据一些比赛数据来进行的,下面我们尝试加入队伍数据来检验一下我们的运行成果,由于我们不能直接对字符数据进行计算,所以先把数据通过LabelEncoder转化为数值类型,但是一般数值类型又会有大小造成的影响,所以我们再进行一次OneHotEncoder的操作即可。代码如下:
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
encoding = LabelEncoder()
onehot = OneHotEncoder()
encoding.fit(dataset['Home Team'].values)
home_teams = encoding.transform(dataset['Home Team'].values)
visitor_teams = encoding.transform(dataset['Visitor Team'].values)
X_teams = np.vstack([home_teams, visitor_teams]).T
X_teams = onehot.fit_transform(X_teams).todense()
X_all = np.hstack([X_lastwinner, X_teams])
clf = DecisionTreeClassifier(random_state=42)
score = cross_val_score(clf, X_all, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))
运行结果如下:
score is:60.7%
很显然虽然比我们直接预测主队获胜的效果要好,但是也没有高出很多。不要慌,我们继续使用更好的算法来尝试一下。
随机森林是一种以决策树为基分类器的集成学习算法,理论上讲,我们所得到的效果要好于决策树。
代码如下:
clf = RandomForestClassifier(random_state=42)
score = cross_val_score(clf, X_all, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))
运行结果如下:
score is:63.6%
尝试使用网格搜索,代码如下:
from sklearn.model_selection import GridSearchCV
parameter_space = {
'max_features': [2, 10, 'auto'],
'n_estimators': [100, 200],
'criterion': ['gini', 'entropy'],
'min_samples_leaf': [2, 4, 6]
}
clf = RandomForestClassifier(random_state=42)
grid = GridSearchCV(clf, parameter_space)
grid.fit(X_all, y_true)
print('score is:{0:.1f}%'.format(np.mean(score) * 100))
运行结果如下:
score is:63.6%
可以看到根据目前我们所构造的几个简单的特征就已经可以得到显著的准确率提升了。同时收到球员转会、受伤等情况的影响,对于不同赛季的数据使用这些方法会得到不同的结果,有兴趣的读者可以自己获取其他赛季的数据来尝试一下。
你可能对当前的结果还不是很满意,可以尝试进行下面几个方向的参考: