科比資料集分析與預測
本文收集了一系列科比的資料,有投籃位置,投進二分、三分球個數,比賽剩餘時間,對手是誰等等來預測科比是否進球。主要是想通過本例來認識一下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.])