利用Python進行NBA比賽資料分析
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
利用Python進行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中的統計資料。在這個網站中,你可以看到不同球員、隊伍、賽季和聯盟比賽的基本統計資料,如得分,犯規次數等情況,勝負次數等情況。而我們在這裡將會使用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 程式碼實現
在Code\cs_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
檔案: