利用Python进行NBA比赛数据分析

实验楼课程 专栏收录该内容
171 篇文章 19 订阅

利用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 pd
import math
import csv
import random
import numpy as np
from sklearn import cross_validation, linear_model

设置回归训练时所需用到的参数变量:

# 当每支队伍没有elo等级分时,赋予其基础elo等级分
base_elo = 1600
team_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中有相关的篮球预测比赛项目,有兴趣的同学可尝试一下。

六、参考阅读

七、课后习题

本次课程中,我们只是利用了scikit-learn提供的Logisitc Regression方法进行回归模型的训练,你可否尝试scikit-learn中的其他机器学习方法,或者其他类似于TensorFlow的开源框架,结合我们所提供的数据集进行训练。若采用Scikit-learn中的方法,可参看实验楼的课程:ebay在线拍卖数据分析。或是结合下图进行模型的尝试:

此处输入图片的描述

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值