絕地求生游戲最終排名預測
知識點
-
數據讀取與預覽
-
數據可視化
-
構建隨機森林預測模型
導入數據並預覽
先導入數據並預覽。本次實驗同樣來源於 Kaggle 上的一個競賽: 絕地求生排名預測 ,由於原始數據較大,我們只取了其中一部分的數據來進行分析。如果你想分析所有的數據可以去 下載原始數據。
讀取數據並預覽前5行
import pandas as pd
df = pd.read_csv('train.csv')
df.head()
由上面的輸出結果可知,數據主要由 29 列構成。我們所要預測的列為 winPlacePerc 。各列所表示的含義如下。
- DBNOs - 擊倒多少敵人
- assists - 傷害過多少敵人(最終該敵人被隊友殺害)
- boosts - 使用過多少個提升性的物品 (boost items used)
- damageDealt - 造成的總傷害-自己所受的傷害
- headshotKills - 通過爆頭而殺死的敵人數量
- heals - 使用了多少救援類物品
- Id - 玩家ID
- killPlace - 殺死敵人數量的排名
- killPoints - 基於殺戮的玩家外部排名。將其視為Elo排名,只有殺死才有意義。如果 rankPoints 中的值不是 -1,那么 killPoints 中的任何 0 都應被視為“無”。
- killStreaks - 短時間內殺死敵人的最大數量
- kills - 殺死的敵人的數量
- longestKill - 玩家和玩家在死亡時被殺的最長距離。 這可能會產生誤導,因為擊倒一名球員並開走可能會導致最長的殺戮統計數據。
- matchDuration - 匹配用了多少秒
- matchId - 匹配的 ID(每一局一個 ID)
- matchType - 單排/雙排/四排;標准模式是 “solo”,“duo”,“squad”,“solo-fpp”,“duo-fpp”和“squad-fpp”; 其他模式來自事件或自定義匹配。
- rankPoints - 類似 Elo 的玩家排名。 此排名不一致,並且在 API 的下一個版本中已棄用,因此請謹慎使用。值 -1 表示“無”。
- revives - 玩家救援隊友的次數
- rideDistance - 玩家使用交通工具行駛了多少米
- roadKills - 在交通工具上殺死了多少玩家
- swimDistance - 游泳了多少米
- teamKills - 該玩家殺死隊友的次數
- vehicleDestroys - 毀壞了多少交通工具
- walkDistance - 步行運動了多少米
- weaponsAcquired - 撿了多少把槍
- winPoints - 基於贏的玩家外部排名。將其視為 Elo 排名,只有獲勝才有意義。如果 kPoints 中的值不是 -1,那么 winPoints 中的任何 0 都應被視為“無”。
- groupId - 隊伍的 ID。 如果同一組玩家在不同的比賽中比賽,他們每次都會有不同的 GroupId。
- numGroups - 在該局比賽中有玩家數據的隊伍數量
- maxPlace - 在該局中已有數據的最差的隊伍名詞(可能與該局隊伍數不匹配,因為數據收集有跳躍)
- winPlacePerc - 預測目標,是以百分數計算的,介於 0-1 之間,1 對應第一名,0 對應最后一名。 它是根據 maxPlace 計算的,而不是 numGroups ,因此匹配中可能缺少某些隊伍。
# 現在查看一下數據的基本信息。
df.info()
# 由上可知,該數據集中不含有缺失值,查看數據描述。
df.describe()
數據可視化
# 由於我們所要預測的列為 winPlacePerc ,即排名情況,所以先來分析該列。先導入相關的畫圖工具。
import seaborn as sns
from matplotlib import pyplot as plt
%matplotlib inline
plt.style.use('fivethirtyeight')
#winPlacePerc 列是系統給出的游戲排名,而 winPoints 是外部給出的游戲排名,現在畫出這兩列的數據分布圖。
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_figwidth(15)
sns.distplot(df['winPlacePerc'], ax=ax1)
sns.distplot(df['winPoints'], ax=ax2)
plt.show()
從上的結果可以看到,游戲排名似乎呈兩極分化現象,0 和 1 兩頭的人數都相對多一點。
# 現在來看玩家擊倒敵人的人數的情況。
train_dbno = pd.DataFrame(df['DBNOs'].value_counts(), columns=['DBNOs'])
dbno = train_dbno.iloc[:9, :]
dbno.iloc[8]['DBNOs'] = train_dbno.iloc[8:, :].sum()['DBNOs']
plt.figure(figsize=(14, 5))
sns.barplot(dbno.index, dbno.DBNOs)
plt.gca().set_xticklabels([0, 1, 2, 3, 4, 5, 6, 7, '8+'])
plt.gca().set_xlabel('No of enemy players knocked')
plt.gca().set_ylabel("count")
plt.show()
plt.savefig("enemy_")
從上圖可以看到,擊倒敵人的數量越多,排名也就越高。這說明,擊倒敵人與排名有很大的關系。
fig, (ax1, ax2) = plt.subplots(figsize=(15, 5))
sns.pointplot(x='DBNOs', y='winPlacePerc', data=df, alpha=0.8)
plt.xlabel('Number of DBNOs', fontsize=15, color='blue')
plt.ylabel('Win Percentage', fontsize=15, color='blue')
plt.title('DBNOs / Win Ratio', fontsize=20, color='blue')
plt.grid()
plt.show()
從上圖可以看到,擊倒敵人的數量越多,排名也就越高。這說明,擊倒敵人與排名有很大的關系。
# 現在看在一局游戲中,玩家自己所受到的傷害。
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_figwidth(15)
sns.distplot(df['damageDealt'], ax=ax1)
sns.boxplot(df['damageDealt'], ax=ax2)
plt.show()
從上圖可以看出,大多數人的受到的傷害在 0 到 500 之間。現在來看玩家受傷害值是否與排名有關系。
# 將傷害值分為 6 個部分。
# pd.cut()
data = df.copy()
data['damageDealt_rank'] = pd.cut(data['damageDealt'],
[-1,500,1000,1500,2000,2500,60000],
labels=['0-500','500-1000','1000-1500',
'1500-2000','2000-2500','2500+'])
f,ax1 = plt.subplots()
sns.pointplot(x='damageDealt_rank', y='winPlacePerc', data=data,alpha=0.8)
plt.xlabel('damageDealtk', fontsize=15, color='blue')
plt.xticks(rotation=45)
plt.ylabel('Win Percentage', fontsize=15, color='blue')
plt.title('damageDealt / Win Ratio', fontsize=20, color='blue')
plt.grid()
plt.show()
從上圖可以看到,玩家排名越靠前,所受到的傷害就越大。
現在來看殺死敵人的排名情況。
plt.figure()
sns.distplot(df['killPlace'], bins=50)
plt.show()
從上圖可以看出,在殺死敵人排名中呈現均勻分布的現象。
現看一下殺死敵人的數量。
plt.figure()
sns.distplot(df['killPlace'])
plt.show()
從上圖可以看出,在殺死敵人排名中呈現均勻分布的現象。
現看一下殺死敵人的數量。
# sns.regplot
plt.figure()
sns.regplot(df['kills'].values, df['damageDealt'].values)
plt.gca().set_ylabel('Damage dealt')
plt.gca().set_xlabel('Total kills')
plt.show()
從上圖可以看到,大多數玩家殺死敵人的數量都不超過 5 個人。從右圖看到,有個別玩家在游戲中殺死敵人的數量超多了 20 人。
我們可以分析一下,游戲玩家殺死敵人的數量與自己所受到的傷害的關系。
plt.figure()
sns.regplot(df['kills'].values, df['damageDealt'].values)
plt.gca().set_ylabel('Damage dealt')
plt.gca().set_xlabel('Total kills')
plt.show()
從上圖可以看到,一個玩家殺死敵人的數量越多,自己所受到的傷害就越大,基本呈線性關系。
現在分析一下玩家殺死敵人的數量與排名的關系。
data = df.copy()
# 將殺死敵人的數量分為 6 個部分。
data['kills_rank'] = pd.cut(data['kills'], [-1, 0, 2, 5, 10, 20, 60],
labels=['0_kills', '1-2_kills', '3-5_kills',
'6-10_kills', '11-20_kills', '20+kills'])
plt.figure(figsize=(10, 4))
sns.boxplot(x='kills_rank', y='winPlacePerc', data=data)
plt.show()
從上圖可以看到,玩家殺死敵人的數量越多,其最后的排名也就越高。
再來看一下玩家在游戲中,一槍爆頭的個數。
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_figwidth(15)
sns.distplot(df['headshotKills'], ax=ax1)
sns.boxplot(df['headshotKills'], ax=ax2)
plt.show()
從上圖可看到,大多數玩家都沒有一槍爆頭。但在右圖中,有個別玩家一槍爆頭的數量到達了 8 人。
現在看一下,爆頭人數與排名之間的關系。
f, ax1 = plt.subplots(figsize=(14, 4))
sns.pointplot(x='headshotKills', y='winPlacePerc', data=df, alpha=0.8)
plt.xlabel('Number of headshotKills', fontsize=15, color='blue')
plt.ylabel('Win Percentage', fontsize=15, color='blue')
plt.title('headshotKills/ Win Ratio', fontsize=20, color='blue')
plt.grid()
plt.show()
可以查看一下短時間內殺死敵人的數量。
killstreak = pd.DataFrame(df['killStreaks'].value_counts())
killstreak.iloc[4] = killstreak.iloc[4:].sum()
killstreak = killstreak[:5]
sns.barplot(killstreak.index, killstreak['killStreaks'])
data = df.copy()
data['move'] = data['rideDistance']+data['swimDistance']+data['walkDistance']
sns.distplot(data['move'])
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_figwidth(15)
sns.distplot(df['weaponsAcquired'], ax=ax1)
sns.boxplot(df['weaponsAcquired'], ax=ax2)
f, ax1 = plt.subplots(figsize=(15, 5))
sns.pointplot(x='weaponsAcquired', y='winPlacePerc', data=df, alpha=0.8)
plt.xlabel('Number of weaponsAcquired', fontsize=15, color='blue')
plt.ylabel('Win Percentage', fontsize=15, color='blue')
plt.title('weaponsAcquired/ Win Ratio', fontsize=20, color='blue')
plt.grid()
plt.show()
# heals - 使用了多少救援類物品
# boosts - 使用過多少個提升性的物品 (boost items used)
data = df.copy()
f, ax1 = plt.subplots(figsize=(14, 4))
sns.pointplot(x='heals', y='winPlacePerc', data=data, color='lime', alpha=0.8)
sns.pointplot(x='boosts', y='winPlacePerc', data=data, color='blue', alpha=0.8)
plt.text(0, 0.9, 'Heals', color='lime', fontsize=17, style='italic')
plt.text(0, 0.85, 'Boosts', color='blue', fontsize=17, style='italic')
plt.xlabel('Number of heal/boost items', fontsize=15, color='blue')
plt.ylabel('Win Percentage', fontsize=15, color='blue')
plt.title('Heals vs Boosts', fontsize=20, color='blue')
plt.grid()
特征工程
上面只是對數據集中的一些特征列進行了可視化,以便更好的理解數據。而我們的任務是根據這些特征來預測玩家的排名。現在我們對數據進行手工提取特征。
def dispose(df):
# 救援類物品和提升性能類物品都可以算作是一類,因此將這兩者加起來得到一個新的特征列。同樣的方法對距離進行處理。
df['healsAndBoosts'] = df['heals']+df['boosts']
df['totalDistance'] = df['walkDistance']+df['rideDistance']+df['swimDistance']
# 當使用提升類物品時,游戲玩家可以運行得更快。同時也幫助玩家保持在區外。因此,我們可以創建一個特征列,用來記錄游戲玩家沒走一步所消耗的提升性物品。救援類物品雖然不會使玩家跑得更快,但也有助於保持遠離危險地帶。所以讓我們也為救援類物品創建相同的特征列。
df['boostsPerWalkDistance'] = df['boosts'] / \
(df['walkDistance']+1) # 加 1 是為了防止分母為 0
df['boostsPerWalkDistance'].fillna(0, inplace=True)
df['healsPerWalkDistance'] = df['heals']/(df['walkDistance']+1)
df['healsPerWalkDistance'].fillna(0, inplace=True)
df['healsAndBoostsPerWalkDistance'] = df['healsAndBoosts'] / \
(df['walkDistance']+1)
df['healsAndBoostsPerWalkDistance'].fillna(0, inplace=True)
df[['walkDistance', 'boosts', 'boostsPerWalkDistance', 'heals',
'healsPerWalkDistance', 'healsAndBoosts', 'healsAndBoostsPerWalkDistance']][40:45]
# 提取殺死敵人的數量與步行距離的關系。
df['killsPerWalkDistance'] = df['kills'] / \
(df['walkDistance']+1) # 加 1 是為了防止分母為 0
df['killsPerWalkDistance'].fillna(0, inplace=True)
df[['kills', 'walkDistance', 'rideDistance','killsPerWalkDistance', 'winPlacePerc']].tail(5)
return df
df=dispose(df)
構建模型
先來看一下我們的數據。
df.head()
從上圖可以看到,此時的數據包含 36 列。但玩家編號(Id)、分組編號(groupId)、游戲局編號(matchId)、游戲的類型(matchType)對預測結果是沒有幫助的。因此現在將這四列刪除掉。
def df_drop(df):
df_drop = df.drop(['Id', 'groupId', 'matchId', 'matchType'], axis=1)
df_drop = df_drop(df)
划分訓練集和測試集。
from sklearn.model_selection import train_test_split
data_X = df_drop.drop(['winPlacePerc'], axis=1)
data_y = df_drop['winPlacePerc']
構建預測模型。這里我們使用隨機森林回歸的方法來構建模型。
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(n_estimators=40) # 構建模型
model.fit(data_X, data_y) # 訓練模型
test_X = pd.read_csv('test_C2.csv')
dispos(test_X)
y_pred = model.predict(test_X) # 預測
y_pred[:10]
上面我們完成了預測模型的構建預訓練,並對測試集進行預測。為了直觀的看出模型預測的好壞,現在通過畫圖的方法來對比。
f, ax1 = plt.subplots(figsize=(15, 5))
plt.plot(test_y[:100])
plt.plot(y_pred[:100])
在上圖中,藍色線條表示測試數據的真實值,而紅色線條表示預測的數據。從圖中可以看出,我們所構建的模型基本能夠預測正確。現在查看一下均方誤差。
from sklearn.metrics import mean_squared_error
mean_squared_error(y_pred, test_y)
。
。
。
。
。
還有較多bug,未完待續。。。。。
