1. 数据准备¶
pubg-finish-placement-prediction/train_V2.csv 数据集来自https://www.kaggle.com/c/pubg-finish-placement-prediction
python3
import numpy as np
import pandas as pd # .CSV格式数据处理 I/O (e.g. pd.read_csv)
import plotly.graph_objects as go
import plotly.express as px
import plotly.figure_factory as ff
from plotly.subplots import make_subplots
from plotly.offline import init_notebook_mode, iplot
import seaborn as sns
import matplotlib as ml
import matplotlib.pyplot as plt
from scipy import stats
%matplotlib inline
ml.style.use('ggplot') # 使用自带样式进行美化
2. 数据预处理¶
2.1. 数据集概况和缺失值处理¶
让我们先来看看该数据集的头和尾
pbg = pd.DataFrame(pd.read_csv('train_V2.csv'))
pbg.head()
pbg.tail(30)
接着让我们来看看数据集中各属性的数据类型、五数概括以及缺失值情况
pbg.info()
下面依次给出数据集中各字段解释:
接着我们来看一下数据集中各属性的五数概括:
pbg.describe()
pbg.isnull().sum()
显然,从上面的缺省值情况可以看出,数据集看起来很好,除了winPlacePerc中有一个缺省值,其余都没有。同时因为它是用户特定的值,我们不能按照
一般的缺省值处理去估计/猜测它,所以直接删除winPlacePerc中的缺少值。
# creating a copy
pg = pbg.copy()
pg[pg.winPlacePerc.isnull()]
pg.dropna(axis=0, inplace=True)
再来测试一下删除缺省值的效果:
pbg = pg
pbg.isnull().sum()
2.2. 异常值处理¶
一些行中的数据统计出来的结果非常反常规,那么这些玩家肯定有问题,为了后续训练模型的准确性,我们会把这些异常数据剔除
例如识别出玩家在游戏中有击杀数,但是全局没有移动;这类型玩家肯定是存在异常情况(挂机),我们把这些玩家删除。
# 创建新变量,统计玩家移动距离
pbg['totalDistance'] = pbg['rideDistance'] + pbg['walkDistance'] + pbg['swimDistance']
# 创建新变量,统计玩家是否在游戏中,有击杀,但是没有移动,如果是返回True, 否则返回false
pbg['killsWithoutMoving'] = ((pbg['kills'] > 0) & (pbg['totalDistance'] == 0))
pbg["killsWithoutMoving"].head()
pbg.head()
可以看到最后两列列出了总移动距离和是否存在 有击杀但没有移动 的情况
# 检查是否存在有击杀但是没有移动的数据
pbg[pbg['killsWithoutMoving'] == True].shape
pbg[pbg['killsWithoutMoving'] == True][['killsWithoutMoving']].head()
# 删除这些数据
pbg.drop(pbg[pbg['killsWithoutMoving'] == True].index, inplace=True)
# 再次检查
pbg[pbg['killsWithoutMoving'] == True].shape
# 查看驾车杀敌数超过十个的玩家,因为一般我们认为驾车杀敌非常困难
pbg[pbg['roadKills'] > 10]
# 删除这些数据
pbg.drop(pbg[pbg['roadKills'] > 10].index, inplace=True)
pbg.shape
异常值处理:删除玩家在一局中杀敌数超过30人的数据
首先绘制一下玩家杀敌数
temp = pbg['kills'].value_counts().sort_values(ascending=False)
print("Total number of states : ",len(temp))
trace = go.Bar(
x = temp.index,
y = (temp),
marker=dict(color='crimson', line=dict(color='black', width=1.5), opacity=0.75)
)
data = [trace]
layout = go.Layout(title = "",
xaxis=dict(title='kills', tickfont=dict(size=14, color='rgb(107, 107, 107)')
),
yaxis=dict(title='Count of kills', titlefont=dict(size=16, color='rgb(107, 107, 107)'),
tickfont=dict(size=14, color='rgb(107, 107, 107)')),
bargap=0.2, bargroupgap=0.1, paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor="rgb(243, 243, 243)")
fig = go.Figure(data=data, layout=layout)
iplot(fig)
# 找出杀敌数大于30
pbg[pbg['kills'] > 30].shape
pbg[pbg['kills'] > 30][['kills']].head()
# 删除上述异常数据
pbg.drop(pbg[pbg['kills'] > 30].index, inplace=True)
temp = pbg['kills'].value_counts().sort_values(ascending=False)
print("Total number of states : ", len(temp))
trace = go.Bar(
x = temp.index,
y = (temp),
marker=dict(color='crimson', line=dict(color='black', width=1.5), opacity=0.75)
)
data = [trace]
layout = go.Layout(title = "",
xaxis=dict(title='kills', tickfont=dict(size=14, color='rgb(107, 107, 107)')
),
yaxis=dict(title='Count of kills', titlefont=dict(size=16, color='rgb(107, 107, 107)'),
tickfont=dict(size=14, color='rgb(107, 107, 107)')),
bargap=0.2, bargroupgap=0.1, paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor="rgb(243, 243, 243)")
fig = go.Figure(data=data, layout=layout)
iplot(fig)
异常值处理:删除爆头率异常数据
如果一个玩家的击杀爆头率过高,也说明其有问题
# 创建变量爆头率
pbg['headshot_rate'] = pbg['headshotKills'] / pbg['kills']
pbg['headshot_rate'] = pbg['headshot_rate'].fillna(0.0)
pbg["headshot_rate"].tail()
pbg.head()
pbg[(pbg['headshot_rate'] == 1) & (pbg['kills'] > 9)].shape
pbg[(pbg['headshot_rate'] == 1) & (pbg['kills'] > 9)].head()
pbg.drop(pbg[(pbg['headshot_rate'] == 1) & (pbg['kills'] > 9)].index, inplace=True)
# 再次检查
pbg[(pbg['headshot_rate'] == 1) & (pbg['kills'] > 9)].shape
# 距离整体描述
pbg[['walkDistance', 'rideDistance', 'swimDistance', 'totalDistance']].describe()
# 行走距离处理
plt.figure(figsize=(20,7))
sns.distplot(pbg['walkDistance'], bins=10, color='#8B668B')
plt.show()
pbg[pbg['walkDistance'] >= 10000].shape
pbg[pbg['walkDistance'] >= 10000][['walkDistance']].head()
删除行走距离超过10000的数据,行走距离超过10000显然是不可能的
pbg.drop(pbg[pbg['walkDistance'] >= 10000].index, inplace=True)
# 再次检查
pbg[pbg['walkDistance'] >= 10000].shape
# 驾车行驶距离处理
plt.figure(figsize=(20,7))
sns.distplot(pbg['rideDistance'], bins=10, color='#8B668B')
plt.show()
pbg[pbg['rideDistance'] >= 20000].shape
pbg[pbg['rideDistance'] >= 20000][['rideDistance']].head()
删除驾车距离超过20000的数据,同样驾车距离超过20000显然是不可能的
pbg.drop(pbg[pbg['rideDistance'] >= 20000].index, inplace=True)
# 再次检查
pbg[pbg['rideDistance'] >= 20000].shape
同上处理游泳距离处理,删除游泳距离超过20000的数据,游泳距离超过20000显然也是不可能的
pbg[pbg['swimDistance'] >= 2000].shape
pbg[pbg['swimDistance'] >= 2000][['swimDistance']].head()
pbg.drop(pbg[pbg['swimDistance'] >= 2000].index, inplace=True)
pbg[pbg['rideDistance'] >= 20000].shape
plt.figure(figsize=(20,7))
sns.distplot(pbg['weaponsAcquired'], bins=100, color='#8B668B')
plt.show()
pbg[pbg['weaponsAcquired'] >= 80].shape
pbg[pbg['weaponsAcquired'] >= 80][['weaponsAcquired']].head()
pbg.drop(pbg[pbg['weaponsAcquired'] >= 80].index, inplace=True)
# 再次检查
pbg[pbg['weaponsAcquired'] >= 80].shape
plt.figure(figsize=(20, 7))
sns.distplot(pbg['heals'], bins=10, color='#8B668B')
plt.show()
pbg[pbg['heals'] >= 40].shape
pbg[pbg['heals'] >= 40][["heals"]].head()
pbg.drop(pbg[pbg['heals'] >= 40].index, inplace=True)
pbg[pbg['heals'] >= 40].shape
3. 数据分析及可视化¶
3.1. 变量的基本描述¶
id
、matchId
、groupId
的区别
前面给出了数据描述,matchId是该场比赛的Id,groupId是所处小组的Id
pbg[pbg['groupId']=='4d4b580de459be']
len(pbg[pbg['matchId']=='a10357fd1a4a91'])
考虑一下上面的例子。在这两种情况下,Id不同,但groupId和matchId是相同的。这也说明了,Id为7f96b2f878858a的人A和Id为7516514fbd1091的人
B是朋友,并有一个团队(groupId),然后他们完成了相同的比赛,因此可以猜想他们是使用相同的matchId进入游戏。
temp = pbg[pbg['matchId']=='a10357fd1a4a91']['groupId'].value_counts().sort_values(ascending=False)
print("Total number of states : ",len(temp))
def get_color(c):
if(c > 7):
return 'rgb(1,15,139)'
elif(c > 6):
return 'rgb(10,77,131)'
elif(c > 4):
return 'rgb(49,54,149)'
elif(c > 3):
return 'rgb(69,117,180)'
elif(c > 2):
return 'rgb(171,217,233)'
elif(c > 1):
return 'rgb(253,174,97)'
else:
return 'rgb(254,224,144)'
trace = go.Bar(
x = temp.index,
y = (temp),
marker_color=[get_color(c) for c in temp],
marker=dict(line=dict(color='black', width=2), opacity=0.75)
)
data = [trace]
layout = go.Layout(
title = "GroupId of Match Id: a10357fd1a4a91",
xaxis=dict(
title='groupId',
tickfont=dict(
size=14,
color='rgb(107, 107, 107)'
)
),
yaxis=dict(
title='Count of groupId of type of MatchId a10357fd1a4a91',
titlefont=dict(
size=16,
color='rgb(107, 107, 107)'
),
tickfont=dict(
size=14,
color='rgb(107, 107, 107)'
)
),
bargap=0.2,
bargroupgap=0.1, paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor="rgb(243, 243, 243)"
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
可以注意到,你可以看到一些奇怪的值得计数。团队成员最多有四个人,这里因为我不知道超过四个人意味着什么所以看了看游戏评论,有游戏玩家评论到无论
在何种模式下,一组玩家的数量通常都会超过预期的最大数量。例如,在matchType == 'squad'下,你可能有超过4个人在一个小组中,这是由于在游戏中断
开连接造成的。当断开连接发生时,多个组的玩家存储在API的数据库中,最终位置相同。这样做的结果是,当我从最终位置创建groupId特性时,会发现有太
多的组。所以可以将groupId理解为“最终位置相同的玩家”,而不是“绝对在一起玩的玩家”。
assists
的数据描述熟悉这个游戏的玩家知道,这个游戏可以简单概括为,拿起你的武器,四处走动,杀死敌人,活到最后。所以这里对一些变量进行详细的数据描述:
assists:助攻的意思是我不杀死敌人,而是帮助杀死敌人。所以当你关注该变量时,也有一个杀死。换句话说,如果我杀死了敌人 kill + 1
。但是如果我
不是杀死敌人而是帮助杀死敌人 assists + 1
temp = pbg['assists'].value_counts().sort_values(ascending=False)
print("Total number of states : ",len(temp))
trace = go.Bar(
x = temp.index,
y = (temp),
marker=dict(color='crimson', line=dict(color='black', width=1.5), opacity=0.75)
)
data = [trace]
layout = go.Layout(
title = "",
xaxis=dict(
title='assists',
tickfont=dict(
size=14,
color='rgb(107, 107, 107)'
)
),
yaxis=dict(
title='Count of assists',
titlefont=dict(
size=16,
color='rgb(107, 107, 107)'
),
tickfont=dict(
size=14,
color='rgb(107, 107, 107)'
)
),
bargap=0.2,
bargroupgap=0.1, paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor="rgb(243, 243, 243)"
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
从上图可以看出,助攻数增加玩家数减少
3.2. 如何取得胜利——直接相关因素(e.g. 杀敌数和总伤害等)¶
plt.figure(figsize=(30, 20))
sns.heatmap(pbg.corr(), annot=True)
plt.show()
显然从热力图中我们可以看到颜色越深代表代表两个属性之间的相关性程度越高,比如很好理解的 杀敌数(kills) 和 连续杀敌数(killStreaks)
两个属性与 本场杀敌排行(killPlace) 之间的相关性程度分别为0.73和0.81;同时 Elo排名(RankPoints) 和 **胜率Elo排名
(winPoints)** 的相关性高达0.99。
kills
相关的变量接下来我们思考杀敌数与胜利的关系:它们是相互依存的吗?
我们从以下点进行分析:
1、杀敌数越多才会赢吗?
2、伤害与杀敌数成比例吗?
3、更多的伤害意味着更好的胜利吗?
pw = pbg[pbg['winPlacePerc'] == 1]
pl = pbg[pbg['winPlacePerc'] == 0]
trace = go.Histogram(x=pw.kills,
marker=dict(color="crimson", line=dict(color='black', width=2)),
opacity=0.75)
layout = go.Layout(title='NO. OF MATCHES WON V/S NO. OF KILLS',
xaxis=dict(
title='WON'
),
yaxis=dict(
title='Count'
),
bargap=0.2,
bargroupgap=0.1, paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor="rgb(243, 243, 243)")
fig = go.Figure(data=[trace], layout=layout)
iplot(fig)
trace = go.Histogram(x=pl.kills,
marker=dict(color="crimson", line=dict(color='black', width=2)),
opacity=0.75)
layout = go.Layout(title='NO. OF MATCHES LOST V/S NO. OF KILLS',
xaxis=dict(
title='WON'
),
yaxis=dict(
title='Count'
),
bargap=0.2,
bargroupgap=0.1, paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor="rgb(243, 243, 243)")
fig = go.Figure(data=[trace], layout=layout)
iplot(fig)
我们得出以下结论:
1、大部分的比赛胜利百分比有非常低的杀死计数(0-3)
2、大部分的比赛失败百分比是杀死计数==0
然而,由于在整个比赛中都没有杀死对手,所以输掉比赛比赢下比赛更常见
随着杀敌数次数的增加,我们看到,我们需要一个像样的杀敌数来赢得一场比赛(杀死大于3个),即杀死<=2更容易失败。 所以,我们可以得出否定的结论。杀敌数量与赢/输有很低但很明确的相关性。杀死<=2的玩家更容易输掉比赛。
下面我们主要关注一下,与kills
相关的变量headshotKills
killStreaks
longestKill
roadKills
teamKills
的情况
# Related variables with kills
temp1 = pbg['headshotKills'].value_counts().sort_values(ascending=False)
temp2 = pbg['killStreaks'].value_counts().sort_values(ascending=False)
temp3 = pbg['longestKill'].value_counts().sort_values(ascending=False)
temp4 = pbg['roadKills'].value_counts().sort_values(ascending=False)
temp5 = pbg['teamKills'].value_counts().sort_values(ascending=False)
temp6 = pbg['kills'].value_counts().sort_values(ascending=False)
trace1 = go.Scatter(x = temp1.index, y = (temp1), mode = "markers", name = "headshotKills",
marker = dict(color='rgba(28, 149, 249, 0.8)', size=8))
trace2 = go.Scatter(x = temp2.index, y = (temp2), mode = "markers", name = "killStreaks",
marker = dict(color='rgba(249, 94, 28, 0.8)', size=8))
trace3 = go.Scatter(x = temp3.index, y = (temp3), mode = "markers", name = "longestKill",
marker = dict(color='rgba(150, 26, 80, 0.8)', size=8))
trace4 = go.Scatter(x = temp4.index, y = (temp4), mode = "markers", name = "roadKills",
marker = dict(color='lime', size=8))
trace5 = go.Scatter(x = temp5.index, y = (temp5), mode = "markers", name = "teamKills",
marker = dict(color='crimson', size=8))
trace6 = go.Scatter(x = temp6.index, y = (temp6), mode = "markers", name = "kills",
marker = dict(color='rgb(188,145,202)', size=8))
data = [trace1, trace2, trace3, trace4, trace5, trace6]
layout = dict(title = 'Related variables with kills',
xaxis = dict(title='Related variables with kills', ticklen=5, zeroline=False, zerolinewidth=1, gridcolor="white"),
yaxis = dict(title='Count of Related variables', ticklen= 5, zeroline= False, zerolinewidth=1, gridcolor="white",),
paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor='rgb(243, 243, 243)',
)
fig = dict(data=data, layout=layout)
iplot(fig)
从上图可以看出,与kills
相类似的几个设计杀敌数的属性与kills的分布是大致相同的,杀敌数(爆头、连续杀敌、远距离杀敌、驾车杀敌数、团队杀敌
数)越多的玩家数量越少。
下面我们主要关注一下,与kills
相关的变量damageDealt
damageDealt
的情况,重点关注杀敌数与胜率和总伤害之间的关系
# 由于数据集数据量大,这里随机从数据集中取1000个数据
df = pbg.sample(n=1000,random_state=123,axis=0)
df.head()
df2 = df.loc[:,["damageDealt", "kills", "winPlacePerc"]]
df2["index"] = np.arange(1, len(df)+1)
# 散点图矩阵
fig = ff.create_scatterplotmatrix(df2, diag='box', index='index', colormap='YlOrRd',
colormap_type='seq', height=1000, width=1200)
iplot(fig)
上面的散点图矩阵中可以看到,kills
和damageDealt
(杀敌数和总伤害)的散点图(第一列第二个图)中,两个变量基本上呈现线性关系,即杀敌数越
多,总伤害越高;而kills
、damageDealt
与winPlacePerc
的关系就不在是我们常规上认为的,杀敌数和总伤害越高则胜率越高,但是若胜率高相对
应总伤害和杀敌数就会高,这也许与前面提到的团队协作有关(毕竟这个游戏在个别形式上还是一个团队游戏)。
wins_modr_best = []
for val in list(pbg.winPlacePerc.unique()):
if val > 0.45:
wins_modr_best.append(val)
else:
continue
print(pbg.winPlacePerc.nunique())
print(pd.Series(wins_modr_best).nunique())
winner = pbg[pbg.winPlacePerc==1]
loser = pbg[pbg.winPlacePerc==0]
fig = go.Figure(data=[go.Pie(labels=['Won','Lost','Drew/Others'],
values=[winner.shape[0], loser.shape[0],
pbg.shape[0] - (winner.shape[0] + loser.shape[0])])])
fig.update_traces(hoverinfo='label+percent', textinfo='value', textfont_size=20,
marker=dict(line=dict(color='#000000', width=2)))
iplot(fig)
鼠标hover上面的饼图可以看到,胜率100%的玩家数为127313,仅占2.86%;胜率0%的玩家也就是所有比赛都失败的玩家数位220313,占4.96%;其余胜率的
玩家占比最高,为92.2%,这样看来大多数玩家还是普通人,胜率100%的玩家确实是少数精英玩家。
match_types = list(pbg.matchType.value_counts().values)
labels = list(pbg.matchType.value_counts().index)
# Plot a pie chart to show which game type is more popular
fig = go.Figure(data=[go.Pie(labels=labels, values=match_types, hole=.3)])
fig.update_traces(hoverinfo='label+percent', textinfo='value', textfont_size=20,
marker=dict(line=dict(color='#000000', width=2)))
iplot(fig)
通过上图所以我们可以得出结论,squad-fpp(四人组队第一视角)是最受欢迎的比赛类型,其次是duo-fpp(两人组队第一视角)。normal-duo是最不常用的游戏类型。
for_win = list(pw.matchType.value_counts().values)
for_loss = list(pl.matchType.value_counts().values)
fig = go.Figure(data=[
go.Bar(name='WON', marker=dict(color='rgb(69,117,180)'), x=list(pw.matchType.values), y=for_win),
go.Bar(name='LOST', marker=dict(color='rgba(150, 26, 80, 0.8)'), x=list(pl.matchType.values), y=for_loss)
])
# Change the bar mode
fig.update_layout(barmode='group')
iplot(fig)
通过观察,可以得到结论:
1、虽然squad-fpp是最受欢迎的比赛类型,但它也有更多的损失。
2、duo-fpp是第二受欢迎的比赛类型,没有出现比赛失败的情况。
3、squad是第三大最受欢迎的比赛类型,但是相比于squad-fpp赢得比赛的记录比输了的记录更多。
4、duo是一种不受欢迎的比赛类型,事实证明这是合理的,因为大部分都带来了失败。
kills
和wins
的关系sns.jointplot(x="winPlacePerc", y="kills", data=pbg, height=8, ratio=3, color="#8B668B")
plt.show()
通过观察,可以得到结论:
1、winPlacePerc和kills是中度相关的。因此,从之前的热力图中获得的信息是合理的。
2、为了获得胜利,获取一些技能是绝对必要的(阈值=3),而不是躺下来掩盖和隐藏。
3、不能以较低的杀戮为目标。当然更高的杀戮确实证明了玩家的技能并保证了更高的获胜机会。
如果胜利者的杀敌数很低时,那么应该关注伤害来得分吗?
1、找到杀敌数的伤害指标。
2、找出比赛失败的伤害指标。
plt.figure(figsize=(20, 7))
sns.distplot(pw['damageDealt'], color="#8B668B")
sns.distplot(pl['damageDealt'], color="#7D9EC0")
plt.legend(['WON','LOST'])
plt.tick_params(labelsize=15)
plt.show()
通过观察,可以得到结论:
1、就最小的总伤害而言,输掉一场比赛的可能性要比赢得一场比赛的可能性高。
2、对于赢了的比赛,最大的总伤害是在3400左右。
3、对于输了的比赛,最大的总伤害发生在2700左右。
4、如果玩家总伤害少,他们更容易输掉比赛。
所以,我们可以得出这样的结论,伤害的数量与获胜的关系很低。
damageDealt
和winPlacePerc
的关系sns.jointplot(x="winPlacePerc", y="damageDealt", data=pbg, height=8, ratio=3, color="#8B668B")
plt.show()
通过观察,可以得到结论:
1、winPlacePerc
和damageDealt
是中度相关的。因此,从热图中获得的信息是合理的。
2、为了赢得比赛,对敌人造成伤害是绝对必要的,因为这是PUBG游戏中最重要的得分点之一。它揭示了一个玩家的技能。
3、如果玩家没有造成足够的伤害来获得分数,他们更容易输掉游戏。
那么有多少玩家在没有杀戮和伤害的情况下赢得了他们的游戏?
隐藏(在游戏中藏好直至游戏结束)仍然是传统的damage-kill-cover策略的有效对抗方法吗?
from plotly.subplots import make_subplots
# Percentage of zero kills winners
colors1 = ['rgb(158,202,225)','darksalmon']
colors2 = ['rgb(188,145,202)', 'darksalmon']
fig = make_subplots(
rows=1, cols=2,
specs=[[{"type": "domain"}, {"type": "domain"}]]
)
fig.add_trace(go.Pie(labels=['ZERO KILLS', 'OTHERS'],
values=[pw[pw.kills==0].shape[0], (pw.shape[0]-pw[pw.kills==0].shape[0])],
marker=dict(colors=colors1, line=dict(color='#000000', width=2))), row=1, col=1)
fig.add_trace(go.Pie(labels=['ZERO DAMAGE', 'OTHERS'],
values=[pw[pw.damageDealt==0].shape[0], (pw.shape[0]-pw[pw.damageDealt==0].shape[0])],
marker=dict(colors=colors2, line=dict(color='#000000', width=2))), row=1, col=2)
fig.update_layout(height=500, showlegend=True)
fig.show()
通过观察,可以得到结论:
1、只有13.1%的玩家在零击的情况下获得胜利。
2、只有3.74%的玩家以零伤害获得胜利。
因此,最好是遵循游戏规则并尝试通过增加杀戮和造成足够的伤害来确保胜利。
3.3. 如何取得胜利——间接相关因素(辅助物品和行动方式)¶
跑步、驾驶和游泳的比较:移动/交通方式是否影响获胜概率?应该避免哪一种?
找出胜利和失败的行走距离分布:
plt.figure(figsize=(20, 7))
sns.distplot(pw['walkDistance'], color="#8B668B")
sns.distplot(pl['walkDistance'], color="#7D9EC0")
plt.legend(['WON','LOST'])
plt.show()
通过观察,可以得到结论:
1、当walkDistance = 0
时丢失最大匹配项。这意味着两件事:
2、显然,最大获胜记录的步行距离大于阈值(步行距离 > 2000),显然是大于零。
3、上图数据显示开始时胜利和失败步行距离都在增加。在失败逐渐消失的时候,胜利开始达到顶峰。所以,随着步行距离的增加:
所以,我们可以得出结论,步行距离是决定胜负的一个很好的衡量标准。超过一定的门槛(> 2000),赢得游戏的机会增加。在游戏一开始就保持空闲/隐藏不是一个好的策略,并且会让玩家很容易被击倒。
sns.jointplot(x='winPlacePerc', y='walkDistance', data=pbg, height=8, ratio=3, color="#8B668B")
plt.show()
通过观察,可以得到结论:
1、winPlacePerc
和walkDistance
是高度相关的。不难看出,前面热图(关系度高达0.81)中获得的信息是合理的。
2、为了赢得胜利,必须通过移动和行走/奔跑来参与杀敌、造成伤害和获得倒下的敌人的武器装备。
3、如果玩家不走一步或走短距离(非常接近于零),他们更容易输掉比赛。
plt.figure(figsize=(20, 7))
sns.distplot(pw['rideDistance'], kde=False, color="#8B668B")
sns.distplot(pl['rideDistance'], kde=False, color='#7D9EC0')
plt.legend(['WON', 'LOST'])
plt.show()
通过观察,可以得到结论:
1、大多数优胜者的乘车距离为零。
2、从递减趋势看,赢车次数明显随乘程的增加而减少。这是意料之中的,因为:
因此,我们可以得出结论,骑行距离并不是决定胜负的好方法。胜利的趋势随着骑距的增加而减少。
sns.jointplot(x='winPlacePerc', y='rideDistance', data=pbg, height=8, ratio=3, color="#8B668B")
plt.show()
显然winPlacePerc
和rideDistance
呈低相关。因此,从热图(0.34)中获得的信息是合理的。
plt.figure(figsize=(20, 7))
sns.distplot(pw['swimDistance'], kde=False, color="#8B668B")
sns.distplot(pl['swimDistance'], kde=False, color='#7D9EC0')
plt.legend(['WON','LOST'])
plt.show()
通过观察,可以得到结论:
1、几乎没有人游泳。
2、即使是那些游泳的人也更容易失败而不是成功。
3、获胜的机会随着距离的增加而减少。
所以,游泳并不是决定胜负的好因素,并且获胜的机会随着距离的增加而减少。
sns.jointplot(x='winPlacePerc', y='swimDistance', data=pbg, height=8, ratio=3, color="#8B668B")
plt.show()
正如预期的一样swimDistance
和winPlacePerc
有较差的相关性,热图数据(0.15)是合理的。
from plotly.subplots import make_subplots
# Percentage of zero walk distance
colors1 = ['rgb(158,202,225)','darksalmon']
colors2 = ['rgb(188,145,202)', 'darksalmon']
colors3 = ['rgb(247,173,13)','darksalmon']
fig = make_subplots(
rows=1, cols=3,
specs=[[{"type": "domain"}, {"type": "domain"}, {"type": "domain"}]]
)
fig.add_trace(go.Pie(labels=['ZERO WALK DISTANCE', 'OTHERWISE'],
values=[pw[pw.walkDistance==0].shape[0], (pw.shape[0]-pw[pw.walkDistance==0].shape[0])],
marker=dict(colors=colors1, line=dict(color='#000000', width=2))),
row=1, col=1)
fig.add_trace(go.Pie(labels=['ZERO RIDE DISTNACE','OTHERWISE'],
values=[pw[pw.rideDistance==0].shape[0],(pw.shape[0]-pw[pw.rideDistance==0].shape[0])],
marker=dict(colors=colors2, line=dict(color='#000000', width=2))),
row=1, col=2)
fig.add_trace(go.Pie(labels=['ZERO SWIM','OTHERWISE'],
values=[pw[pw.swimDistance==0].shape[0], (pw.shape[0]-pw[pw.swimDistance==0].shape[0])],
marker=dict(colors=colors3, line=dict(color='#000000', width=2)), opacity=0.75),
row=1, col=3)
fig.update_layout(height=500, showlegend=True)
fig.show()
通过观察,可以得到结论:
1、大多数胜利记录在 步行距离> 0 (99.4%)。
2、几乎一半的比赛胜利记录在 驾车距离= 0时(49.4%)。
3、当 游泳距离= 0(84.9%)时,会记录相当数量的胜利。
所以最好的策略是walk
>ride
>>swim
Heals
和Boosts
对胜率有怎样的影响plt.figure(figsize=(20, 10))
sns.pointplot(x='heals',y='winPlacePerc', data=pbg, color='crimson', alpha=0.8)
sns.pointplot(x='boosts',y='winPlacePerc', data=pbg, color='#7D9EC0', alpha=0.8)
plt.legend(['HEALS','BOOSTS'])
plt.xlabel('NUMBER OF HEALING/BOOSTING ITEMS USED', fontsize=12)
plt.ylabel('Win Percentage', fontsize=12)
plt.title('HEALS V/S BOOSTS', fontsize=20)
plt.grid()
plt.show()
通过观察,可以得到结论:
1、boost几乎呈现上升趋势。随着boost的增加,winPlacePerc也普遍增加,它也确实是获得更多得分的方式。
2、然而,heals却不是这样。它们表现出随机的波动,所以我们不确定它与胜利的确切关系,不过根据热力图可以看到其与胜率的关系度为0.43,不算太差。
weaponsAcquired
会对胜率有影响吗plt.figure(figsize=(20,10))
sns.pointplot(x='weaponsAcquired', y='winPlacePerc', data=pbg, color='crimson',alpha=0.8)
plt.xlabel('NUMBER OF WEAPONS ACQUIRED', fontsize=12)
plt.ylabel('Win Percentage', fontsize=12)
plt.title('Weapons Acquired', fontsize=20)
plt.grid()
plt.show()
可以看到上图数据非常的波动
通过观察,可以得到结论:
1、weaponsAcquired
与winPlacePerc
有低-中度的相关性。虽然它不是一个重要的影响因素,但却是一个确实存在影响的因素。
3.4. 最终结论¶
PUBG是一款风靡全球的多人游戏。每一个PUBG玩家都会以赢得一场胜利(用PUBG行话来说就是“大吉大利,今晚吃鸡”)带来的巨大的、无与伦比的快乐为目标。它让人放松,也让人上瘾。所以经过上面的分析和可视化内容,我总结了一些影响比赛胜利的最重要的因素,这也是影响预测玩家排名的重要因素,毕竟胜率将会直接影响玩家的排名。
在PUBG不要做什么?
1、除非你非常确定你的沟通技巧,否则不要选择Squad-FPP
的比赛。分析表明,选择Squad-FPP
的玩家往往会输掉更多的比赛。他们背后的一个主要原因可能是沟通错误,毕竟Squad-FPP
中的玩家来自世界各地。
2、不要选择duo
比赛,因为根据分析显示,没有一个被观察的玩家在双人比赛中获胜。
3、永远不要只杀一个人。因为根据分析显示,杀敌少于2次的玩家会失败。
4、不要从游戏一开始就躲起来。因为根据分析表明,零步行距离的人失败率更高。
5、尽量不要驾车,因为你有被公路撞死的危险。另外不要游泳,分析表明,他们对胜利的起不到太大的作用。
在PUBG中要怎么做才能赢?
1、选择Squad
比赛。因为根据分析显示,他们是第三大最受欢迎的比赛类型,同时比Squad
更能保证胜利。这是因为,在squad
比赛中,你可以选择自己组队而不是随机组队,如果你和朋友一起比赛,交流就会更容易。
2、选择Duo-FPP
配对,因为分析表明它们的胜率最高。
3、在不被枪杀的情况下尽可能多地杀人,分析表明,杀敌的数量和获胜的比例是中度相关的。如果你觉得情况安全,就有策略地杀人。比如,如果你有一把散弹枪,那就用散弹枪和别人打架,而不是用高级别的武器。你杀的人越多,你收集的武器装备就越多)
4、只要你没有处于危险之中,分析表明,施加更多的伤害可以展示一个玩家的技能,通常可以确保一个好的分数。
5、比起游泳或驾车,我更推荐步行或跑步。数据告诉我们,取得胜利的玩家都伴随着一定步行距离。
6、沉迷于拾起更多的boosts
和heals
,因为这两者都与胜利紧密相关。
7、明智地选择战斗,与你确信你能开枪击倒的敌人战斗。
4. PUBG玩家排名预测¶
4.1. 线性回归预测模型¶
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import GridSearchCV
import os
pubg_train=pd.read_csv( '/home/mqli/data-mining/final-work/train_V2.csv')
pubg_train
#将所有缺失值填充为0
pubg_train=pubg_train.fillna(0)
#将matchType列数据转化为适合模型的数据,相当于一个标准化处理
Le=LabelEncoder()
pubg_train['matchType']=Le.fit_transform(pubg_train['matchType'])
from sklearn.model_selection import train_test_split
X_train=pubg_train.drop(['winPlacePerc','Id','matchId','groupId'],axis=1)
Y_train=np.array(pubg_train['winPlacePerc'])
random_seed=1
X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size = 0.1, random_state=random_seed)
#创建线性回归模型
model=LinearRegression()
#训练线性回归模型
model=model.fit(X_train,Y_train)
#用线性回归模型进行预测
pred=model.predict(X_train)
from sklearn.metrics import mean_absolute_error
print('MAE of train: ', mean_absolute_error(pred, Y_train))
print('MAE of val: ', mean_absolute_error(model.predict(X_val), Y_val))
pred_val = model.predict(X_val)
test_df = X_val
test_df['winPlacePerc'] = Y_val
test_df['pred_winPlacePerc'] = pred_val
test_df.to_csv('/home/mqli/data-mining/final-work/test_pred_lr.csv')
4.2. 随机森林预测模型¶
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import preprocessing
from keras.models import Sequential
from keras.layers import Dense, Activation
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error,mean_absolute_error
from sklearn.ensemble import RandomForestRegressor
df_train = pd.read_csv( '/home/mqli/data-mining/final-work/train_V2.csv')
df_train = df_train[df_train['maxPlace'] > 1]
df_train
将没有用的数据信息以及标签数据删除得到特征表,并将最终排名作为标签。
target = 'winPlacePerc'
features = list(df_train.columns)
features.remove("Id")
features.remove("matchId")
features.remove("groupId")
features.remove("matchType")
y_train = np.array(df_train[target])
features.remove(target)
x_train = df_train[features]
将数据集按9:1的比例划分为训练集和测试集
from sklearn.model_selection import train_test_split
random_seed = 1
X_train, X_val, Y_train, Y_val = train_test_split(x_train, y_train, test_size = 0.1, random_state=random_seed)
model = RandomForestRegressor(n_estimators=70, min_samples_leaf=3, max_features=0.5,n_jobs=-1)
model.fit(X_train, Y_train)
pred = model.predict(X_train)
pred_val = model.predict(X_val)
print('mae train: ', mean_absolute_error(pred, Y_train))
print('mae val: ', mean_absolute_error(pred_val, Y_val))
test_df = X_val
test_df['winPlacePerc'] = Y_val
test_df['pred_winPlacePerc'] = pred_val
test_df.to_csv('/home/mqli/data-mining/final-work/test_pred_rf.csv')
4.3. MLP预测模型¶
import numpy as np
import pandas as pd
import os
import warnings
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error,mean_absolute_error
df = pd.read_csv( './train_V2.csv')
df = df.dropna()
print(df.shape)
target = 'winPlacePerc'
features = list(df.columns)
features.remove("Id")
features.remove("matchId")
features.remove("groupId")
features.remove("matchType")
y = np.array(df[target])
features.remove(target)
x = df[features]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.1, random_state=0)
print(x_test.shape, x_train.shape, y_train.shape)
from keras import models
from keras import layers
from keras import Sequential
from keras.layers import Dense, Dropout, Input
def build_model():
model = Sequential()
model.add(Dense(80,input_dim=x_train.shape[1],activation='relu'))
model.add(Dense(160,activation='relu'))
model.add(Dense(320,activation='relu'))
model.add(Dropout(0.1))
model.add(Dense(160,activation='relu'))
model.add(Dense(80,activation='relu'))
model.add(Dense(40,activation='relu'))
model.add(Dense(20,activation='relu'))
model.add(Dense(1,activation='sigmoid'))
model.summary()
model.compile(optimizer='adam', loss='mse', metrics=['mae'])
return model
from keras import backend as K
# Some memory clean-up
K.clear_session()
num_epochs = 100
model = build_model()
history = model.fit(x_train, y_train, validation_split=0.2, epochs=num_epochs, batch_size=10000, verbose=1)
100轮训练后,模型MAE收敛于0.06左右,Loss收敛于0.01,效果还是可以的。
import matplotlib.pyplot as plt
def plot_history(history):
plt.plot(history.history['mean_absolute_error'])
plt.plot(history.history['val_mean_absolute_error'])
plt.title('model MAE')
plt.xlabel('epoch')
plt.ylabel('MAE')
plt.legend(['mean_absolute_error', 'val_mean_absolute_error'])
plt.show()
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['loss', 'val_loss'])
plt.show()
plot_history(history)
pred = model.predict(x_test)
print('mae train: ', mean_absolute_error(model.predict(x_train), y_train))
print('mae test: ', mean_absolute_error(pred, y_test))
我们将三个模型的结果综合到一起进行对比分析,可以看出,三个模型中随机森林的效果是最好的,其次是MLP,线性回归效果最差。
在实验过程中,随机森林的训练时间也比MLP要短。
(注:这里为了显示美观,将数值保留至小数点后5位)
name_list = ['LR', 'RF', 'MLP']
y_train = [0.09199775645256723, 0.03297402648441849, 0.0603489711746765]
y_text = [0.09222107817896709, 0.058151005937161174, 0.06065848714850899]
x =list(range(len(y_train)))
total_width, n = 0.7, 2
width = total_width / n
plt.figure(figsize=(8, 6))
plt.bar(x, y_train, width=width, label='train', color='steelblue', alpha=0.8)
for x1, yy in zip(x, y_train):
plt.text(x1, yy, str(round(yy, 5)), ha='center', va='bottom', fontsize=10, rotation=0)
for i in range(len(x)):
x[i] = x[i] + width
plt.bar(x, y_text, width=width, label='test', tick_label=name_list, fc='r', alpha=0.8)
for x1, yy in zip(x, y_text):
plt.text(x1, yy, str(round(yy, 5)), ha='center', va='bottom', fontsize=10, rotation=0)
plt.title("MAE")
plt.legend()
plt.show()
5. PUBG玩家排名预测结果分析及可视化¶
import pandas as pd
import numpy as np
import os
import matplotlib.pyplot as plt
from sklearn import metrics
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
读取测试集上的预测结果文件
data_lr = pd.read_csv("test_pred_lr.csv")
data_mlp = pd.read_csv("test_pred_mlp.csv")
data_rf = pd.read_csv("test_pred_rf.csv")
5.1. 线性回归模型结果分析及可视化¶
首先选取了4个评价指标MSE,RMSE,MAE以及SMPAE来对模型进行评价,评价指标如下所示
def mape(y_true, y_pred):
return np.mean(np.abs((y_pred - y_true) / y_true)) * 100
def smape(y_true, y_pred):
return 2.0 * np.mean(np.abs(y_pred - y_true) / (np.abs(y_pred) + np.abs(y_true))) * 100
def mse(y_true,y_pred):
return metrics.mean_squared_error(y_true, y_pred)
def rmse(y_true,y_pred):
return np.sqrt(metrics.mean_squared_error(y_true, y_pred))
def mae(y_true,y_pred):
return metrics.mean_absolute_error(y_true, y_pred)
y_true_lr = data_lr['winPlacePerc']
y_pred_lr = data_lr['pred_winPlacePerc']
mse_lr = mse(y_true_lr,y_pred_lr)
rmse_lr = rmse(y_true_lr,y_pred_lr)
mae_lr = mae(y_true_lr,y_pred_lr)
smape_lr = smape(y_true_lr,y_pred_lr)
print("MSE : ",mse_lr)
print("RMSE : ",rmse_lr)
print("MAE : ",mae_lr)
print("SMAPE : ",smape_lr)
从中可以看到,线性回归模型存在着一定误差,首先选取前100个数据将真实值和预测值绘制成折线图,观察真实值与预测值之间的差距,如下图所示
def zhe(y_true,y_pred,title):
t = np.arange(len(y_true[:100]))
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.plot(t, y_true[:100], "r-", linewidth=2, label="true")
plt.plot(t, y_pred[:100], "g-", linewidth=2, label="predict")
plt.title(title, fontsize=24)
plt.legend(loc="upper right")
plt.grid()
plt.show()
zhe(y_true_lr,y_pred_lr,"线性回归预测值与真实值的对比")
红色折线代表真实值,绿色折线代表预测值,从中可以发现,当真实值的胜率偏大时,预测值低于真实值,然而当真实值的胜率偏低时,预测值高于真实值,这说明当利用线性回归模型进行预测时,预测的数字偏于稳定。
def true_pred(y_true,y_pred,title):
plt.figure("scatter")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.title(title, fontsize=24)
x = np.arange(0, 1,0.01)
y = x
plt.plot(x, y)
plt.xlabel("真实值", fontsize=14)
plt.ylabel("预测值", fontsize=14)
plt.scatter(y_true, y_pred, color='green', label='Test')
plt.grid(linestyle=":")
plt.show()
true_pred(y_true_lr,y_pred_lr,"线性回归预测值与真实值散点图")
i = 0
lis = list(data_lr.columns)
data_lr_values = data_lr.values
error_data_lr = []
for tmp in data_lr_values:
if tmp[len(tmp)-1] >1 or tmp[len(tmp)-1]<0:
error_data_lr.append(tmp)
print(len(error_data_lr))
从散点图中可以发现,线性回归模型的预测值并没有很好的拟合真实值,甚至与概率学相违背,即在预测时出现了预测值大于1以及小于0的情况。通过统计发现总共有18292个离奇的预测值,将这些数据单拿出来,观察真实值和预测值之间相关系数有何不同?绘制出相关系数的热力图如下所示
plt.figure(figsize=(200, 300))
xLabel = lis
yLabel = lis
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_yticks(range(len(yLabel)))
ax.set_yticklabels(yLabel)
ax.set_xticks(range(len(xLabel)))
ax.set_xticklabels(xLabel)
im = ax.imshow(pd.DataFrame(error_data_lr).corr(), cmap=plt.cm.hot_r)
plt.colorbar(im)
plt.xticks(rotation=270)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.title("真实值和预测值与其他特征的相关系数热力图")
plt.show()
通过观察发现,主要集中在boosts(饮料数量),kills(杀敌数),killStreak(连续杀敌数.)以及demageDealt(总伤害).这4个特征的真实值与预测值之间的相关系数存在着较大差异。在预测真实值中观察相关系数会发现,winPlacePerc(获胜概率)与walkDistance(步行距离),weaponsAcquired(武器收集数量),boosts(饮料数量)相关度最高。这说明在线性回归模型学习的过程中,对boosts(饮料数量)的学习不充分,导致结果出现了异常情况。
e_boosts = []
e_y_true = []
e_y_pred = []
for tmp in error_data_lr:
e_boosts.append(tmp[1])
e_y_pred.append(tmp[len(tmp)-1])
e_y_true.append(tmp[len(tmp)-2])
plt.scatter(e_boosts,e_y_true, s=200, label = '$true$', c = 'blue', marker='.', alpha = None, edgecolors= 'white')
plt.scatter(e_boosts,e_y_pred , s=200, label = '$predict$', c = 'red', marker='.', alpha = None, edgecolors= 'white')
plt.legend()
在图中可以观察到,大部分的预测值都是高于真实值的,从图中可以看到在boosts为0的时候,预测有很大的偏差。
plt.figure(figsize=(15,10))
plt.scatter(e_y_true,e_y_pred,alpha=0.5,s=200,c=e_boosts)
plt.xlabel("真实值", fontsize=14)
plt.ylabel("预测值", fontsize=14)
plt.colorbar()
从图中可以发现,当真实值为1时,boosts为6时出现了预测值小于0的情况,在真实值为0,预测值普遍低于0,同时boosts大多数为0,也就是说在线性回归模型中,较小的真实值和较大的真实值在预测时容易出现错误。
5.2. MLP预测模型结果分析及可视化¶
y_true_mlp = data_mlp['winPlacePerc']
y_pred_mlp = data_mlp['pred_winPlacePerc']
mse_mlp = mse(y_true_mlp,y_pred_mlp)
rmse_mlp = rmse(y_true_mlp,y_pred_mlp)
mae_mlp = mae(y_true_mlp,y_pred_mlp)
smape_mlp = smape(y_true_mlp,y_pred_mlp)
print("MSE : ",mse_mlp)
print("RMSE : ",rmse_mlp)
print("MAE : ",mae_mlp)
print("SMAPE : ",smape_mlp)
zhe(y_true_mlp,y_pred_mlp,"MLP模型预测值与真实值的对比")
通过观察可以发现,在折线图的对比中,MLP模型的真实值和预测值之间的差距明显比线性回归模型小很多,同时真实值往往会大于预测值。
true_pred(y_true_mlp,y_pred_mlp,"MLP模型预测值与真实值散点图")
在MLP模型的预测值和真实值的散点图中可以发现,它的分布像一个树叶,部分的数据有很好的预测结果,但是可以发现在真实值为1时,预测值有的结果接近于0,同时在真实值为0时,预测也存在接近于1的情况,我们选取所有真正值与预测值大于0.5的数据。
data_mlp_values = data_mlp.values
error_data_mlp = []
e_y_true_mlp = []
e_y_pred_mlp = []
for tmp in data_mlp_values:
if abs(tmp[len(tmp)-1]-tmp[len(tmp)-2])>0.5:
error_data_mlp.append(tmp)
e_y_true_mlp.append(tmp[len(tmp)-2])
e_y_pred_mlp.append(tmp[len(tmp)-1])
print(len(error_data_mlp))
通过统计发现,真实值与预测值出现较大偏差的数据总共有314个。
plt.figure(figsize=(7,5))
plt.scatter(e_y_true_mlp,e_y_pred_mlp,alpha=0.5,s=100)
plt.xlabel("真实值", fontsize=14)
plt.ylabel("预测值", fontsize=14)
在散点图中可以看到,主要在真实值接近于0和真实值接近于1的位置出现的预测失误最大。
5.3. 随机森林预测模型结果分析及可视化¶
y_true_rf = data_rf['winPlacePerc']
y_pred_rf = data_rf['pred_winPlacePerc']
mse_rf = mse(y_true_rf,y_pred_rf)
rmse_rf = rmse(y_true_rf,y_pred_rf)
mae_rf = mae(y_true_rf,y_pred_rf)
smape_rf = smape(y_true_rf,y_pred_rf)
print("MSE : ",mse_rf)
print("RMSE : ",rmse_rf)
print("MAE : ",mae_rf)
print("SMAPE : ",smape_rf)
通过选取三个评价指标可以发现,随机森林的在各个指标下取得了良好的结果
zhe(y_true_rf,y_pred_rf,"随机森林模型预测值与真实值的对比")
从图中可以发现,随机森林的预测值和真实值比较接近。
true_pred(y_true_rf,y_pred_rf,"随机森林模型预测值与真实值散点图")
在散点图中也可以观察到,在预测值和真实值的散点图中,相比比较于MLP,在真正值为0和1的部分,随机森林的效果比MLP要强,在之前的分析中探讨了三个模型MAE评价指标,这里又选取了其他3个指标,探讨随机森林模型是否真的为最优模型?
plt.figure(figsize=(15,5))
plt.subplot(131)
label_list = ['MLP', '随机森林','线性回归']
num_list1 = [mse_mlp,mse_rf,mse_lr]
x = range(len(num_list1))
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
rects1 = plt.bar(x =label_list , height=num_list1, width=0.4, alpha=0.8, color='red',label='MSE')
plt.xticks([index +0.0 for index in x], label_list)
plt.title("三个模型的MSE值")
plt.legend()
for rect in rects1:
height = rect.get_height()
plt.text(rect.get_x() + rect.get_width() / 2,height, str(round(height, 5)), ha="center", va="bottom")
plt.subplot(132)
label_list = ['MLP', '随机森林','线性回归']
num_list1 = [rmse_mlp,rmse_rf,rmse_lr]
x = range(len(num_list1))
rects1 = plt.bar(x =label_list , height=num_list1, width=0.4, alpha=0.8, color='green',label='RMSE')
plt.xticks([index +0.0 for index in x], label_list)
plt.title("三个模型的RMSE值")
plt.legend()
for rect in rects1:
height = rect.get_height()
plt.text(rect.get_x() + rect.get_width() / 2,height, str(round(height, 5)), ha="center", va="bottom")
# plt.subplot(223)
# label_list = ['MLP', '随机森林','线性回归']
# num_list1 = [mae_mlp,mae_rf,mae_lr]
# x = range(len(num_list1))
# rects1 = plt.bar(x =label_list , height=num_list1, width=0.4, alpha=0.8, color='yellow',label='MAE')
# plt.xticks([index +0.0 for index in x], label_list)
# plt.title("三个模型的MAE值")
# plt.legend()
# for rect in rects1:
# height = rect.get_height()
# plt.text(rect.get_x() + rect.get_width() / 2,height, str(round(height, 5)), ha="center", va="bottom")
plt.subplot(133)
label_list = ['MLP', '随机森林','线性回归']
num_list1 = [smape_mlp,smape_rf,smape_lr]
x = range(len(num_list1))
rects1 = plt.bar(x =label_list , height=num_list1, width=0.4, alpha=0.8, color='blue',label='SMAPE')
plt.xticks([index +0.0 for index in x], label_list)
plt.title("三个模型的SMAPE值")
plt.legend()
for rect in rects1:
height = rect.get_height()
plt.text(rect.get_x() + rect.get_width() / 2,height, str(round(height, 5)), ha="center", va="bottom")
通过MSE,RMSE,SMAPE以及在第4节提到的MAE指标,可以发现随机森林模型在这4个指标下都是最优的,这符合之前观察到的折线图和散点图,同时也发现虽然随机森林为最优,但其实MLP模型和随机森林模型的评价指标相差并不大,那么在极端情况下MLP模型和随机森林模型谁的更能准确预测呢?
data_rf_values = data_rf.values
error_data_rf = []
e_y_true_rf = []
e_y_pred_rf = []
for tmp in data_rf_values:
if abs(tmp[len(tmp)-1]-tmp[len(tmp)-2])>0.99:
error_data_rf.append(tmp)
e_y_true_rf.append(tmp[len(tmp)-2])
e_y_pred_rf.append(tmp[len(tmp)-1])
print(len(error_data_rf))
data_mlp_values = data_mlp.values
eerror_data_mlp = []
ee_y_true_mlp = []
ee_y_pred_mlp = []
for tmp in data_mlp_values:
if abs(tmp[len(tmp)-1]-tmp[len(tmp)-2])>0.99:
eerror_data_mlp.append(tmp)
ee_y_true_mlp.append(tmp[len(tmp)-2])
ee_y_pred_mlp.append(tmp[len(tmp)-1])
print(len(eerror_data_mlp))
label_list = ['MLP', 'RF']
num_list1 = [len(eerror_data_mlp), len(error_data_rf)]
x = range(len(num_list1))
rects1 = plt.bar(x =label_list , height=num_list1, width=0.4, alpha=0.8, color='red',label='数量')
plt.ylim(0, 7)
plt.xticks([index +0.0 for index in x], label_list)
plt.title("MLP和随机森林真实值和预测值的极端数量")
plt.legend()
for rect in rects1:
height = rect.get_height()
plt.text(rect.get_x() + rect.get_width() / 2, height+0.2, str(height), ha="center", va="bottom")
plt.show()
假定真实值和预测值的差值为0.99以上为极端预测值,可以发现MLP模型的极端数量6远远大于随机森林的预测数量2,虽然随机森林模型也存在着一定误差,但是相比较之下,随机森林模型最适合解决PUBG玩家排名预测问题。