网址来源:世界杯 学习赛数据集_数据集-阿里云天池
前提
该数据来源于阿里云天池,发布者已经将csv文件,各个字段均有一定的解释,详情见“网址来源”。这里不做过多的说明,若感兴趣也可以自行下载。
获取数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
plt.rcParams["font.sans-serif"] = ["SimHei"] # windows系统
plt.rcParams['axes.unicode_minus']=False #正常显示符号
%matplotlib inline
data1=pd.read_csv('downloads/160989/WorldCupsSummary.csv') #若要复制记得下载文档后更改地址
data2=pd.read_csv('downloads/160989/WorldCupMatches%20%282%29.csv')
data3=pd.read_csv('downloads/160989/WorldCupPlayers%20%281%29.csv')
1.summary文档数据分析
1.1.数据预处理
data1.head()
data1.info() #查看数据类型
data1.isnull().sum() #查看空值
#仅展示查看空值和前五行的结果
该数据源很干净,并且格式均正确,没有任何的空值
1.2.根据年份对其他指标的分析
plt.figure(figsize=(12,4))
plt.subplot(121)
# 绘制折线图
plt.plot(data1['Year'], data1['QualifiedTeams'], label='比赛队伍数')
plt.plot(data1['Year'], data1['MatchesPlayed'], label='比赛场数')
plt.legend()
plt.title('1930-2018年比赛队伍数比赛场数折线图')
plt.xlabel('年份')
plt.subplot(122)
plt.scatter(x=data1['QualifiedTeams'],y=data1['MatchesPlayed'])
plt.xlabel('比赛队伍数')
plt.ylabel('比赛场数')
一般认知里,,比赛队伍数越多,比赛场次越多。通过数据我们可以发现,在1980年以前,二者的数量变化趋势并没有多大关联。
而散点图我们可以看到,比赛队伍在16的时候,有5个不同的比赛场数,也说明了一定的不协调性
plt.figure(figsize=(20,8))
plt.subplot(121)
plt.plot(data1['Year'], data1['GoalsScored'])
plt.xlabel('年份')
plt.ylabel('得分')
plt.title('各年份比赛的总得分')
plt.subplot(122)
plt.bar(data1['Year'],data1['Attendance'])
plt.xlabel('年份')
plt.ylabel('观众数量')
plt.title('各年份观众总数')
随着年份的增长,比赛总得分和观众总数总体都呈现上升趋势,不过总得分某年前后起伏很大,当然这跟队员的表现等因素有关,而随着足球赛事的突飞猛进,逐渐进入大众眼中也会造成观众总数增加的形式
1.3.根据国家对获胜队伍分析
data1_country=data1.groupby('HostCountry')['Year'].count()
print('在',len(data1_country),'个国家里,有',len(data1_country[data1_country>1]),'个国家举办了不止一次的世界杯')
data1_country[data1_country>1]
data1_grade=data1.iloc[:,[1,2,3,4,5]]
def grade(x):
a=0
for i in range(1,len(x)):
if x[i]==x[0]:
return i
break
else:
a+=1
if a>0:
return 0
data1_grade['grade']=data1_grade.apply(grade,axis=1)
data1_grade.head()
将数据整合,如果主场冠军grade则为1,亚军为2,季军为3,第四为4,四强开外为0
data1_grade.groupby('grade')['HostCountry'].count().plot(kind='bar')
x=[0,1,2,3,4]
plt.xticks(x, ['四强开外', '主场冠军', '主场亚军','主场季军','主场第四'], rotation=0,fontsize=9)
plt.title('对于主场冠军的队伍荣誉柱状图')
print('主场冠军的国家有',data1_grade[data1_grade['grade']==1]['HostCountry'])
可以看到,一共有5个国家有主场冠军,而大多数举办国家的本土队伍成绩都在四强开外
plt.figure(figsize=(12,8))
plt.subplot(221)
data1_grade['Winner'].value_counts().head(5).plot(kind='bar')
plt.xticks(rotation=0)
plt.subplot(222)
data1_grade['Second'].value_counts().head(5).plot(kind='bar',color= 'red')
plt.xticks(rotation=0)
plt.subplot(223)
data1_grade['Third'].value_counts().head(5).plot(kind='bar',color='yellow')
plt.xticks(rotation=0)
plt.subplot(224)
data1_grade['Fourth'].value_counts().head(5).plot(kind='bar',color='green')
plt.xticks(rotation=0)
plt.suptitle('各国家获得的第一名/第二名/第三名/第四名(取前五)情况')
2.WorldCupMatches分析
2.1.数据预处理
print(data2.isnull().sum())
print('原长度',len(data2),'现长度',len(data2[data2.iloc[:,[6,7,10,11]].values>=0]))
通过筛查,我们发现只有观众这一列存在两个空值,因为不是分析的重点,该数据统计可能是遗漏造成的,所以不做删除
我们的进球数是分析的重点,该值必须大于等于0,所以筛选出符合条件的部分,发现和原数据长度一致,说明数据没有问题
2.2.胜率和比赛场数的分析
a1=data2.groupby('Home Team Name')['Year'].count().reset_index().rename(columns={'Home Team Name':'team','Year':'score1'})
a2=data2.groupby('Away Team Name')['Year'].count().reset_index().rename(columns={'Away Team Name':'team','Year':'score2'})
group=pd.merge(left=a1,right=a2,how='outer') #提取出21届各队伍的出场次数,score1代表赢球场数,socre2代表输的场数
group=group.fillna(0)
#值得注意的是,由于Home team name和away team name长度不一样,所以用outer连接
group['win_rate']=round(group['score1']/(group['score1']+group['score2']),2)
group['lose_rate']=round(group['score2']/(group['score1']+group['score2']),2)
group
上述的操作中,我们把世界杯的所有队伍汇总到一张表内,分别统计它们的胜场/负场,进而算出胜率和败率
win=group.sort_values('win_rate',ascending=False).head(10).iloc[:,[0,3]]
lose=group.sort_values('lose_rate',ascending=False).head(10).iloc[:,[0,4]]
plt.figure(figsize=(15,6))
plt.subplot(121)
plt.bar(x=win['team'],height=win['win_rate'])
plt.xticks(fontsize=7)
plt.title('胜率前十的队伍')
plt.subplot(122)
plt.bar(x=lose['team'],height=lose['lose_rate'],color='red')
plt.xticks(fontsize=6)
plt.title('败率前十的队伍')
通过柱状图,我们可以发现这些队伍中胜率最高的为Brazil,高达70%,胜率最低有5个队伍均为全败
但是仅看胜率是不合理的,比如一个队伍仅打了一场比赛胜率达到了100% 或者0%,都不能体现真实水平
group['count']=group['score1']+group['score2']
group['count'].describe()
#最大值为108,平均值和中位数均很小,呈现长尾右偏分布
group1=group[group['count']>=30] #选取场数大于等于30的队伍
fig=plt.figure(dpi=600)
ax=fig.add_subplot(111)
ax.set_ylim([30,120])
xdata=np.arange(1,23)
bar_width = 0.2
ax.set_ylabel('比赛场数',fontsize=12,fontweight='bold')
lns1=ax.bar(x=xdata,width=bar_width,height=group1['count'],label= 'y1', fc = 'steelblue',alpha=0.8)
plt.axhline(y=group1['count'].mean(), color='blue', linestyle='dashed') #添加场数的平均值作为虚线
ax1 = ax.twinx() # this is the important function
ax1.set_ylim(0,1)
plt.yticks(np.arange(0,1,0.2))
ax1.set_ylabel('胜率',fontsize=12,fontweight='bold')
lns2=ax1.bar(x=xdata+bar_width,width=bar_width,height=group1['win_rate'],label= 'y2',fc = 'indianred',alpha=0.8)
plt.xticks(xdata)
ax.set_xlabel('国家队伍') #设置横坐标间隔
ax.set_xticklabels(group1['team'],fontsize=7,rotation='vertical') #更换横坐标标签
plt.legend([lns1, lns2], ['比赛场数', '胜率'], loc='upper right')
plt.axhline(y=group1['win_rate'].mean(), color='red', linestyle='dashed') #添加胜率均值为虚线
我们绘制出比赛场数和胜率的柱状图,发现大多数队伍只有胜率高,但是比赛次数不算太多
我们将两者的均值用虚线表示,可以看到同时达到二者均值以上的有7个队伍。其中Brazil二者均达到最大值
2.3.胜率和比赛场数的关系
在足球比赛里,如果你一直没有被淘汰,比赛的场数就越多,胜率也越高;如果一开始就被淘汰,胜率为0,场数也只为1.
所以我们认为胜率和比赛场数是有一定关系的,对其进行相关性分析。
cor=group1[['count','win_rate']]
print('二者的相关系数为',cor.corr('spearman'))
plt.figure(figsize=(12,4))
plt.subplot(121)
sns.heatmap(data=cor.corr('spearman'),annot=True,cmap='RdBu_r',linewidths=10)
plt.title('胜率和场数的系数热力图')
plt.subplot(122)
plt.scatter(x=group1['win_rate'],y=group1['count'])
plt.title('胜率场数散点图')
胜率和场数的相关系数只有0.56,二者的散点图也呈散乱上升趋势,但是大多数队伍的胜率在0.3-0.6之间,场数在70场之间,而大于0.6胜率的队伍场数又很高,所以分析有一定的局限性。
2.4.给球队综合评分
data21 = data2[(data2['Stage'] == 'Quarter-finals') | (data2['Stage'] == 'Semi-finals') | (data2['Stage'] == 'Final')]
data21.head()
pivot_tabel=data21.pivot_table(index='Home Team Name',
columns='Stage',
values='Home Team Goals',
aggfunc='count').fillna(0)
pivot_tabel=pivot_tabel.reset_index()
pivot_tabel.rename(columns={'Home Team Name':'team'},inplace=True)
grade=pd.merge(left=group,right=pivot_tabel,how='outer')
grade=grade.fillna(0)
grade
我们创建了一个新的数据表,里面包含着胜率,比赛场数,赢下决赛,赢下四强,赢下八强等数据
至于stage里其他的属性由于题目未说明,所以我们不进行分析,而选择三个含金量高的进行分析
2.4.1.球队聚类
grade_sum=grade.iloc[:,[0,3,5,6,7,8]]
grade_sum=grade_sum.set_index(grade_sum.columns[0])
grade_sum_copy=grade_sum #创建副本
grade_sum
#聚类并显示聚类中心
normalized_data=StandardScaler().fit_transform(grade_sum)
df=pd.DataFrame(normalized_data, columns=grade_sum.columns)
k=4 #设置聚类数量
kmeans_model=KMeans(n_clusters=k,random_state=123)
fit_kmeans=kmeans_model.fit(df) #训练模型
kmeans_cc=kmeans_model.cluster_centers_ #查询聚类中心
kmeans_labels=kmeans_model.labels_ #查询标签
r1=pd.Series(kmeans_model.labels_).value_counts()
cluster_center=pd.DataFrame(kmeans_cc,columns=['win_rate','count','Final','Quarter-finals','Semi-finals'])
cluster_center.index=pd.Series([1,2,3,4])
cluster_center
#绘制雷达图
label=['win_rate','count','Final','Quarter-finals','Semi-finals']
legend=['球队类型'+ str(i) for i in cluster_center.index]
cluster_centers=pd.concat([cluster_center,cluster_center[['win_rate']]],axis=1) #首尾连接闭合
centers=np.array(cluster_centers.iloc[:,0:])
pictures=np.linspace(0,2*np.pi,len(label),endpoint=False)
picture=np.concatenate((pictures,[pictures[0]])) #沿第一个维度连接两者,形成闭环
fig=plt.figure(figsize=(12,8))
ax=fig.add_subplot(111,polar=True)
for i in range(4):
ax.plot(picture,centers[i],linewidth=2,label=legend[i])
ax.set_thetagrids(pictures*180/np.pi,label)
plt.title('球队类型雷达图')
plt.legend(legend)
通过雷达图,我们可以看到球队类型2的面积最大,并且在五个指标下的分值也最大。说明这种类型的球队比较全能,胜率、场数、八强/四强/冠军都拿得很多,我们定义为"强队"
球队类型3各指标排名第二,并且比另外两个队伍类型的面积更大,我们定义为"亚强队"
球队类型1除了胜率在标准化后的中心大于0外,其他的都小于0,说明这些国家的队伍打的比赛数量并不高,但是有一些胜率,不过没拿到很好的名次,定义为"中等队伍"
球队类型4各个指标都小于0,并且占比最小,这类国家的队伍实力相对较弱,我们定义为"再接再厉队"
#判断各个国家的队伍种类,汇总成数据框
kmeans_labels=pd.Series(kmeans_labels)
bin=[-1,0,1,2,3]
cut_group=pd.cut(kmeans_labels,bins=bin,labels=['中等队伍','强队','亚强队','再接再厉队'])
cut_group
grade_sum=grade_sum.reset_index()
grade_sum['series']=cut_group
grade_sum
#绘制饼图
plt.figure(figsize=(20,8))
grade_sum.groupby('series')['team'].count().plot.pie(autopct='%1.1f%%',explode=[0.02,0.05,0.03,0.01],wedgeprops={'edgecolor':'black'})
plt.legend()
plt.ylabel('')
plt.title('各国家球队类型占比')
通过饼图我们不难发现中等类型的队伍占了大概50%,其次是再接再厉队伍,而顶级、次顶级队伍仅有12%,也是比较符合实际。越厉害层次的人越少,这也是一种常见的分布趋势
2.4.2.球队主成分得分
scaler = StandardScaler()
data_scaled = scaler.fit_transform(grade_sum_copy) #注意我们选取之前创立的副本
pca = PCA()
pca.fit(data_scaled)
print('主成分方差贡献率:',pca.explained_variance_ratio_)
print("主成分的特征向量:",pca.components_)
cov_matrix = np.cov(data_scaled, rowvar=False)
eigenvalues = np.linalg.eigvals(cov_matrix)
print('特征值:',eigenvalues)
plt.figure(figsize=(12,4))
plt.subplot(121)
plt.plot(eigenvalues,linestyle='--', marker='o')
plt.xticks([0,1,2,3,4], ['主成分1','主成分2','主成分3','主成分4','主成分5'],fontsize=7)
plt.axhline(y=1, color='blue', linestyle='dashed')
plt.title('特征值碎石图')
plt.subplot(122)
list=np.cumsum(eigenvalues)/5
plt.plot(list,linestyle='--', marker='o',color='red')
plt.xticks([0,1,2,3,4], ['主成分1','主成分2','主成分3','主成分4','主成分5'],fontsize=7)
plt.axhline(y=0.85, color='blue', linestyle='dashed')
plt.title('方差累计贡献率')
根据碎石图和方差累计贡献率可以发现虽然只有第一个主成分特征值大于1,但如果选择两个主成分占比高达90%,只选择一个占比不到80%,所以我们选择两个主成分
接下来选取两个主成分进行分析
pca1 = PCA(n_components=2)
pca1.fit(data_scaled)
data_reduced = pca1.transform(data_scaled)
data_reduced_df = pd.DataFrame(data_reduced,columns=['主成分1','主成分2'])
data_reduced_df['team']=group['team']
data_reduced_df
#将各个队伍的主成分得分展示成新的数据表
data_reduced_df1=data_reduced_df.sort_values('主成分1',ascending=False).head(10)
data_reduced_df2=data_reduced_df.sort_values('主成分2',ascending=False).head(10)
plt.figure(figsize=(12,4))
plt.subplot(121)
plt.bar(data_reduced_df1['team'],data_reduced_df1['主成分1'])
plt.xticks(fontsize=10,rotation=90)
plt.title('第一主成分得分柱状图')
plt.subplot(122)
plt.bar(data_reduced_df2['team'],data_reduced_df2['主成分2'],color='red')
plt.xticks(fontsize=10,rotation=90)
plt.title('第二主成分得分柱状图')
print("主成分的特征向量:",pca1.components_)
主成分的特征向量: [[ 0.31664964 0.45433662 0.46886617 0.49328959 0.47973027] [-0.93789073 0.02662138 0.23061762 0.16888403 0.19479731]]
根据特征向量我们可以分析出不同主成分的含义:
第一主成分代表了各个属性都均匀发展,各个系数差别不大,衡量了总体的水平。而第二个主成分代表了胜率和其他属性的差距。数值越大说明胜率较其他属性相对更小,而其他属性
相对胜率来说分值更大,例如EI Salvador胜率只有0,所以得分受胜率系数的影响就非常小(0*(-0.9)=0)
#绘制两个主成分的散点图
plt.scatter(x=data_reduced_df['主成分1'],y=data_reduced_df['主成分2'])
plt.axhline(y=0,linestyle='dashed',color='black')
plt.axvline(x=0,linestyle='dashed',color='black')
plt.xlabel('第一主成分得分')
plt.ylabel('第二主成分得分')
画出两个主成分的散点图,我们可以发现,一半的国家队伍处在第三象限,说明整体水平不佳,但是胜率相对还行;而少部分队伍处于第一象限,说明整体水平高但是胜率相对低
第二和第四象限则分别表示整体水平不高胜率也相对低和整体水平高并且胜率相对高的队伍
3.player分析
3.1.数据预处理
data3.isnull().sum()
#显然其他字段没空值
data3=data3.fillna('其他')
#将空值变为'其他'
RoundID 0 MatchID 0 Team Initials 0 Coach Name 0 Line-up 0 Shirt Number 0 Player Name 0 Position 33641 Event 28715 dtype: int64
通过观察数据,我们发现这个数据表有和上一个工作表相同的字段。Position和Event存在着空值,但是我们不能对其进行删除,因为对于这两个指标只有存在该情况才能填充特定的值
但是我们也需要查询其他字段有没有空值
3.2.首发补发人数分析
data3.groupby('Line-up')['RoundID'].count().plot(kind='bar')
plt.xticks(rotation=0)
plt.title('首发/替补的人数柱状图')
plt.ylabel('人数')
line_pivot=data3.groupby(['Line-up','Position'])['Position'].count().unstack()
line_pivot=line_pivot.fillna(0)
color=['blue','red']
for i in range(len(line_pivot.columns)-2):
plt.figure(figsize=(12,4))
plt.bar(line_pivot.columns,line_pivot.iloc[i,:],color=color[i],label=line_pivot.index[i])
plt.legend()
plt.suptitle('首发/补发在比赛中担任各个角色的统计')
补发和首发的人数几乎持平,N在比赛中只担任GK和其他;S则均有职位胜任;二者都在'其他'中担任的数量最多
3.3.比赛角色造成的比赛事件分析
#统计造成比赛事件的次数,不造成则为0,造成了则根据数量进行统计
def event(x):
if len(x)==0:
return 0
else:
return len(x)//4
data3['events']=data3['Event'].apply(event)
data3.tail()
Event字段下我们已经将空值变成‘其他’,有些值则有几个object类型的值,我们统计个数并提取出新的字段属性表示比赛中的事件次数
plt.figure(figsize=(20,20))
plt.subplot(221)
data3.groupby('Position')['events'].sum().plot(kind='bar')
for i, v in enumerate(data3.groupby('Position')['events'].sum()):
plt.text(i, v, str(v), ha='center', va='bottom')
plt.yscale('log')
plt.title('不同位置球员犯规情况')
plt.subplot(222)
data3.groupby('Position')['events'].sum().plot.pie(autopct='%1.1f%%',explode=[0,0.2,0.2,0.1])
plt.ylabel('')
plt.title('不同位置球员犯规情况比例')
plt.legend(loc='upper left')
plt.subplot(223)
data3['Position'].value_counts().plot.pie(autopct='%1.1f%%')
plt.ylabel('')
plt.title('不同位置球员所占比例')
可以看到大多数的犯规来源于其他位置成员,占比6.5%的GK位成员犯规仅0.8%,即84次犯规;占比4%的C位犯规达5.1%,还是较高的,而占比最少的GKC犯规了13次占比0.1%
3.4.决赛获胜队员分析
win_name=data2[['Home Team Initials','MatchID']][data2['Stage']=='Final']
columns=['Team Initials','MatchID']
win_name.columns=columns
win_name
#提取出决赛获胜的队伍缩写和比赛ID
Team Initials | MatchID | |
---|---|---|
17 | URU | 1087.0 |
34 | ITA | 1134.0 |
52 | ITA | 1174.0 |
100 | FRG | 1278.0 |
135 | BRA | 1343.0 |
167 | BRA | 1463.0 |
199 | ENG | 1633.0 |
231 | BRA | 1765.0 |
269 | NED | 2063.0 |
307 | ARG | 2198.0 |
359 | ITA | 923.0 |
411 | ARG | 393.0 |
463 | FRG | 27.0 |
515 | BRA | 3104.0 |
579 | BRA | 8788.0 |
643 | GER | 43950064.0 |
707 | ITA | 97410064.0 |
771 | NED | 300061509.0 |
828 | GER | 300186501.0 |
851 | GER | 300186501.0 |
通过数据表data2,我们找到了在决赛中取胜的比赛ID和比赛队伍,值得注意的是,我们必须把比赛ID给一同提取出来。因为不同的队伍在不同的年份有不同的成绩,而比赛ID是唯一的,并且在data3里面有相同的字段可以进行合并
data3_win=data3.merge(win_name, on=['Team Initials', 'MatchID'], how='inner')
print('赢得决赛的成员数量有:',len(data3_win))
data3_win
以上,我们就提取出了赢得决赛的队员信息,接下来我们继续分析队员夺冠次数。
plt.figure(figsize=(20,8))
plt.subplot(121)
player_win=data3_win.groupby('Player Name')['MatchID'].count().reset_index()
player_win.boxplot()
plt.title('夺冠成员箱线图')
plt.subplot(122)
player_win = player_win.set_index('Player Name')
player_win['MatchID'][player_win['MatchID']>2].sort_values(ascending=False).plot(kind='bar')
plt.title('夺冠成员次数')
赢得决赛的人数有491,但是大多数人只赢过一次,赢得五次冠军的是KLOSE队员,赢过四次的大概有十几个人,赢得三次的仅有一个人,就连赢两次的占比也很少