利用KNN分析预测英雄联盟游戏结果

一、简介

1、数据获取

我是直接在这里下载数据,这个网站还有类型的数据,总体还是比较全面的。

2、数据介绍

这个数据集收集近一万场钻石一到大师的比赛游戏前十分钟的数据。一共有四十列属性。下面主要介绍一下每个属性的含义。

标签 含义
gameId 游戏ID
blueWins 蓝方是否获胜(同时也是数据的目标值)
blueWardsPlaced 蓝色方眼数
blueWardsDestroyed 蓝色方被排眼数
blueFirstBlood 蓝色方是否获得第一滴血
blueKills 蓝色方击杀数
blueDeaths 蓝色方死亡数
blueAssists 蓝色方的助攻数
blueEliteMonsters 蓝色方获取的小龙数
blueDragons 蓝色方大龙数
blueHeralds 蓝色方先锋数
blueTowersDestroyed 蓝色方被摧毁的防御塔
blueTotalGold 蓝色方的总经济
blueAvgLevel 蓝色方平均等级
blueTotalExperience 蓝色方的总经验
blueTotalMinionsKilled 蓝色方补兵数
blueTotalJungleMinionsKilled 蓝色方击杀野怪
blueGoldDiff 双方经济差
blueExperienceDiff 经验差
blueCSPerMin 蓝色方最少个人补兵数
blueGoldPerMin 蓝色方个人最少经济

后面的红色方同理。

3、数据简要分析

首先由于数据为游戏前十分钟数据,而实际上游戏是20分钟才刷新大龙,因此上面的大龙击杀数便不需要。
除此之外会发现双方插眼数有些明显大于实际情况,初始饰品每支2分钟cd,每个人有一只如果一开始就插眼那么十分钟团队最多插20只饰品眼,但是由于实际情况中队伍还会购买真眼等,所以我预估每队十分钟最多插28只眼左右。因此大于该值的数据都是偏离实际的。

二、数据处理及模型建立

1、整体预览

首先是导入库

#导入库
import pandas as pd
import numpy as np

读取下载好的数据

df = pd.read_csv('high_diamond_ranked_10min.csv')
print(df.head())

整体查看数据

#整体查看数据
df.info()

这里可以看到数据类型以及是否有空值,当然空值也可以通过df.isnull().sum()来查看,这一语句是专门查看空值的。
通过对蓝色方胜利值的统计,可以知道该数据是否获胜场次是相近的,说明数据也是很平衡的。


image.png
2、处理异常值

刚刚上面在数据的简单介绍中提到了插眼数是有些偏离实际情况的,所以我画了个箱线图来查看。


箱线图.png

可以发现异常值其实非常多,并且偏离也很大,由于值太多近两千个所以并不能将其删除只能将其进行替换。

#对超过实际情况的值进行替换,十分钟最多眼数也就28左右,那些超过的应该是一整场的数据了
def get_WardsPlaced(data,name):
    print(len(df[data> 22]))    #有1978行数据偏离事实
    x_bar = data.mean()   #均值
    x_std = data.std()    #标准差
    #统计量
    print("未处理前的统计量:",data.describe())

    p75 = data.quantile(q=0.75)
    df[name] = data
    df.loc[data > 28,name] = p75
    print("进行值替换后再查看其统计量:",df[name].describe())
get_WardsPlaced(df['blueWardsPlaced'],'blueWardsPlaced_new')

这里对蓝色方的插眼数进行替换,替换完的箱线图如下:


image.png

值就相对集中也不会出现非常多的偏离值了。
处理完插眼数之后会发现排眼数有些甚至是多于插眼数的,这种就是数据错误了,有些是因为我修改了插眼数的值,有些是本来就大于原插眼数的,于是全部都进行删除。

#有38行数据不符合实际要求,被排眼数大于插眼数
print(len(df[df['blueWardsDestroyed'] > df['blueWardsPlaced_new']]))
df.drop(index=df[df['blueWardsDestroyed'] > df['blueWardsPlaced_new']].index,axis=1,inplace=True)
3、可视化
全队经济差距.png

通过这幅散点图可以明显的看出当某一方在十分钟经济差距超过(-6324,6744)这一区间的话,落后一方便没有翻盘的可能性。因为英雄联盟主要就是通过对线打团以及资源的把控来获得经济,将经济转换为装备领先,最终战胜对方来推倒防御塔,所以经济对比赛结果影响非常大。


全队经验差距.png

上图是经验的差距,经验主要体现于等级的领先,如果双方对线等级相差两级以上,那么低等级的一方会非常弱势,所以我除了画经验的差距图之外同时画出双方平均等级的散点图。


平均等级散点图.png

通过该散点图我们可以明显的看到当蓝色方的平均等级处于6.4而红色方在6.0以上那么,蓝色方基本没有获胜的希望,当然这只是从数据中得到的信息,并不代表全部比赛,因为双方都处于6点几的等级时还是胜负难分,这需要看其他的影响因素。
当蓝色方平均等级在7.0而红色方在6.0以下那么红色方便没有获胜的希望。这个拓展到全部比赛还是比较有信服力度的,因为平均队伍每个人领先对面一个等级以上。
双方击杀人数.png

通过这幅图我们可以看到当红色方击杀9个人以上而蓝色方击杀在9个人以下,那么大概率是红色方获胜。而反过来也同样成立。


视野.png

在游戏中视野对于比赛的影响也非常大,但是从这幅图中并没有很好的反应出来,我觉得主要原因是插眼数减去被排眼数并不能很好的反应出视野的得分,因为一只眼可能插在那里并没有发挥什么作用,比如在自己家野区插了个真眼但是前十分钟对方只顾着刷自己野区的野和抓人并没有来反野,那么这个眼其实就是没有发挥什么作用的,所以感觉还是数据的问题,但是由于没有更好的数据替代只能将就着了。
3、生成特征

我主要用了13个特征

特征 介绍
blue_view 蓝色方的视野(插眼减去被排的眼)
blueTowersDestroyed 蓝色方摧毁的防御塔(十分钟获得一塔说明游戏优势很大)
blueKDA 击杀助攻以及死亡数计算而出
blue_num_pkill 蓝色方每次击杀由几个人完成(通过助攻加击杀的和除以击杀,体现支援的速度)
blueEliteMonsters 小龙数(由于现在是龙魂版本所以小龙也很重要)
blueGoldDiff 双方的经济差
grade_Diff 双方平均等级差

红色方同理。
生成这些特征后,由于有除法所以数据有一些nan值和无穷大值存在,所以又要对数据进行清洗一下。

4、建立模型

我使用的是knn近邻分类算法,将数据划分为训练集和测试集,进行训练模型。
结果如下:
没有调参的knn
利用自己生成的特征进行训练,预测准确结果是70.2%,


image.png

利用网格搜索进行调参,最优参数的knn对自己生成的特征属性预测准则率是73.1%


image.png

三、代码

#导入库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
%matplotlib inline

#导入数据
df = pd.read_csv('high_diamond_ranked_10min.csv')
print(df.head())
#查看数据的格式
print(df.shape)
#整体查看数据
print(df.info())
#查看是否有空值
print(df.isnull().sum())
#数据红蓝方的胜利场次
print(df.blueWins.value_counts())


#通过画图来分析数据

#箱线图
#画箱线图查看蓝方插眼数的离散值
df['blueWardsPlaced'].plot(kind='box')
plt.show()
#对超过实际情况的值进行替换,十分钟最多眼数也就28左右,那些超过的应该是一整场的数据了
def get_WardsPlaced(data,name):
    print(len(df[data> 22]))    #有1978行数据偏离事实
    x_bar = data.mean()   #均值
    x_std = data.std()    #标准差
    #统计量
    print("未处理前的统计量:",data.describe())

    p75 = data.quantile(q=0.75)
    df[name] = data
    df.loc[data > 28,name] = p75
    print("进行值替换后再查看其统计量:",df[name].describe())
get_WardsPlaced(df['blueWardsPlaced'],'blueWardsPlaced_new')
#替换完数据再画箱线图查看其偏离程度
df['blueWardsPlaced_new'].plot(kind='box')
plt.show()
#有38行数据不符合实际要求,被排眼数大于插眼数
print(len(df[df['blueWardsDestroyed'] > df['blueWardsPlaced_new']]))
df.drop(index=df[df['blueWardsDestroyed'] > df['blueWardsPlaced_new']].index,axis=1,inplace=True)
#对红色方的偏离的插眼数进行替换
get_WardsPlaced(df['redWardsPlaced'],'redWardsPlaced_new')
#对被排眼数大于插眼的数据进行整行删除,共37行
print("\n被排眼数大于插眼数:",len(df[df['redWardsDestroyed'] > df['redWardsPlaced_new']]))
df.drop(index=df[df['redWardsDestroyed'] > df['redWardsPlaced_new']].index, axis=1, inplace=True)


#双方经济差散点图
#查看蓝色方经济差优势最大是多少被红方翻盘
print(df.blueGoldDiff.where(df.blueWins == 0).sort_values(ascending=False).head(1))
#查看红色方经济差优势最大是多少被蓝方翻盘
print(df.redGoldDiff.where(df.blueWins == 1).sort_values(ascending=False).head(1))

#双方经济差可视化
plt.rcParams['font.sans-serif']='SimHei'     #显示中文
plt.rcParams['axes.unicode_minus'] = False   #显示负号
plt.scatter(df.blueGoldDiff.where(df.blueWins == 0),df.blueWins.where(df.blueWins == 0),label='红方胜利')
plt.scatter(df.blueGoldDiff.where(df.blueWins == 1),df.blueWins.where(df.blueWins == 1),label='蓝方胜利')
plt.ylim(-0.1,1.1)
#plt.scatter(df.redGoldDiff,df.blueWins,c=df.blueWins)
plt.xlabel('双方经济差')
plt.ylabel('红方胜利                           蓝方胜利')
plt.legend(loc='upper left')
plt.vlines(-6324,-1,2,color='red',linestyle=':')
plt.vlines(6744,-1,2,color='red',linestyle=':')
plt.show()


#双方经验差散点图
#蓝色方经验领先5355还输了
print(df.blueExperienceDiff.where(df.blueWins == 0).sort_values(ascending=False).head(1))
#红色方失败的居最高领先经验4619
print(df.redExperienceDiff.where(df.blueWins == 1).sort_values(ascending=False).head(1))

plt.scatter(df.blueExperienceDiff.where(df.blueWins == 0),df.blueWins.where(df.blueWins == 0),label='红方胜利')
plt.scatter(df.blueExperienceDiff.where(df.blueWins == 1),df.blueWins.where(df.blueWins == 1),label='蓝方胜利')
plt.xlabel('')
plt.ylim(-0.1,1.1)
plt.xlabel('blueExperienceDiff')
plt.ylabel('红方胜利                           蓝方胜利')
plt.vlines(5355,-1,2,color='r',linestyle=':')
plt.vlines(-4619,-1,2,color='red',linestyle=':')
plt.legend()
plt.show()


#平均等级散点图
plt.scatter(df.blueAvgLevel,df.redAvgLevel,c=df.blueWins,label='等级差')
plt.legend()
plt.xlabel('blueAvgLevel')
plt.ylabel('redAvgLevel')
plt.ylim(4.5,8.5)
plt.xlim(4.3,8.2)
plt.vlines(7.0, 3, 9,color='red',linestyle=':')
plt.vlines(6.4,3,9, color='red', linestyle=':')
plt.hlines(6.0,4,8.5,color='red',linestyle=':')
plt.show()


#双方击杀数散点图
#当蓝色方击杀超过9人时,双方胜利的可能性
print(df.blueWins.where(df.blueKills >= 9).value_counts(normalize=True))
print(df.blueWins.where(df.redKills >= 9).value_counts(normalize=True))

#根据击杀数来画散点图,观察大概位于哪个区间蓝色方胜利
plt.scatter(df['blueKills'],df['redKills'],c=df.blueWins)
plt.xlim(-0.8,23.2)
plt.ylim(-0.8,23.2)
plt.hlines(9,-1,24, color='red',linestyle=':')
plt.vlines(9,-1,24,color='red',linestyle=':')
plt.xlabel('bluekill')
plt.ylabel('redkill')
plt.show()


#双方视野散点图
#根据双方视野来绘制散点图
plt.scatter(df['blueWardsPlaced_new'] - df['blueWardsDestroyed'],df['redWardsPlaced_new'] - df['redWardsDestroyed'],c=df.blueWins)
plt.xlabel('blue_view')
plt.ylabel('red_view')
plt.show()


#生成需要的特征变量
data_new = pd.DataFrame()
data_new['blueWins'] = df['blueWins']
#蓝色方实际的眼数
data_new['blue_view'] = df['blueWardsPlaced_new'] - df['blueWardsDestroyed']
#蓝色方被摧毁的防御塔
data_new['blueTowersDestroyed'] = df['blueTowersDestroyed']
#蓝色方的KDA
data_new['blueKDA'] = (df['blueKills']+df['blueAssists'])/df['blueDeaths']
#蓝色方每次击杀的参与人数
data_new['blue_num_pkill'] = (df['blueAssists']+df['blueKills'])/df['blueKills']
#蓝色方的击杀小龙数
data_new['blueEliteMonsters'] = df['blueEliteMonsters']
#双方的经济差
data_new['blueGoldDiff'] = df['blueGoldDiff']
#双方的平均等级差
data_new['grade_Diff'] = df['blueAvgLevel'] - df['redAvgLevel']
data_new['red_view'] = df['redWardsPlaced_new'] - df['redWardsDestroyed']
data_new['redTowersDestroyed'] = df['redTowersDestroyed']
data_new['redKDA'] = (df['redKills']+df['redAssists'])/df['redDeaths']
data_new['red_num_pkill'] = (df['redAssists']+df['redKills'])/df['redKills']
data_new['redEliteMonsters'] = df['redEliteMonsters']
print(data_new.head())

#对新特征进行清洗
#对运算后为nan值的行进行替换
data_new['blue_num_pkill'] = data_new['blue_num_pkill'].fillna(0)
data_new['red_num_pkill'] = data_new['red_num_pkill'].fillna(0)
#将无穷大进行替换,replace将在原数据的副本上操作,所以时间比较慢
data_new['blueKDA'] = data_new['blueKDA'].replace(np.inf,(df['blueKills']+df['blueAssists']))
data_new['redKDA'] = data_new['redKDA'].replace(np.inf,(df['redKills']+df['redAssists']))
#print(data_new['blue_num_pkill'].isnull().sum())
#print(data_new['red_num_pkill'].isnull().sum())
print(data_new['blueKDA'].describe())


#建立模型
#对数据进行划分
x_train,x_test,y_train,y_test = train_test_split(data_new.iloc[:,1:],data_new['blueWins'],test_size=0.2,random_state=1)
print("训练集前五行:",x_train.head())
print("训练集结果前五行:",y_train.head())
print("测试集前五行:",x_test.head())
print("测试集结果前五行:",y_test.head())

#建立最普通的knn算法,70.2%
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()
#训练模型
knn.fit(x_train,y_train)
#预测结果
y_predicted = knn.predict(x_test)
#计算准确率
accuracy = np.mean(y_predicted == y_test)*100
print("当前分类评估器是:knn")
print("当前Accuracy是:%.1f"%accuracy+"%")

#使用网格搜素,寻找最优参数
knn_params = {"n_neighbors" :np.arange(95,105),
             "weights" : ["distance"],
             "algorithm" : ["ball_tree"] , #,"kd_tree","brute"
             "leaf_size":[1,2]}
knn = KNeighborsClassifier()
grid_knn = GridSearchCV(knn, knn_params,cv=5,verbose=2,n_jobs=-1)
grid_knn.fit(x_train,y_train)
knn_best_params=grid_knn.best_params_
print(knn_best_params)

#最优参数knn的结果使用data_new数据
knn = KNeighborsClassifier(algorithm="ball_tree",n_neighbors=98,weights="distance",leaf_size=1)
knn.fit(x_train,y_train)
y_predicted = knn.predict(x_test)
accuracy = np.mean(y_predicted == y_test)*100
print("当前分类评估器是:knn")
print("当前Accuracy是:%.1f"%accuracy+"%")

四、总结

上面的预测准确率只有73%左右,我认为主要的原因是这只是前十分钟的数据并且还缺少了很多的特征,就如上面所提到的视野问题、甚至双方选择的英雄也有影响(前期英雄或者后期英雄等),每个英雄的优势期都不一样、以及打野的意识等这些很难通过数据表现出来,所以导致模型预测率不是很理想。
一开始选择这个数据做预测的时候只是为了完成实验报告,自己下载好数据后进行一顿操作后发现预测率卡在了70%左右,这其实不是很理想,然后自己就开始通过生成更多的特征来试图提高预测率,但是并没有用,后来实在没办法上网一搜发现居然有人已经做了这份数据的分析。
所以最后借鉴了大佬的方法利用网格调参终于让准确度有了点点增加,同时通过大佬的代码也学到了利用画图来将数据的特点呈现出来而不是仅仅靠输出数据来观察。
不过我的特征变量与大佬不一样,当然我也尝试结合了两个人生成的特征变量来训练模型,可惜并没有提高,可能是变量的相关性太高了,因为大佬做的是对击杀助攻经济差距的处理,而我是通过视野,每次击杀参与人数等虽然我没对经济差有什么处理,但是我的特征中也是传入了经济差的,才会导致加入新特征后结果还是没有什么进步。

参考:https://blog.csdn.net/deephub/article/details/109667929

你可能感兴趣的:(利用KNN分析预测英雄联盟游戏结果)