先來看看這份科比生涯的數據集:數據集下載
這個表格記錄了科比30000多個鏡頭的詳細數據,共有25個標簽。
具體的設計思路是將這25個標簽代表的數據進行分析,找出對科比投籃結果有影響的標簽,利用機器學習中隨機森林的算法訓練出可以預測科比是否能夠投籃命中的模型。
先來看看這25個標簽具體代表什么(自己不是籃球的專業人士和愛好者,所以具體的內容可能有所出入,不過不會影響到分析結果)
action_type(用什么方式投的籃)
combined_shot_type(結合什么方式投籃)
game_event_id(游戲事件ID)
game_id(游戲ID)
la(投籃的經度)
loc_x (投籃的x坐標)
loc_y(投籃的y坐標)
lon(投籃的緯度)
minutes_remaining(離比賽結束還有多少分鍾)
period(第幾場)
playoffs(是不是季后賽)
season(賽季)
seconds_remaining(離比賽結束還有多少秒)
shot_distance(投籃離籃筐的的距離)
shot_made_flag (是不是進球了(主要的標簽))
shot_type(2分球還是3分球區域)
shot_zone_area(投籃區域的表示方法一)
shot_zone_basic(投籃區域的表示方法二)
shot_zone_range(投籃區域的表示方法三)
team_id(隊伍ID)
team_name(隊伍名字)
game_date(比賽時間)
matchup(比賽雙方隊伍)
opponent(自己所在隊伍名字)
shot_id(鏡頭ID)
可以看到,這25個標簽中對於科比能否投籃命中有一些無關緊要的數據,比如team_id,因為這30000多份樣本中全是在湖人隊打的,shot_id,game_id等等這個數據也無關緊要,具體的分析將會在下面講解。
三、數據分析
首先我們導入數據,編寫代碼如下
import pandas as pd
# 導入數據
filename= "data.csv"
raw = pd.read_csv(filename)
print(raw.shape)
print(raw.head()) #head函數打印前5行,如果需要打印前10行,這樣寫
接下來我們再來分析這一份數據表,我們發現其中shot_made_flag這個標簽竟然有缺失值,這個表示了科比是否進球了,作為最重要的數據,是不能隨意進行填充的,我們必須刪除掉這些樣本進行下一步的工作,代碼如下
import pandas as pd
# 導入數據
filename= "data.csv"
raw = pd.read_csv(filename)
kobe = raw[pd.notnull(raw['shot_made_flag'])]
print(kobe.shape)
此時我們只有25697個數據進行訓練了。
接着我們分析lat,loc_x,loc_y,lon這4個標簽,這4個標簽說明了科比投籃的位置,而具體指的是什么呢,有什么關系嗎,我們畫散點圖來看一下。
編寫代碼如下
import pandas as pd
import matplotlib.pyplot as plt
# 導入數據
filename= "data.csv"
raw = pd.read_csv(filename)
#刪除shot_made_flag為空的數據項,並且命名為kobe用作訓練
kobe = raw[pd.notnull(raw['shot_made_flag'])]
#畫散點圖用來分析lat loc_x loc_y lon這4個標簽
alpha = 0.02 #指定一個數字,用於后面的透明度
plt.figure(figsize=(6,6)) #指定畫圖域
# loc_x and loc_y
plt.subplot(121) #一行兩列 第一個位置
plt.scatter(kobe.loc_x, kobe.loc_y, color='R', alpha=alpha) #畫散點圖
plt.title('loc_x and loc_y')
# lat and lon
plt.subplot(122) #一行兩列 第二個位置
plt.scatter(kobe.lon, kobe.lat, color='B', alpha=alpha)
plt.title('lat and lon')
plt.show()
我們大致可以看出,這4個坐標大致表示了距離籃筐的距離,那樣的話,我們接下來用於數據處理的時候選擇其中的一組數據即可了。
shot_type,shot_zone_area,shot_zone_basic,shot_zone_range 這4個標簽代表了投籃的區域,其實還是說明一件事,這里就不做贅述了,當然shot_zone_area,shot_zone_basic,shot_zone_range這3個標簽將投籃區域相比於shot_type來說分的更細,直接刪掉是不是會有問題,其實大可不必擔心,因為,接下來我們將會用極坐標的形式表示科比的投籃位置,將更會細化科比的投籃區域。
四、數據處理
首先處理我們上節所說的極坐標的問題,然后我們會發現算出來的dist和,shot_distance竟然具有正相關性。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
from sklearn.ensemble import RandomForestClassifier #導入隨機森林分類器
from sklearn.cross_validation import KFold
from sklearn.metrics import log_loss
# 導入數據
filename= "data.csv"
raw = pd.read_csv(filename)
#刪除shot_made_flag為空的數據項,並且命名為kobe用作訓練
kobe = raw[pd.notnull(raw['shot_made_flag'])]
#對於lat,loc_x,loc_y,lon這4個標簽,我們取loc_x和loc_y這2個標簽,並將其轉化為極坐標的形式
#dist表示離籃筐的距離,angle表示投籃的角度,這樣將會更好的科比投籃的反應結果`
raw['dist'] = np.sqrt(raw['loc_x']**2 + raw['loc_y']**2)
loc_x_zero = raw['loc_x'] == 0
raw['angle'] = np.array([0]*len(raw))
raw['angle'][~loc_x_zero] = np.arctan(raw['loc_y'][~loc_x_zero] / raw['loc_x'][~loc_x_zero])
raw['angle'][loc_x_zero] = np.pi / 2
#畫圖展示dist和shot_distance的正相關性
plt.figure(figsize=(5,5))
plt.scatter(raw.dist, raw.shot_distance, color='blue')
plt.title('dist and shot_distance')
plt.show()
運行結果如下
這樣我們可以保留其中的一個(這里我們保留了dist這個標簽),接着我們將minutes_remaining和seconds_remaining轉化成一個標簽remaining_time,然后刪除不必要的列,非數值型的轉換成onehot編碼格式
具體編寫代碼如下,具體說明在代碼注釋中
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 導入數據
filename= "data.csv"
raw = pd.read_csv(filename)
#刪除shot_made_flag為空的數據項,並且命名為kobe用作訓練
kobe = raw[pd.notnull(raw['shot_made_flag'])]
#對於lat,loc_x,loc_y,lon這4個標簽,我們取loc_x和loc_y這2個標簽,並將其轉化為極坐標的形式
#dist表示離籃筐的距離,angle表示投籃的角度,這樣將會更好的科比投籃的反應結果
raw['dist'] = np.sqrt(raw['loc_x']**2 + raw['loc_y']**2)
loc_x_zero = raw['loc_x'] == 0
raw['angle'] = np.array([0]*len(raw))
raw['angle'][~loc_x_zero] = np.arctan(raw['loc_y'][~loc_x_zero] / raw['loc_x'][~loc_x_zero])
raw['angle'][loc_x_zero] = np.pi / 2
#對於minutes_remaining:離比賽結束還有多少分鍾;seconds_remaining:離比賽結束還有多少秒(0-60),這
#2個屬性我們合成距離比賽結束的時間
raw['remaining_time'] = raw['minutes_remaining'] * 60 + raw['seconds_remaining']
#機器學習只能識別數值型的數據
#將賽季中'Jan-00' 'Feb-01' 'Mar-02' ··· '1998-99'轉換成
# 0 1 2 ··· 99
raw['season'] = raw['season'].apply(lambda x: int(x.split('-')[1]) )
#刪除對於比賽結果沒有影響的數據
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', 'loc_x', 'loc_y', 'game_event_id', 'game_id',
'game_date']
for drop in drops:
raw = raw.drop(drop, 1)
#將非數值型的數據轉換成為onehot編碼的格式,加入到數據中並且將原來的數據刪除
categorical_vars = ['action_type', 'combined_shot_type', 'shot_type', 'opponent', 'period', 'season']
for var in categorical_vars:
raw = pd.concat([raw, pd.get_dummies(raw[var], prefix=var)], 1)
raw = raw.drop(var, 1)
print(raw)
為什么會有129行之多,是因為我們用了onehot編碼,具體什么是onehot編碼這里就不做贅述了,感興趣的可以谷歌或者百度一下。
最后我們總結一下,到底這25個標簽還剩下什么,首先除去和比賽結果無關的標簽,’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’
然后’loc_x’, ‘loc_y’轉換成了極坐標的形式,變成了’dist’,’angle’;’seconds_remaining’和’minutes_remaining’合並成了’remaining_time’。
最后將’action_type’, ‘combined_shot_type’, ‘shot_type’, ‘opponent’, ‘period’, ‘season’轉換成onehot編碼格式。
至此我們的數據處理工作基本完成了。
五、利用sklearn來進行數據的處理
具體的思路是利用隨機森林分類器配合着交叉驗證的方法進行數據的分析,先找到最佳的樹的個數,和樹的深度。
編寫代碼如下
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
from sklearn.ensemble import RandomForestClassifier #導入隨機森林分類器
from sklearn.cross_validation import KFold
from sklearn.metrics import log_loss
# 導入數據
filename= "data.csv"
raw = pd.read_csv(filename)
#刪除shot_made_flag為空的數據項,並且命名為kobe用作訓練
kobe = raw[pd.notnull(raw['shot_made_flag'])]
#對於lat,loc_x,loc_y,lon這4個標簽,我們取loc_x和loc_y這2個標簽,並將其轉化為極坐標的形式
#dist表示離籃筐的距離,angle表示投籃的角度,這樣將會更好的科比投籃的反應結果`
raw['dist'] = np.sqrt(raw['loc_x']**2 + raw['loc_y']**2)
loc_x_zero = raw['loc_x'] == 0
raw['angle'] = np.array([0]*len(raw))
raw['angle'][~loc_x_zero] = np.arctan(raw['loc_y'][~loc_x_zero] / raw['loc_x'][~loc_x_zero])
raw['angle'][loc_x_zero] = np.pi / 2
# 對於minutes_remaining:離比賽結束還有多少分鍾;seconds_remaining:離比賽結束還有多少秒(0-60),這
# 2個屬性我們合成距離比賽結束的時間
raw['remaining_time'] = raw['minutes_remaining'] * 60 + raw['seconds_remaining']
#機器學習只能識別數值型的數據
#將賽季中'Jan-00' 'Feb-01' 'Mar-02' ··· '1998-99'轉換成
# 0 1 2 ··· 99
raw['season'] = raw['season'].apply(lambda x: int(x.split('-')[1]) )
# 刪除對於比賽結果沒有影響的數據
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', 'loc_x', 'loc_y', 'game_event_id', 'game_id',
'game_date']
for drop in drops:
raw = raw.drop(drop, 1)
#將非數值型的數據轉換成為onehot編碼的格式,加入到數據中並且將原來的數據刪除
categorical_vars = ['action_type', 'combined_shot_type', 'shot_type', 'opponent', 'period', 'season']
for var in categorical_vars:
raw = pd.concat([raw, pd.get_dummies(raw[var], prefix=var)], 1)
raw = raw.drop(var, 1)
#將數據分為訓練集和測試集
train_kobe = raw[pd.notnull(raw['shot_made_flag'])]
train_label = train_kobe['shot_made_flag']
train_kobe = train_kobe.drop('shot_made_flag', 1)
test_kobe = raw[pd.isnull(raw['shot_made_flag'])]
test_kobe = test_kobe.drop('shot_made_flag', 1)
print('尋找隨機森林分類器的的最佳樹的數量...')
min_score = 100000
best_n = 0
scores_n = []
range_n = np.logspace(0, 2, num=10).astype(int)
for n in range_n:
print('樹的數量 : {0}'.format(n))
t1 = time.time()
rfc_score = 0.
rfc = RandomForestClassifier(n_estimators=n)
for train_k, test_k in KFold(len(train_kobe), n_folds=10, shuffle=True):
rfc.fit(train_kobe.iloc[train_k], train_label.iloc[train_k])
pred = rfc.predict(train_kobe.iloc[test_k])
rfc_score += log_loss(train_label.iloc[test_k], pred) / 10
scores_n.append(rfc_score)
if rfc_score < min_score:
min_score = rfc_score
best_n = n
t2 = time.time()
print('建造 {0} 顆樹(耗時{1:.3f}秒)'.format(n, t2 - t1))
print("最佳樹的顆樹為 : {0},得分為: {1}".format(best_n,min_score))
print('\n')
print('尋找隨機森林分類器的最佳樹的最佳深度...')
min_score = 100000
best_m = 0
scores_m = []
range_m = np.logspace(0, 2, num=10).astype(int)
for m in range_m:
print("樹的最大的深度 : {0}".format(m))
t1 = time.time()
rfc_score = 0.
rfc = RandomForestClassifier(max_depth=m, n_estimators=best_n)
for train_k, test_k in KFold(len(train_kobe), n_folds=10, shuffle=True):
rfc.fit(train_kobe.iloc[train_k], train_label.iloc[train_k])
pred = rfc.predict(train_kobe.iloc[test_k])
rfc_score += log_loss(train_label.iloc[test_k], pred) / 10
scores_m.append(rfc_score)
if rfc_score < min_score:
min_score = rfc_score
best_m = m
t2 = time.time()
print('樹的最大深度為: {0}(耗時{1:.3f}秒)'.format(m, t2 - t1))
print('最佳樹的深度: {0},得分為:{1}'.format(best_m, min_score))
plt.figure(figsize=(10,5))
plt.subplot(121)
plt.plot(range_n, scores_n)
plt.ylabel('score')
plt.xlabel('number of trees')
plt.subplot(122)
plt.plot(range_m, scores_m)
plt.ylabel('score')
plt.xlabel('max depth')
plt.show()
下面我們用100,12這個參數訓練模型,並且預測出5000個’shot_made_flag’的缺失值。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
from sklearn.ensemble import RandomForestClassifier #導入隨機森林分類器
from sklearn.cross_validation import KFold
from sklearn.metrics import log_loss
# 導入數據
filename = "data.csv"
raw = pd.read_csv(filename)
# 刪除shot_made_flag為空的數據項,並且命名為kobe用作訓練
kobe = raw[pd.notnull(raw['shot_made_flag'])]
# 對於lat,loc_x,loc_y,lon這4個標簽,我們取loc_x和loc_y這2個標簽,並將其轉化為極坐標的形式
# dist表示離籃筐的距離,angle表示投籃的角度,這樣將會更好的科比投籃的反應結果`
raw['dist'] = np.sqrt(raw['loc_x']**2 + raw['loc_y']**2)
loc_x_zero = raw['loc_x'] == 0
raw['angle'] = np.array([0]*len(raw))
raw['angle'][~loc_x_zero] = np.arctan(raw['loc_y'][~loc_x_zero] / raw['loc_x'][~loc_x_zero])
raw['angle'][loc_x_zero] = np.pi / 2
# 對於minutes_remaining:離比賽結束還有多少分鍾;seconds_remaining:離比賽結束還有多少秒(0-60),這
# 2個屬性我們合成距離比賽結束的時間
raw['remaining_time'] = raw['minutes_remaining'] * 60 + raw['seconds_remaining']
# 機器學習只能識別數值型的數據
# 將賽季中'Jan-00' 'Feb-01' 'Mar-02' ··· '1998-99'轉換成
# 0 1 2 ··· 99
raw['season'] = raw['season'].apply(lambda x: int(x.split('-')[1]) )
#刪除對於比賽結果沒有影響的數據
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', 'loc_x', 'loc_y', 'game_event_id', 'game_id',
'game_date']
for drop in drops:
raw = raw.drop(drop, 1)
#將非數值型的數據轉換成為onehot編碼的格式,加入到數據中並且將原來的數據刪除
categorical_vars = ['action_type', 'combined_shot_type', 'shot_type', 'opponent', 'period', 'season']
for var in categorical_vars:
raw = pd.concat([raw, pd.get_dummies(raw[var], prefix=var)], 1)
raw = raw.drop(var, 1)
# print(raw)
# 將數據分為訓練集和測試集
train_kobe = raw[pd.notnull(raw['shot_made_flag'])]
train_label = train_kobe['shot_made_flag']
train_kobe = train_kobe.drop('shot_made_flag', 1)
test_kobe = raw[pd.isnull(raw['shot_made_flag'])]
test_kobe = test_kobe.drop('shot_made_flag', 1)
# 訓練模型並且用預測shot_made_flag的缺失值
model = RandomForestClassifier(n_estimators=100, max_depth=12)
model.fit(train_kobe, train_label)
predictions = model.predict(test_kobe)
result=pd.DataFrame({'shot_id':test_shot_id['shot_id'].as_matrix(),'shot_made_flag':predictions.astype(np.int32)})
result.to_csv("result.csv", index=False)
這里給出了5000個缺失值。
六、總結
本篇文章主要用了機器學習的sklearn庫,配合着numpy,pandas,matplotlib的技術路線,利用隨機森林算法對科比生涯數據進行分析,對缺失值進行了預測。