Python!使用机器学习预测2022世界杯

Posted Code_cab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python!使用机器学习预测2022世界杯相关的知识,希望对你有一定的参考价值。

使用机器学习预测2022世界杯

文章目录

项目说明

  • 本项目是基于Kaggle开源的Baseline进行调整及优化,并对baseline进行逐行讲解。
  • 本项目所使用的数据集为国际足联世界排名1992-2022以及1872年至2022年国际足球成绩
  • 在本项目中,我们将问题变为分类问题,即我们最后模型的目标是预测主队的胜率和客场的平局/胜率。
  • 为了去除客场球队的优势,在项目中预测了客场和主场球队的变化结果(因为世界杯没有主场优势),并使用两个预测的平均值作为概率。
  • 有需要的同学请点击超链接进行下载。

数据集说明

1872年至2022年国际足球成绩

  • 该数据集包括从 1872 年的第一场正式比赛到 2022 年的44,152场国际足球比赛结果。
  • 比赛范围从FIFA世界杯到FIFI野生杯到常规友谊赛。
  • 这些比赛严格来说是男子的正式国际比赛,数据不包括奥运会或至少有一支球队是国家B队,U-23或联赛选择球队的比赛。

results.csv包括以下列:

  • date- 比赛日期
  • home_team- 主队名称
  • away_team- 客队名称
  • home_score- 全职主队得分,包括加时赛,不包括点球大战
  • away_score- 全场客队得分,包括加时赛,不包括点球大战
  • tournament- 比赛名称
  • city- 比赛所在的城市/城镇/行政单位的名称
  • country- 比赛所在国的名称
  • neutral- TRUE/FALSE 列,指示比赛是否在中立场地进行

shootouts.csv包括以下列:

  • date- 比赛日期
  • home_team- 主队名称
  • away_team- 客队名称
  • winner- 点球大战获胜者

国际足联世界排名1992-2022

  • country_full— 国家全名
  • country_abrv— 国家缩写
  • rank — 当前国家/地区排名
  • total_points— 当前总分
  • previous_points— 上次评分的总分
  • rank_change — 排名变化
  • confederation— 国际足联联合会
  • rank_date— 评级计算日期

数据分析及预处理

数据准备

#解压数据集
!unzip -d datasets/international-football-results-from-1872-to-2017 1872年至2022年国际足球成绩.zip 
!unzip -d datasets/fifaworldranking 国际足联世界排名1992-2022.zip 

对1872年至2022年国际足球成绩进行分析和预处理

导入1872年至2022年国际足球成绩中的result.csv

import pandas as pd
import numpy as np 
import re
df =  pd.read_csv("datasets/international-football-results-from-1872-to-2017/results.csv")
df.head()

我们先大概预览一下数据,知道其大概结构

查看下数据的基本信息

df.info()

  • 可以发现data是表示日期的,但它并不是日期的格式,因此我们预处理的时候需将它修改为日期的格式
  • 这个表格中只有两列是连续型特征,其余的特征都是离散型的

查看下缺失值

#缺失值查看、
df.isna().sum()

  • 可以发现有两列特征是同时含有40个缺失值的。
  • 而这两列特征代表的恰好是我们最重要的分数特征。
  • 没有分数特征,我们对其就不能进行接下来的建模,因此我们要剔除含有分数缺失的样本。

剔除含有分数缺失的样本及修改date格式

#删除缺失值所在的行
df.dropna(inplace=True)
#将日期列的格式转换为日期格式
df["date"] = pd.to_datetime(df["date"])

但是这样直接运行会报错,因为运行之后我们发现,在date这一列中含有’2022-19-22’这个字符串(并不符合正常日期逻辑),因此我们要先将含有该字符串的行剔除再对数据进行处理。

df = df.drop(df[df['date']=='2022-19-22'].index,axis=0)

我们使用的数据集将是2018年国际足联奥运会,从2018年世界杯后到2022年世界杯前的最后一场比赛。这个想法是为了分析世界杯准备和分类阶段的比赛情况。
因此,我们要对数据集进行筛选

  • 我们先查看一下2022年最后几场的情况吧
df.sort_values("date").tail()

  • 筛选出2018年8月1日以后的比赛,并重置索引
df = df[(df["date"] >= "2018-8-1")].reset_index(drop=True)
df.sort_values('data').tail()

对国际足联世界排名1992-2022的数据集进行分析和预处理

和刚刚一样,我们需要先转换日期格式并抽出2018年8月1日后的数据

rank = pd.read_csv("datasets/fifaworldranking/fifa_ranking-2022-10-06.csv")
rank["rank_date"] = pd.to_datetime(rank["rank_date"]) #转换日期格式
rank = rank[(rank["rank_date"] >= "2018-8-1")].reset_index(drop=True) #筛选数据集

世界杯上的一些球队在排名数据集中有不同的名字。所以,它需要调整。

rank["country_full"] = rank["country_full"].str.replace("IR Iran", "Iran").str.replace("Korea Republic", "South Korea").str.replace("USA", "United States")

对两表进行merge

接下来,我们要对数据集进行merge,merge是为了得到一个世界杯的数据集及其排名。

  • 将日期设置为我们的索引,然后根据国家进行分组,再重新采样每一天的第一条数据作为我们的数据,最后重置索引
  • 若是空值的话,我们使用了前向填充的方法。
rank = rank.set_index(['rank_date']).groupby(['country_full'], group_keys=False).resample('D').first().fillna(method='ffill').reset_index()
  • 我们选择rank表中的"country_full", “total_points”, “previous_points”, “rank”, “rank_change”, "rank_date"这些特征和df表进行merge
  • 并根据左表的date、home_team以及右表的rank_date、country_full进行左右对齐
  • 由于左右表有重复的特征列,因此我们只需取其中一个即可,因此我们这里选择将rank_date和country_full进行删除
df_wc_ranked = df.merge(rank[["country_full", "total_points", "previous_points", "rank", "rank_change", "rank_date"]],
 left_on=["date", "home_team"], right_on=["rank_date", "country_full"]).drop(["rank_date", "country_full"], axis=1)
  • 我们知道在result.csv中除了有home_team(主队名称)还有away_team(客队名称)
  • 上方的merge只是将主队的数据merge到一起,而客队的还没merge,因此我们还需要重新merge一次
  • 在这里我们取rank的特征列与上方一致,只是左对齐中的home_team变成了我们的away_team
  • 由于刚刚已经合并过一次了,因此再合并的话,会出现很多重复的列名。也为了区分主客队的特征,我们将主队的rank特征列的后缀改为_home,将客队的rank特征列的后缀改为_away
  • 最后也是和刚刚一样,剩下的重复特征(例如时间与国家名),我们取其中一个即可
df_wc_ranked = df_wc_ranked.merge(rank[["country_full", "total_points", "previous_points", "rank", "rank_change", "rank_date"]], 
left_on=["date", "away_team"], right_on=["rank_date", "country_full"], 
suffixes=("_home", "_away")).drop(["rank_date", "country_full"], axis=1)

合并完我们抽主客队都为Brazil的部分数据出来看一下

df_wc_ranked[(df_wc_ranked.home_team == "Brazil") | (df_wc_ranked.away_team == "Brazil")].tail()


现在,我们已经准备好了数据,可以根据数据集进行特征工程了

特征工程

  • 这是的想法是创造更多对足球比赛胜负有影响的特征
  • 我们认为影响的特征可能是以下几个:
    1.球队的历史得分
    2.球队历史的进球与失球
    3.球队的排名
    4.球队排名的上升情况
    5.排名所面临的进球和损失
    6.比赛的重要性(是否友好)
  • 因此我们要创建一个功能:判断哪支队赢了,以及他们在比赛中获得了多少分

封装一个判断输赢的函数

df = df_wc_ranked
def result_finder(home, away):
    if home > away:
        return pd.Series([0, 3, 0])
    if home < away:
        return pd.Series([1, 0, 3])
    else:
        return pd.Series([2, 1, 1])

results = df.apply(lambda x: result_finder(x["home_score"], x["away_score"]), axis=1)
df[["result", "home_team_points", "away_team_points"]] = results

假设的检验

  • 比赛积分是赢3分,平1分,输0分,与数据库中已有的排名积分不同。
  • 另外,我们认为数据集中的排名积分和同一球队的排名是负相关的,我们应该只使用其中的一个来创建新的特征。
  • 以下是对这一假设的检验。
import seaborn as sns
import matplotlib.pyplot as plt

plt.figure(figsize=(15, 10))
sns.heatmap(df[["total_points_home", "rank_home", "total_points_away", "rank_away"]].corr())
plt.show()

特征衍生

  • 现在,我们需要创建对建模有利的特征
  • 例如:
    1.排名差异
    2.在比赛中赢得的分数与面对的球队排名
    3.比赛中的进球差异。
    所有不属于差异的特征都应该为两支球队(客场和主场)创建。
df["rank_dif"] = df["rank_home"] - df["rank_away"]  #排名差异
df["sg"] = df["home_score"] - df["away_score"]  #分数差异
df["points_home_by_rank"] = df["home_team_points"]/df["rank_away"] #主场队伍进球与排名的关系
df["points_away_by_rank"] = df["away_team_points"]/df["rank_home"] #客场队伍进球与排名的关系

为了更好的特征衍生,我们将数据集分为主队和客队的数据集,然后合并一起计算它们过去比赛的各种特征。
之后再将它们分离并合并,构造出一个原始的数据集。
这个过程优化了特征衍生

  • 先将数据集分成主场与客场的数据集
home_team = df[["date", "home_team", "home_score", "away_score", "rank_home", "rank_away","rank_change_home", "total_points_home", "result", "rank_dif", "points_home_by_rank", "home_team_points"]]

away_team = df[["date", "away_team", "away_score", "home_score", "rank_away", "rank_home","rank_change_away", "total_points_away", "result", "rank_dif", "points_away_by_rank", "away_team_points"]]
  • 由于刚刚merge数据集的时候,对特征列的名字是做了修改的,现在我们要将特征列的名字修改成初始的名字,以便后续的处理
home_team.columns = [h.replace("home_", "").replace("_home", "").replace("away_", "suf_").replace("_away", "_suf") for h in home_team.columns]

away_team.columns = [a.replace("away_", "").replace("_away", "").replace("home_", "suf_").replace("_home", "_suf") for a in away_team.columns]
  • 将它们append到一起进行特征计算
team_stats = home_team.append(away_team)
  • 这些列将被用来特征计算
team_stats_raw = team_stats.copy()

现在,我们得到了一个数据集,准备进行进一步的特征衍生。将要衍生的列是:

  • Mean goals of the team in World Cup Cycle. --世界杯球队的平均进球数
  • Mean goals of the team in last 5 games. --球队最近5场比赛的平均进球数
  • Mean goals suffered of the team in World Cup Cycle. --世界杯球队的平均犯规数
  • Mean goals suffered of the team in last 5 games. --球队最近5场比赛的平均犯规数
  • Mean FIFA Rank that team faced in World Cup Cycle. --球队在世界杯中FIFA平均排名
  • Mean FIFA Rank that team faced in last 5 games. --球队在最近5场比赛中FIFA平均排名
  • FIFA Points won at the cycle. --FIFA积分
  • FIFA Points won in last 5 games. --最近5场FIFA积分
  • Mean game points at the Cycle. --比赛得分
  • Mean game points at last 5 games. --最近5场比赛积分
  • Mean game points by rank faced at the Cycle.
  • Mean game points by rank faced at last 5 games.
stats_val = []

for index, row in team_stats.iterrows():
    team = row["team"]
    date = row["date"]
    past_games = team_stats.loc[(team_stats["team"] == team) & (team_stats["date"] < date)].sort_values(by=['date'], ascending=False)
    last5 = past_games.head(5) #取出过去五场比赛
    
    goals = past_games["score"].mean()
    goals_l5 = last5["score"].mean()
    
    goals_suf = past_games["suf_score"].mean()
    goals_suf_l5 = last5["suf_score"].mean()
    
    rank = past_games["rank_suf"].mean()
    rank_l5 = last5["rank_suf"].mean()
    
    if len(last5) > 0:
        points = past_games["total_points"].values[0] - past_games["total_points"].values[-1]#qtd de pontos ganhos
        points_l5 = last5["total_points"].values[0] - last5["total_points"].values[-1] 
    else:
        points = 0
        points_l5 = 0
        
    gp = past_games["team_points"].mean()
    gp_l5 = last5["team_points"].mean()
    
    gp_rank = past_games["points_by_rank"].mean()
    gp_rank_l5 = last5["points_by_rank"].mean()
    
    stats_val.append([goals, goals_l5, goals_suf, goals_suf_l5, rank, rank_l5, points, points_l5, gp, gp_l5, gp_rank, gp_rank_l5])
  • 将刚刚衍生出来的特征与原表格合并到一起
  • 并且重新用full_df去接收
stats_cols = ["goals_mean", "goals_mean_l5", "goals_suf_mean", "goals_suf_mean_l5", "rank_mean", "rank_mean_l5", "points_mean", "points_mean_l5", "game_points_mean", "game_points_mean_l5", "game_points_rank_mean", "game_points_rank_mean_l5"]

stats_df = pd.DataFrame(stats_val, columns=stats_cols)

full_df = pd.concat([team_stats.reset_index(drop=True), stats_df], axis=1, ignore_index=False)
  • 再次将合并好的数据集分成主场与客场
home_team_stats = full_df.iloc[:int(full_df.shape[0]/2),:]
away_team_stats = full_df.iloc[int(full_df.shape[0]/2):,:]
  • 取出刚刚特征衍生出来的列
home_team_stats = home_team_stats[home_team_stats.columns[-12:]]
away_team_stats = away_team_stats[away_team_stats.columns[-12:]]
  • 对其进行重命名(home_代表主场)(away_代表客场)
    为了统一数据集,需要为每一列添加主场和客场的后缀,之后,数据就可以合并使用了
home_team_stats.columns = ['home_'+str(col) for col in home_team_stats.columns]
away_team_stats.columns = ['away_'+str(col) for col in away_team_stats.columns]
  • 数据合并
match_stats = pd.concat([home_team_stats, away_team_stats.reset_index(drop=True)], axis=1, ignore_index=False)
full_df = pd.concat([df, match_stats.reset_index(drop=True)], axis=1, ignore_index=False)
full_df.columns

看一下现有的特征列

  • 为了确定该场比赛是否友好,我们封装一个函数去判断它
def find_friendly(x):
    if x == "Friendly":
        return 1
    else: return 0

full_df["is_friendly"] = full_df["tournament"].apply(lambda x: find_friendly(x)) 
  • 并对其进行One-hot编码
full_df = pd.get_dummies(full_df, columns=["is_friendly"])

对特征工程后的数据集进行数据分析

  • 在这里,我们只选择有助于我们特征分析的列进行分析
base_df = full_df[["date", "home_team", "away_team", "rank_home", "rank_away","home_score", "away_score","result", "rank_dif", "rank_change_home", "rank_change_away", 'home_goals_mean',
       'home_goals_mean_l5', 'home_goals_suf_mean', 'home_goals_suf_mean_l5',
       'home_rank_mean', 'home_rank_mean_l5', 'home_points_mean',
       'home_points_mean_l5', 'away_goals_mean', 'away_goals_mean_l5',
       'away_goals_suf_mean', 'away_goals_suf_mean_l5', 'away_rank_mean',
       'away_rank_mean_l5', 'away_points_mean', 'away_points_mean_l5','home_game_points_mean', 'home_game_points_mean_l5',
       'home_game_points_rank_mean', 'home_game_points_rank_mean_l5','away_game_points_mean',
       'away_game_points_mean_l5', 'away_game_points_rank_mean',
       'away_game_points_rank_mean_l5',
       'is_friendly_0', 'is_friendly_1']]

base_df.head()

  • 查询一下缺失值
base_df.isna().sum()

  • 我们知道,带有空值的行,是无法计算其平均值的,所以我们需要将有空值的样本剔除
base_df_no_fg = base_df.dropna()

现在,我们需要分析所有创建的特征,检查它们是否具有预测能力。另外,如果它们没有,我们需要创建一些有预测力的特征,比如主客队的差异。为了分析预测能力,我将指定平局作为主队的输球,并将二分类问题。

df = base_df_no_fg
def no_draw(x):
    if x == 2:
        return 以上是关于Python!使用机器学习预测2022世界杯的主要内容,如果未能解决你的问题,请参考以下文章

Python!使用机器学习预测2022世界杯

世界杯足彩怎么买划算?机器学习AI告诉你答案(含预测)

听说你赌球?Python来预测哪只球队能夺冠!别外传哦!小赌怡情!

小5聊使用div+css布局绘制32支球队比赛对阵图,拭目以待冠军花落谁家

ML之shap:基于FIFA 2018 Statistics(2018年俄罗斯世界杯足球赛)球队比赛之星分类预测数据集利用RF随机森林+计算SHAP值单样本力图/依赖关系贡献图可视化实现可解释性之攻略

ML之PDP:基于FIFA 2018 Statistics(2018年俄罗斯世界杯足球赛)球队比赛之星分类预测数据集利用DT决策树&RF随机森林+PDP部分依赖图可视化实现模型可解释性之详细攻