1. 程式人生 > 其它 >用python基於2015-2016年的NBA常規賽及季後賽的統計資料分析

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

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

  1. 進入到basketball-refernce.com中,在導航欄中選擇Season並選擇2015~2016賽季中的Summary
  1. 進入到2015~2016年的Summary介面後,滑動視窗找到Team Per Game Stats表格,並選擇左上方的Share & more,在其下拉選單中選擇Get table as CSV (for Excel):
  1. 複製在介面中生的的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(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值也會做相應的調整:

  • 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的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 程式碼實現

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中有相關的籃球預測比賽專案,有興趣的同學可嘗試一下。