1. 程式人生 > >利用Python進行NBA比賽資料分析

利用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比賽資料分析的任務:

  1. 獲取比賽統計資料
  2. 比賽資料分析,得到代表每場比賽每支隊伍狀態的特徵表達
  3. 利用機器學習方法學習每場比賽與勝利隊伍的關係,並對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表格資料為例,展示如何獲取這三項統計資料。

  1. 進入到basketball-refernce.com中,在導航欄中選擇Season並選擇2015~2016賽季中的Summary

    此處輸入圖片的描述

  2. 進入到2015~2016年的Summary介面後,滑動視窗找到Team Per Game Stats表格,並選擇左上方的Share & more,在其下拉選單中選擇Get table as CSV (for Excel)

    此處輸入圖片的描述

  3. 複製在介面中生的的csv格式資料,並複製貼上至一個文字編輯器儲存為csv檔案即可:

    此處輸入圖片的描述

為了方便同學們進行實驗,我們已經將資料全部都儲存成csv檔案上傳至實驗樓的雲環境中。在後續的程式碼實現小節裡,我們將給出獲取這些檔案的地址。

三、資料分析

在獲取到資料之後,我們將利用每支隊伍過去的比賽情況Elo 等級分來判斷每支比賽隊伍的可勝概率。在評價到每支隊伍過去的比賽情況時,我們將使用到Team Per Game StatsOpponent Per Game StatsMiscellaneous 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_ARAR_BRB,則A對B的勝率期望值為:

E_A=\frac{1}{1+10^{(R_B-R_A)/400}}EA=1+10(RBRA)/4001

B對A的勝率期望值為

E_B=\frac{1}{1+10^{(R_A-R_B)/400}}EB=1+10(RARB)/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(SARAold)

在國際象棋中,根據等級分的不同K值也會做相應的調整:

  • \ge24002400,K=16
  • 2100~2400分,K=24
  • \le21002100,K=32

因此我們將會用以表示某場比賽資料的特徵向量為(加入A與B隊比賽):[A隊Elo score, A隊的T,O和M表統計資料,B隊Elo score, B隊的T,O和M表統計資料]

四、基於資料進行模型訓練和預測

4.1 實驗前期準備

在本次實驗環境中,我們將會使用到python的pandasnumpyscipysklearn庫,不過實驗樓中已經安裝了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檔案: