背景:
尽管你可能没有关注过篮球赛事,但你一定听过科比·布莱恩特这个名字,这位与乔丹齐名的篮球巨星。科比在1996年的选秀大会上以第十三顺位的身份步入了职业联赛,此后他一直为洛杉矶湖人队效力,表现出了对球队的忠诚与热爱。他在2016年宣布退役,结束了辉煌的职业生涯。
科比五次帮助湖人队夺得NBA总冠军,两次获得FMVP,一次获得MVP,四次荣获AMVP,并十八次入选全明星阵容。他的生涯总得分超过三万三千分,无疑是一位未来将入驻名人堂的伟大球员。
然而,在今年的1月26日,科比所乘坐的私人飞机失事。这场意外带走了科比以及他的二女儿吉安娜的生命,他们将永远留在我们心中。
目标:
该数据集目标分析
1、读取科比职业生涯的数据
2、对该数据进行一个探索性分析
3、建立集成的算法模型
4、模型调参、选择最优的模型参数
5、对测试及进行预测
读取数据
# 导入第三方库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
%matplotlib inline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import KFold
# 忽略警告
import warnings
warnings.filterwarnings('ignore')
raw = pd.read_csv('科比生涯数据集分析/kobe_data.csv')
raw.head()
raw.shape
raw.info()
由上述结果可以看出,该数据集共有25个特征,其中24个特征都是完整的,并且只有shot_made_flag这个特征是存在缺失值的。那么接下来我们会将数据集切分为两部分,将含有shot_made_flag的数据集作为训练集,缺失shot_made_flag的数据集作为测试集。通过对训练集进行建模,来预测测试集上的shot_made_flag值。
raw.describe(include = 'all').T
探索性分析
单变量分析
#正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']
#用来正常显示负号
plt.rcParams['axes.unicode_minus']=False
#查看科比出手类型的分布
plt.figure(figsize = (10,6))
raw['combined_shot_type'].value_counts().plot(kind = 'bar')
plt.xlabel("出手类型");plt.ylabel("出手次数");plt.title("科比职业生涯不同出手类型的次数统计")
由上图可知,科比最喜欢的进攻方式就是跳投,紧接着是上篮。(没有安装“SimHei”字体,所以导致图表标题等出现方框,有需求的可以下载并安装相应的字体)
# 查看科比两分球、三分球的分数
plt.figure(figsize = (10,8))
raw['shot_type'].value_counts().plot(kind='bar')
plt.xlabel('远投还是中投');
plt.ylabel('出手次数');
plt.title('科比职业生涯远投及中投出手数');
plt.xticks(rotation=0)
由上图可知,科比的进攻手段主要以中投为主,就是我们比较熟悉的急停跳投、翻身跳投、干拔跳投等。
# 查看科比出手时的距离分布
# 查看科比两分球、三分球的分数
plt.figure(figsize = (10,8))
raw['shot_distance'].hist(bins=100)
plt.xlabel('出手距离');
plt.ylabel('出手次数');
plt.title('科比出手距的分布');
#绘制箱型图
plt.figure(figsize = (8,6))
sns.boxplot(data = raw,y = 'shot_distance')
plt.xlabel('出手距离');
plt.ylabel('出手次数');
plt.title('科比出手距离的分布')
由上图可以看出科比在篮下的出手最多,大约有6000次,大约75%的出手为2分球,25%的三分球。
# 可视化科比的出手区域,按照不同的标准划分出手区域
import matplotlib.cm as cm
plt.figure(figsize=(20,10))
def scatter_plot_by_category(feat):
alpha = 0.1
gs = raw.groupby(feat)
cs = cm.rainbow(np.linspace(0,1,len(gs)))
for g,c in zip(gs,cs):
plt.scatter(g[1].loc_x,g[1].loc_y,color=c,alpha=alpha)
plt.subplot(1,3,1)
scatter_plot_by_category(raw['shot_zone_area'])
plt.title('shot_zone_area')
plt.subplot(1,3,2)
scatter_plot_by_category(raw['shot_zone_basic'])
plt.title('shot_zone_basic')
plt.subplot(1,3,3)
scatter_plot_by_category(raw['shot_zone_range'])
plt.title('shot_zone_range')
raw['shot_distance'].describe()
双变量分析
#将shot_made_falg非空的数据集提出来,也就是我们的训练集
kobe = raw[pd.notnull(raw['shot_made_flag'])]
print(kobe.shape)
#查看科比的出手命中率
plt.figure(figsize = (6,4))
kobe['shot_made_flag'].value_counts(normalize = True).plot(kind = 'bar')
plt.xlabel('命中情况');plt.ylabel('命中个数');plt.title('科比的出手命中率')
该样本只是整个数据集中的部分样本,不能反映出其职业生涯的真实命中率。可以看出科比的出手命中率大约为44%,还是挺高的命中率。
#观察不同出手类型与命中率之间的关系
sns.barplot(data = kobe,x = 'combined_shot_type',y = 'shot_made_flag')
由上图可知,命中率从高往低依次为:扣篮-擦板-上篮-勾手-跳投-补篮。
kobe.groupby('shot_type')['shot_made_flag'].value_counts(normalize = True)
由上述结果可以看出科比的两分球命中率为47.7%,三分球的命中率为32.9%。
#观察出手距离与命中率之间的关系
sns.scatterplot(data = kobe, x = 'shot_distance',y = 'shot_made_flag' )
sns.violinplot(data = kobe, y = 'shot_distance',x = 'shot_made_flag' )
由上图可以看出,出手距离越远,命中率越低,出手超过43英尺的都没有命中。
数据预处理
raw.head()
raw.info()
#删除对最终预测结果无影响的id特征
drop_ids = ['game_event_id','game_id','team_id','shot_id']
for feature in drop_ids:
raw = raw.drop(feature,axis = 1)
#创建一个新的特征time_remaining,用于替代minutes_remaining和seconds_remaining
raw['time_remaining'] = raw['minutes_remaining']*60 + raw['seconds_remaining']
#删除minutes_remaining和seconds_remaining特征
raw = raw.drop(['minutes_remaining','seconds_remaining'],axis = 1)
raw['season'].unique()
#将season数据处理为更简单易懂的格式
raw['season'] = raw['season'].apply(lambda x:int(x.split('-')[1]))
raw['season'].unique()
#lat,lon,loc_x,loc_y表达的是相同的含义,删除lat,lon特征
raw = raw.drop(['lat','lon'],axis = 1)
#action_type和combined_shot_type表达的含义相近,删除action_type
raw = raw.drop(['action_type'],axis = 1)
#shot_zone_area,shot_zone_basic,shot_zone_range表达的也是相同的含义,保留一个就行
raw = raw.drop(['shot_zone_basic','shot_zone_range'],axis = 1)
#team_name和game_date对最终的预测结果也没什么影响,删除这两个特征
raw = raw.drop(['team_name','game_date'],axis = 1)
#matchup和opponent表达的是相同的意思,保留opponent就行
raw = raw.drop('matchup',axis = 1)
#查看当前数据集的信息
raw.info()
其中combined_shot_type,shot_type,shot_zone_area,opponent为object类型,对这几个特征做one_hot_enncoding编码。
raw = pd.get_dummies(raw)
raw.head()
#将数据集分为训练集和测试集
train_data = raw[pd.notnull(raw['shot_made_flag'])]
test_data = raw[pd.isnull(raw['shot_made_flag'])]
print('训练集的大小:',train_data.shape)
print('测试集的大小:',test_data.shape)
由于该测试集没有标签,所以我们需要通过交叉验证的方式将训练集分为训练样本和验证样本,用验证集来评估模型的好坏。最终选取最好的模型,对测试样本进行预测。
#准备数据,将训练数据分为特征个标签,删掉测试数据中的shot_made_flag列
train_labels = train_data['shot_made_flag']
train_features = train_data.drop('shot_made_flag',axis = 1)
test_features = test_data.drop('shot_made_flag',axis = 1)
建立随机森林模型
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
import time
#初始化一个随机森林模型
RFC = RandomForestClassifier()
#使用网格搜索gridsearchcv寻找最佳的模型参数
param_grid = {'n_estimators':[40,80,120,160,200],
'max_depth':[3,4,5,6,7,8,9,10],
'min_samples_split':[3,4,5,6,7]}
grid = GridSearchCV(RFC,param_grid = param_grid,cv = 5,verbose = 2,n_jobs = -1)
t1 = time.time()
grid.fit(train_features,train_labels)
t2 = time.time()
print('模型的训练时间{}'.format(t2 - t1))
#打印最好的模型参数
grid.best_params_
best_model = grid.best_estimator_
test_flag = best_model.predict(test_features)
print(test_flag[:10])