1. 程式人生 > >科比資料集分析與預測

科比資料集分析與預測

本文收集了一系列科比的資料,有投籃位置,投進二分、三分球個數,比賽剩餘時間,對手是誰等等來預測科比是否進球。主要是想通過本例來認識一下pandas在資料處理方面強大的功能 。資料集有需要的可以聯絡我qq:1344184686

一、匯入需要用到的包,讀入資料集

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#讀入資料
data = pd.read_csv("data.csv")

#顯示大小
data.shape

#顯示頭部,預設前5行
data.head()

#顯示尾部
data.tail()

#顯示前k行資料
data.head(k)

#顯示具體位置的資料,如a到b行之間的資料
data.loc[a:b]

二、資料清洗

首先去掉標籤為缺失值的資料

#保留標籤不為缺失值的資料
data = data[pd.notnull(data['shot_made_flag'])]

#檢視一下有多少有標籤的資料,即有用的資料
data.shape

通過對資料的分析,發現特徵既有科比投籃的位置座標loc_x,loc_y又有經度lat,緯度lon,猜測這兩組特徵重複,我們就來對比一下:

#分配畫布大小
plt.figure(figsize = (10,10))

plt.subplot(1,2,1)
#alpha為不透明度,loc_x,loc_y為科比投籃的位置
plt.scatter(data.loc_x,data.loc_y,color ='g',alpha = 0.05)
plt.title('loc_x and loc_y')

plt.subplot(1,2,2)
#lat為緯度,lon為經度
plt.scatter(data.lon,data.lat,color ='b',alpha = 0.05)
plt.title('lat and lon')

通過比較,科比投籃的位置和經緯度這兩組特徵是類似的,保留一組特徵即可。

又發現原始特徵中既有分鐘又有秒,所以可以把這兩組特徵進行合併:

data['remain_time'] = data['minutes_remaining']*60 + data['seconds_remaining']
#顯示某一特徵裡的獨一無二值,這裡顯示科比的得分型別,有二分和三分兩種
print(data['shot_type'].unique())

#顯示某一特徵裡各種值的數量,可見科比二分得分20285次,三分得分5412次
print(data['shot_type'].value_counts())

###執行結果如下:
['2PT Field Goal' '3PT Field Goal']
2PT Field Goal    20285
3PT Field Goal     5412
Name: shot_type, dtype: int64

又發現'season'這個特徵資料中用-相連,是計算機不認識的資料,所以對其進行一個分割:

#列印原始資料型別
print(data['season'].unique())

#apply是呼叫函式,這裡用python的匿名函式lambda,對資料進行分割,以-為分割符,【1】選擇分隔符右邊的,【0】選擇分隔符左邊的,分割後強制轉換成int型別
data['season'] = data['season'].apply(lambda x: int(x.split('-')[1]) )
data['season'].unique()

####執行結果如下:
['2000-01' '2001-02' '2002-03' '2003-04' '2004-05' '2005-06' '2006-07'
 '2007-08' '2008-09' '2009-10' '2010-11' '2011-12' '2012-13' '2013-14'
 '2014-15' '2015-16' '1996-97' '1997-98' '1998-99' '1999-00']
Out[117]:
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 97,
       98, 99,  0], dtype=int64)

還發現有“shot_distance”特徵,即投籃距離,這可能與科比投籃位置特徵重複,我們來比較一下:

通過上圖可以看到這兩個特徵是線性相關的,所以保留一組特徵即可。

再還有‘shot_zone_basic','shot_zone_basic','shot_zone_range'這三個特徵是不是也類似,再來比較一下:

import matplotlib.cm as cm
plt.figure(figsize=(20,10))

#data.groupyby(feature),是將資料根據feature裡的類進行分類

def scatterbygroupby(feature):
    alpha = 0.1
    gb = data.groupby(feature)
    cl = cm.rainbow(np.linspace(0,1,len(gb)))
    for g,c in zip(gb,cl):
        plt.scatter(g[1].loc_x,g[1].loc_y,color = c,alpha = alpha) #這裡為什麼是g[1]還沒搞清楚,希望知道的可以告知一下,謝謝!

plt.subplot(1,3,1)
scatterbygroupby('shot_zone_basic')
plt.title('shot_zone_basic')

plt.subplot(1,3,2)
scatterbygroupby('shot_zone_range')
plt.title('shot_zone_range')

plt.subplot(1,3,3)
scatterbygroupby('shot_zone_area')
plt.title('shot_zone_area')

可以看到這三組特徵也類似,保留一組即可。

下面把沒用的或類似的或重複的特徵從原始資料中去掉:

drops = ['shot_id', 'team_id', 'team_name', 'shot_zone_area', 'shot_zone_range', 'shot_zone_basic', \
         'matchup', 'lon', 'lat', 'seconds_remaining', 'minutes_remaining', \
         'shot_distance', 'game_event_id', 'game_id', 'game_date']
for drop in drops:
    data = data.drop(drop, 1)
data.head()

經過清清洗後的資料如下,data.head()顯示一下:

發現前兩個特徵裡都是英文,是計算機所不認識的,這裡採取一種經典的編碼方式,one-hot編碼,one-hot編碼即將特徵中的多個屬性當作多個新的特徵,並且每個資料在這多個特徵中只有一個為1,其餘都為0:

a = ['action_type', 'combined_shot_type', 'shot_type', 'opponent']
for i in a:
    #使用one-hot編碼,將a中的特徵裡的屬性值都當作新的特徵附在資料的列上,特徵名為字首prefix加上該屬性名
    data = pd.concat([data, pd.get_dummies(data[i], prefix=i)], 1)
    data = data.drop(i, 1)
data.head()

結果如下:

發現列數明顯增加了很多,新加的列數即把那些計算機不認識的屬性都當作特徵處理。直到這裡,資料才清洗完畢。

三、構造訓練集、訓練標籤,測試集、測試標籤

train_data = data.drop('shot_made_flag',1)#去掉標籤欄
train_label = data['shot_made_flag']#標籤欄

#將標籤為缺失值的資料作為測試集,第一步處理的時候已經將缺失值的資料去掉了,所以這裡只是熟悉一下測試集和測試標籤的構造,最後我們用訓練集進行簡單測試一下就好。也可以from sklearn.model_selection import train_test_split將原始有用的即有標籤的資料集進行一個切割,分成訓練集和測試集,用測試集進行測試。
test_data = data[pd.isnull(data['shot_made_flag'])]
test_data = test_data.drop('shot_made_flag',1)

四、選擇模型

這裡選擇整合演算法中的隨機森林,首先選擇用多少棵樹:

#這裡使用隨機森林模型進行預測,首先尋找最適的樹的棵樹
from sklearn.ensemble import RandomForestClassifier
from sklearn.cross_validation import KFold
from sklearn.metrics import confusion_matrix,accuracy_score,log_loss
import time
#logspace預設以10為底,然後以分割的數為冪,若要以2為底,在後面加上base = 2
#這裡是平均分割,3是指在0和4之間平均分割3個數而不像linspace是間隔,這裡應為8,16,32

range_n = np.logspace(3,5,3,base = 2).astype(int)
best_n = 0
min_scores = 10000
scores_n = []

for n in range_n:
    print("樹的棵數 : {0}".format(n))
    t1 = time.time()
    scores = 0
    model = RandomForestClassifier(n_estimators=n)
    for train_k,test_k in KFold(len(train_data),n_folds=10,shuffle=True):
        model.fit(train_data.iloc[train_k], train_label.iloc[train_k])
        pred = model.predict(train_data.iloc[test_k])
        scores += log_loss(train_label.iloc[test_k],pred)/10
    scores_n.append(scores)
    if scores<min_scores:
        min_scores = scores
        best_n = n
    t2 = time.time()
    print('執行{0}棵數所需要的時間{1:.3f}'.format(n,t2-t1))   

print(best_n, min_scores)#列印隨機森林的樹的最佳數量和其損失值
print(scores_n)#列印不同數量樹的隨機森林模型的損失值

####執行結果如下:
樹的棵數 : 8
執行8棵數所需要的時間5.838
樹的棵數 : 16
執行16棵數所需要的時間11.923
樹的棵數 : 32
執行32棵數所需要的時間21.272
32 11.994654403643343
[12.59950205168197, 12.20163004686796, 11.994654403643343]

再選擇樹的最大深度,即進行剪枝,防止過擬合:

range_m = np.logspace(2,5,4,base=2).astype(int)
best_m = 0
min_scores = 10000
scores_m = []
for m in range_m:
    print("樹的最大深度 : {0}".format(m))
    t1 = time.time()
    scores = 0
    model = RandomForestClassifier(n_estimators=best_n,max_depth=m)
    for train_k,test_k in KFold(len(train_data),n_folds=10,shuffle=True):
        model.fit(train_data.iloc[train_k], train_label.iloc[train_k])
        pred = model.predict(train_data.iloc[test_k])
        scores += log_loss(train_label.iloc[test_k],pred)/10
    scores_m.append(scores)
    if scores<min_scores:
        min_scores = scores
        best_m = m
    t2 = time.time()
    print('深度為{0}時所需要的時間{1:.3f}'.format(m,t2-t1))   

print(best_m, min_scores)#列印樹的最大深度和其損失值
print(scores_m)#列印不同深度的隨機森林模型的損失值

####執行結果如下:
樹的最大深度 : 4
深度為4時所需要的時間4.749
樹的最大深度 : 8
深度為8時所需要的時間7.331
樹的最大深度 : 16
深度為16時所需要的時間12.739
樹的最大深度 : 32
深度為32時所需要的時間18.667
16 11.008054682278084
[11.994653839123812, 11.104822458405712, 11.008054682278084, 11.719071756233244]

顯示一下不同數量的樹與其損失值;不同深度與其損失值的關係如下圖:

五、訓練模型

model = RandomForestClassifier(n_estimators=best_n,max_depth=best_m)
model.fit(train_data,train_label)

六、模型的評估

pred = model.predict(train_data)
true = train_label

#1、精度得分
accuracy_score(true,pred)

#2、混淆矩陣
confusion_matrix(true,pred)

####執行結果如下:
0.9238821652332957
Out[135]:
array([[13834,   398],
       [ 1558,  9907]], dtype=int64)

七、隨便拿一個數據進行預測

#隨便拿一個樣本進行測試驗證,train_data.iloc[2]是一個Series型別的資料,為一列,現要將其轉換為一行,用reshape,-1代表指定行後它會自己算有多少列,而Series型別沒有reshape屬性,所以先將其轉換為陣列形式。

model.predict(np.array(train_data.iloc[2]).reshape(1,-1))

####執行結果如下:
array([0.])