用python基於2015-2016年的NBA常規賽及季後賽的統計資料分析
一、實驗介紹
1.1 內容簡介
不知道你是否朋友圈被刷屏過nba的某場比賽進度或者結果?或者你就是一個nba狂熱粉,比賽中的每個進球,搶斷或是逆轉壓哨球都能讓你熱血沸騰。除去觀賞精彩的比賽過程,我們也同樣好奇比賽的結果會是如何。因此本節課程,將給同學們展示如何使用nba比賽的以往統計資料,判斷每個球隊的戰鬥力,及預測某場比賽中的結果。
我們將基於2015-2016年的NBA常規賽及季後賽的比賽統計資料,預測在當下正在進行的2016-2017常規賽每場賽事的結果。
1.2 實驗知識點
- nba球隊的
Elo score
計算 - 特徵向量
- 邏輯迴歸
1.3 實驗環境
- python2.7
- Xfce終端
1.4 實驗流程
本次課程我們將按照下面的流程實現NBA比賽資料分析的任務:
- 獲取比賽統計資料
- 比賽資料分析,得到代表每場比賽每支隊伍狀態的特徵表達
- 利用機器學習方法學習每場比賽與勝利隊伍的關係,並對2016-2017的比賽進行預測
1.5 程式碼獲取
本次實驗的原始碼可通過以下命令獲得:
$ wget http://labfile.oss.aliyuncs.com/courses/782/prediction.py
二、獲取 NBA比賽統計資料
2.1 比賽資料介紹
在本次實驗中,我們將採用Basketball Reference.com
http://www.basketball-reference.com/
中的統計資料。在這個網站中,你可以看到不同球員、隊伍、賽季和聯盟比賽的基本統計資料,如得分,犯規次數等情況,勝負次數等情況。而我們在這裡將會使用2015-16 NBA Season Summary中資料。
在這個2015-16總結的所有表格中,我們將使用的是以下三個資料表格:
- Team Per Game Stats:每支隊伍平均每場比賽的表現統計
資料名 |
含義 |
---|---|
Rk -- Rank |
排名 |
G -- Games |
參與的比賽場數(都為82場) |
MP -- Minutes Played |
平均每場比賽進行的時間 |
FG--Field Goals |
投球命中次數 |
FGA--Field Goal Attempts |
投射次數 |
FG%--Field Goal Percentage |
投球命中次數 |
3P--3-Point Field Goals |
三分球命中次數 |
3PA--3-Point Field Goal Attempts |
三分球投射次數 |
3P%--3-Point Field Goal Percentage |
三分球命中率 |
2P--2-Point Field Goals |
二分球命中次數 |
2PA--2-point Field Goal Attempts |
二分球投射次數 |
2P%--2-Point Field Goal Percentage |
二分球命中率 |
FT--Free Throws |
罰球命中次數 |
FTA--Free Throw Attempts |
罰球投射次數 |
FT%--Free Throw Percentage |
罰球命中率 |
ORB--Offensive Rebounds |
進攻籃板球 |
DRB--Defensive Rebounds |
防守籃板球 |
TRB--Total Rebounds |
籃板球總數 |
AST--Assists |
輔助 |
STL--Steals |
偷球 |
BLK -- Blocks |
封阻 |
TOV -- Turnovers |
失誤 |
PF -- Personal Fouls |
個犯 |
PTS -- Points |
得分 |
- Opponent Per Game Stats:所遇到的對手平均每場比賽的統計資訊,所包含的統計資料與Team Per Game Stats中的一致,只是代表的該球隊對應的對手的
- Miscellaneous Stats:綜合統計資料
資料項 |
資料含義 |
---|---|
Rk (Rank) |
排名 |
Age |
隊員的平均年齡 |
W (Wins) |
勝利次數 |
L (Losses) |
失敗次數 |
PW (Pythagorean wins) |
基於畢達哥拉斯理論計算的贏的概率 |
PL (Pythagorean losses) |
基於畢達哥拉斯理論計算的輸的概率 |
MOV (Margin of Victory) |
贏球次數的平均間隔 |
SOS (Strength of Schedule) |
用以評判對手選擇與其球隊或是其他球隊的難易程度對比,0為平均線,可以為正負數 |
SRS (Simple Rating System) |
3 |
ORtg (Offensive Rating) |
每100個比賽回合中的進攻比例 |
DRtg (Defensive Rating) |
每100個比賽回合中的防守比例 |
Pace (Pace Factor) |
每48分鐘內大概會進行多少個回合 |
FTr (Free Throw Attempt Rate) |
罰球次數所佔投射次數的比例 |
3PAr (3-Point Attempt Rate) |
三分球投射佔投射次數的比例 |
TS% (True Shooting Percentage) |
二分球、三分球和罰球的總共命中率 |
eFG% (Effective Field Goal Percentage) |
有效的投射百分比(含二分球、三分球) |
TOV% (Turnover Percentage) |
每100場比賽中失誤的比例 |
ORB% (Offensive Rebound Percentage) |
球隊中平均每個人的進攻籃板的比例 |
FT/FGA |
罰球所佔投射的比例 |
eFG% (Opponent Effective Field Goal Percentage) |
對手投射命中比例 |
TOV% (Opponent Turnover Percentage) |
對手的失誤比例 |
DRB% (Defensive Rebound Percentage) |
球隊平均每個球員的防守籃板比例 |
FT/FGA (Opponent Free Throws Per Field Goal Attempt) |
對手的罰球次數佔投射次數的比例 |
畢達哥拉斯定律:win% = frac{{runs scored}^2}{{runs scored}^2 + {runs allowed}^2}win%=runs scored2+runs allowed2runs scored2
我們將用這三個表格來評估球隊過去的戰鬥力,另外還需2015-16 NBA Schedule and Results中的2015~2016年的nba常規賽及季後賽的每場比賽的比賽資料,用以評估Elo score
(在之後的實驗小節中解釋)。在Basketball Reference.com
中按照從常規賽至季後賽的時間。列出了2015年10月份至2016年6月份的每場比賽的比賽情況。
可在上圖中,看到2015年10月份的部分比賽資料。在每個Schedule表格中所包含的資料為:
資料項 |
資料含義 |
---|---|
Date |
比賽日期 |
Start (ET) |
比賽開始時間 |
Visitor/Neutral |
客場作戰隊伍 |
PTS |
客場隊伍最後得分 |
Home/Neutral |
主場隊伍 |
PTS |
主場隊伍最後得分 |
Notes |
備註,表明是否為加時賽等 |
在預測時,我們同樣也需要在2016-17 NBA Schedule and Results中2016~2017年的NBA的常規賽比賽安排資料。
2.2 獲取比賽資料
我們將以獲取Team Per Game Stats表格資料為例,展示如何獲取這三項統計資料。
- 進入到basketball-refernce.com中,在導航欄中選擇
Season
並選擇2015~2016
賽季中的Summary
:
- 進入到2015~2016年的
Summary
介面後,滑動視窗找到Team Per Game Stats
表格,並選擇左上方的Share & more,在其下拉選單中選擇Get table as CSV (for Excel):
- 複製在介面中生的的csv格式資料,並複製貼上至一個文字編輯器儲存為csv檔案即可:
為了方便同學們進行實驗,我們已經將資料全部都儲存成csv檔案上傳至實驗樓的雲環境中。在後續的程式碼實現小節裡,我們將給出獲取這些檔案的地址。
三、資料分析
在獲取到資料之後,我們將利用每支隊伍過去的比賽情況和Elo 等級分來判斷每支比賽隊伍的可勝概率。在評價到每支隊伍過去的比賽情況時,我們將使用到Team Per Game Stats,Opponent Per Game Stats和Miscellaneous Stats(之後簡稱為T、O和M表)這三個表格的資料,作為代表比賽中某支隊伍的比賽特徵。我們最終將實現針對每場比賽,預測比賽中哪支隊伍最終將會獲勝,但並不是給出絕對的勝敗情況,而是預判勝利的隊伍有多大的獲勝概率。因此我們將建立一個代表比賽的特徵向量。由兩支隊伍的以往比賽情況統計情況(T、O和M表),和兩個隊伍各自的Elo等級分構成。
關於Elo score等級分,不知道同學們是否看過《社交網路》
這部電影,在這部電影中,Mark(主人公原型就是扎克伯格,FaceBook創始人)在電影起初開發的一個美女排名系統就是利用其好友Eduardo在窗戶上寫下的排名公式,對不同的女生進行等級制度對比,最後PK出勝利的一方。
這條對比公式就是Elo Score等級分制度。Elo的最初為了提供國際象棋中,更好地對不同的選手進行等級劃分。在現在很多的競技運動或者遊戲中都會採取Elo等級分制度對選手或玩家進行等級劃分,如足球、籃球、棒球比賽或LOL,DOTA等遊戲。
在這裡我們將基於國際象棋比賽,大致地介紹下Elo等級劃分制度。在上圖中Eduardo在窗戶上寫下的公式就是根據Logistic Distribution
計算PK雙方(A和B)對各自的勝率期望值計算公式。假設A和B的當前等級分為R_ARA何R_BRB,則A對B的勝率期望值為:
E_A=frac{1}{1+10^{(R_B-R_A)/400}}EA=1+10(RB−RA)/4001
B對A的勝率期望值為
E_B=frac{1}{1+10^{(R_A-R_B)/400}}EB=1+10(RA−RB)/4001
如果棋手A在比賽中的真實得分S_ASA(勝1分,和0.5分,負0分)和他的勝率期望值E_AEA不同,則他的等級分要根據以下公式進行調整:
R_A^{new} = R_A^{old} + K(S_A - R_A^{old})RAnew=RAold+K(SA−RAold)
在國際象棋中,根據等級分的不同K值也會做相應的調整:
- ge2400≥2400,K=16
- 2100~2400分,K=24
- le2100≤2100,K=32
因此我們將會用以表示某場比賽資料的特徵向量為(加入A與B隊比賽):[A隊Elo score, A隊的T,O和M表統計資料,B隊Elo score, B隊的T,O和M表統計資料]
四、基於資料進行模型訓練和預測
4.1 實驗前期準備
在本次實驗環境中,我們將會使用到Python的pandas
,numpy
,scipy
和sklearn
庫,不過實驗樓中已經安裝了numpy
,所以在實驗前,我們需要先利用pip
命令安裝另外兩個python庫。
$ sudo pip install pandas
$ sudo pip install scipy
$ sudo pip install sklearn
在安裝完所需的實驗庫之後,進入到實驗環境的Code
目錄下,建立cs_782
資料夾,並且通過以下地址獲取我們為大家處理好的csv檔案壓縮包data.zip
:
$ cd Code
$ mkdir cs_782 && cd cs_782# 獲取資料檔案$ wget http://labfile.oss.aliyuncs.com/courses/782/data.zip# 解壓data壓縮包並且刪除該壓縮包$ unzip data.zip
$ rm -r data.zip
在data
資料夾中,包含了2015~2016年的NBA資料T,O和M表,及經處理後的常規賽和挑戰賽的比賽資料2015~16result.csv
,這個資料檔案是我們通過在basketball-reference.com
的2015-16 Schedule and result的幾個月份比賽資料中提取得到的,其中包括三個欄位:
- WTeam: 比賽勝利隊伍
- LTeam: 失敗隊伍
- WLoc: 勝利隊伍一方所在的為主場或是客場 另外一個檔案就是
16-17Schedule.csv
,也是經過我們加工處理得到的NBA在2016~2017年的常規賽的比賽安排,其中包括兩個欄位: - Vteam: 訪問/客場作戰隊伍
- Hteam: 主場作戰隊伍
4.2 程式碼實現
在Codecs_782
目錄下,建立prediciton.py
開始實驗。 首先插入實驗相關模組:
# -*- coding:utf-8 -*-import pandas as pdimport mathimport csvimport randomimport numpy as npfrom sklearn import cross_validation, linear_model
設定迴歸訓練時所需用到的引數變數:
# 當每支隊伍沒有elo等級分時,賦予其基礎elo等級分base_elo = 1600team_elos = {}
team_stats = {}
X = []
y = []
folder = 'data' #存放資料的目錄
在最開始需要初始化資料,從T、O和M表格中讀入資料,去除一些無關資料並將這三個表格通過Team
屬性列進行連線:
# 根據每支隊伍的Miscellaneous Opponent,Team統計資料csv檔案進行初始化def initialize_data(Mstat, Ostat, Tstat):
new_Mstat = Mstat.drop(['Rk', 'Arena'], axis=1)
new_Ostat = Ostat.drop(['Rk', 'G', 'MP'], axis=1)
new_Tstat = Tstat.drop(['Rk', 'G', 'MP'], axis=1)
team_stats1 = pd.merge(new_Mstat, new_Ostat, how='left', on='Team')
team_stats1 = pd.merge(team_stats1, new_Tstat, how='left', on='Team') return team_stats1.set_index('Team', inplace=False, drop=True)
獲取每支隊伍的Elo Score
等級分函式,當在開始沒有等級分時,將其賦予初始base_elo
值:
def get_elo(team):
try: return team_elos[team] except: # 當最初沒有elo時,給每個隊伍最初賦base_elo
team_elos[team] = base_elo return team_elos[team]
定義計算每支球隊的Elo等級分
函式:
# 計算每個球隊的elo值def calc_elo(win_team, lose_team):
winner_rank = get_elo(win_team)
loser_rank = get_elo(lose_team)
rank_diff = winner_rank - loser_rank
exp = (rank_diff * -1) / 400
odds = 1 / (1 + math.pow(10, exp)) # 根據rank級別修改K值
if winner_rank < 2100:
k = 32
elif winner_rank >= 2100 and winner_rank < 2400:
k = 24
else:
k = 16
new_winner_rank = round(winner_rank + (k * (1 - odds)))
new_rank_diff = new_winner_rank - winner_rank
new_loser_rank = loser_rank - new_rank_diff return new_winner_rank, new_loser_rank
基於我們初始好的統計資料,及每支隊伍的Elo score計算結果,建立對應2015~2016年常規賽和季後賽中每場比賽的資料集(在主客場比賽時,我們認為主場作戰的隊伍更加有優勢一點,因此會給主場作戰隊伍相應加上100等級分):
def build_dataSet(all_data):
print("Building data set..")
X = []
skip = 0
for index, row in all_data.iterrows():
Wteam = row['WTeam']
Lteam = row['LTeam'] #獲取最初的elo或是每個隊伍最初的elo值
team1_elo = get_elo(Wteam)
team2_elo = get_elo(Lteam) # 給主場比賽的隊伍加上100的elo值
if row['WLoc'] == 'H':
team1_elo += 100
else:
team2_elo += 100
# 把elo當為評價每個隊伍的第一個特徵值
team1_features = [team1_elo]
team2_features = [team2_elo] # 新增我們從basketball reference.com獲得的每個隊伍的統計資訊
for key, value in team_stats.loc[Wteam].iteritems():
team1_features.append(value) for key, value in team_stats.loc[Lteam].iteritems():
team2_features.append(value) # 將兩支隊伍的特徵值隨機的分配在每場比賽資料的左右兩側
# 並將對應的0/1賦給y值
if random.random() > 0.5:
X.append(team1_features + team2_features)
y.append(0) else:
X.append(team2_features + team1_features)
y.append(1) if skip == 0: print X
skip = 1
# 根據這場比賽的資料更新隊伍的elo值
new_winner_rank, new_loser_rank = calc_elo(Wteam, Lteam)
team_elos[Wteam] = new_winner_rank
team_elos[Lteam] = new_loser_rank return np.nan_to_num(X), y
最終在main函式中呼叫這些資料處理函式,使用sklearn的Logistic Regression
方法建立迴歸模型:
if __name__ == '__main__':
Mstat = pd.read_csv(folder + '/15-16Miscellaneous_Stat.csv')
Ostat = pd.read_csv(folder + '/15-16Opponent_Per_Game_Stat.csv')
Tstat = pd.read_csv(folder + '/15-16Team_Per_Game_Stat.csv')
team_stats = initialize_data(Mstat, Ostat, Tstat)
result_data = pd.read_csv(folder + '/2015-2016_result.csv')
X, y = build_dataSet(result_data) # 訓練網路模型
print("Fitting on %d game samples.." % len(X))
model = linear_model.LogisticRegression()
model.fit(X, y) #利用10折交叉驗證計算訓練正確率
print("Doing cross-validation..")
print(cross_validation.cross_val_score(model, X, y, cv = 10, scoring='accuracy', n_jobs=-1).mean())
最終利用訓練好的模型在16~17年的常規賽資料中進行預測。 利用模型對一場新的比賽進行勝負判斷,並返回其勝利的概率:
def predict_winner(team_1, team_2, model):
features = [] # team 1,客場隊伍
features.append(get_elo(team_1)) for key, value in team_stats.loc[team_1].iteritems():
features.append(value) # team 2,主場隊伍
features.append(get_elo(team_2) + 100) for key, value in team_stats.loc[team_2].iteritems():
features.append(value)
features = np.nan_to_num(features) return model.predict_proba([features])
在main函式中呼叫該函式,並將預測結果輸出到16-17Result.csv
檔案中:
#利用訓練好的model在16-17年的比賽中進行預測
print('Predicting on new schedule..')
schedule1617 = pd.read_csv(folder + '/16-17Schedule.csv')
result = [] for index, row in schedule1617.iterrows():
team1 = row['Vteam']
team2 = row['Hteam']
pred = predict_winner(team1, team2, model)
prob = pred[0][0] if prob > 0.5:
winner = team1
loser = team2
result.append([winner, loser, prob]) else:
winner = team2
loser = team1
result.append([winner, loser, 1 - prob]) with open('16-17Result.csv', 'wb') as f:
writer = csv.writer(f)
writer.writerow(['win', 'lose', 'probability'])
writer.writerows(result)
執行prediction.py
:
生成預測結果檔案16-17Result.csv
檔案:
五、總結
在本節課程中,我們利用Basketball-reference.com
的部分統計資料,計算每支nba比賽隊伍的Elo socre
,和利用這些基本統計資料評價每支隊伍過去的比賽情況,並且根據國際等級劃分方法Elo Score
對隊伍現在的戰鬥等級進行評分,最終結合這些不同隊伍的特徵判斷在一場比賽中,哪支隊伍能夠佔到優勢。但在我們的預測結果中,與以往不同,我們沒有給出絕對的正負之分,而是給出勝算較大一方的隊伍能夠贏另外一方的概率。當然在這裡,我們所採用評價一支隊伍效能的資料量還太少(只採用了15~16年一年的資料),如果想要更加準確、系統的判斷,有興趣的你當然可以從各種統計資料網站中獲取到更多年份,更加全面的資料。結合不同的迴歸、決策機器學習模型,搭建一個更加全面,預測準確率更高的模型。在kaggle中有相關的籃球預測比賽專案,有興趣的同學可嘗試一下。