当前位置:   article > 正文

【Python】惊呆了!我用 Python 可视化分析和预测了 2022 年 FIFA 世界杯

python预测结果可视化代码

0ed0f9905fbb0b5b344c61b9e709f91b.png

许多人称足球为 "不可预测的游戏",因为一场足球比赛有太多不同的因素可以改变最后的比分。

从某种程度上这是真的.....因此本文仅供学习参考!!

预测一场比赛的最终比分或赢家确实是很难的,但在预测一项比赛的赢家时就不是这样了。在过去的5年中,Bayern Munich赢得了所有的德甲联赛,而Manchester City则赢得了4个首发联赛。其实这些都可以用来预测的。

经过测试,本文建立的模型能成功地预测了在20-21赛季中期的所有英超、西甲、意甲和德甲这些联赛的冠军,这也是基于当时已经有近19场比赛了。同样,我们使用该模型来预测下2022年世界杯,会不会有如此惊人的效果呢?一起拭目以待吧~

如何预测比赛呢?

有不同的方法来进行预测。我可以建立一个花哨的机器学习模型并给它提供多个变量,但在阅读了一些论文后,我决定使用泊松分布试一试。

泊松分布

有读者会疑问,这是为什么呢?那接下来首先看一下泊松分布的定义。

泊松分布是一个离散的概率分布,描述了在一个固定的时间间隔或机会区域内发生的事件的数量。

如果我们把进球看作是一场足球比赛90分钟内可能发生的事件,我们可以计算出A队和B队在一场比赛中可能进球的概率。

但这还不够。我们仍然需要满足泊松分布的假设。

  1. 可以计算事件的数量(一场比赛可以有1、2、3或更多的进球)。

  2. 事件的发生是独立的(一个目标的发生不应影响另一个目标的概率)。

  3. 事件发生的速度是恒定的(在某一时间间隔内发生目标的概率对于相同长度的其他每一个时间间隔都应该是完全相同的)。

  4. 两个事件不可能在完全相同的时间内发生(两个目标不可能同时发生)

毫无疑问,假设1和4是符合的,但2和3是部分正确的。也就是说,我们假设假设2和3总是正确的。

当预测欧洲顶级联赛的冠军时,我绘制了过去5年前4个联赛每场比赛的进球数柱状图。

33f3b6ca153041ecd5cbf35a68d8ee45.png
4个联赛的进球数柱状图

如果你看一下任何联赛的拟合曲线,它看起来像泊松分布。

现在我们可以说,可以用泊松分布来计算一场比赛中可能出现的进球数的概率。

下面是泊松分布的公式。

为了进行预测,我考虑了。

  • lambda:90分钟内进球数的中位数(A队和B队)。

  • x:一场比赛中A队和B队可能进的球数

为了计算lambda,我们需要每个国家队的平均进/丢球数。这将我们引向下一个问题。

每个国家队的进球/丢球情况

在收集了从1930年到2018年的所有世界杯比赛的数据(需要完整数据请在公众号:数据STUDIO 后台回复 云朵君)后,可以计算出每个国家队的平均进球和丢球情况。

数据清洗

读取数据
  1. df_historical_data = pd.read_csv('data/fifa_worldcup_matches.csv')
  2. df_fixture = pd.read_csv('data/fifa_worldcup_fixture.csv')
  3. df_missing_data = pd.read_csv('data/fifa_worldcup_missing_data.csv')
清洗df_fixture
  1. df_fixture['home'] = df_fixture['home'].str.strip()
  2. df_fixture['away'] = df_fixture['away'].str.strip()
清洗df_missing_data
  1. df_missing_data.dropna(inplace=True)
  2. df_historical_data = pd.concat([df_historical_data, df_missing_data], ignore_index=True)
  3. df_historical_data.drop_duplicates(inplace=True)
  4. df_historical_data.sort_values('year', inplace=True)
  5. df_historical_data
93f284ef5a1c6b12940566f1af5ece7a.png
清洗df_historical_data
  1. # 删掉与走过场的比赛
  2. delete_index = df_historical_data[df_historical_data['home'].str.contains('Sweden') &
  3.                                   df_historical_data['away'].str.contains('Austria')].index
  4. df_historical_data.drop(index=delete_index, inplace=True)
  5. # 清洗分数和主客场columns
  6. df_historical_data['score'] = df_historical_data['score'].str.replace('[^\d–]''', regex=True)
  7. df_historical_data['home'] = df_historical_data['home'].str.strip() # 清洗空白格: Yugoslavia twice
  8. df_historical_data['away'] = df_historical_data['away'].str.strip()
  9. # splitting score columns into home and away goals and dropping score column
  10. # 将得分columns分成主客场进球和降分columns
  11. df_historical_data[['HomeGoals''AwayGoals']] = df_historical_data['score'].str.split('–', expand=True)
  12. df_historical_data.drop('score', axis=1, inplace=True)
  13. # 重命名列名并更改格式
  14. df_historical_data.rename(columns={'home''HomeTeam''away''AwayTeam'
  15.                                    'year':'Year'}, inplace=True)
  16. df_historical_data = df_historical_data.astype({'HomeGoals'int'AwayGoals':int'Year'int})
  17. # 创建一个新列 "totalgoals"
  18. df_historical_data['TotalGoals'] = df_historical_data['HomeGoals'] + df_historical_data['AwayGoals']
  19. df_historical_data
7e8d1f99f84065e7b0c77de9a3dd1a38.png
保存清洗过后的数据
  1. df_historical_data.to_csv('clean_fifa_worldcup_matches.csv',index=False)
  2. df_fixture.to_csv('clean_fifa_worldcup_fixture.csv',index=False)

数据可视化

上下滑动查看更多源码
  1. # nation_position, club_position, player_positions
  2. df = pd.read_csv('players_22.csv', low_memory=False)
  3. # 选择需要用的列
  4. df = df[['short_name''age''nationality_name''overall''potential',
  5.          'club_name''value_eur''wage_eur''player_positions']]
  6. # 只选择一个position
  7. df['player_positions'] = df['player_positions'].str.split(',', expand=True)[0]
  8. # 删除缺失值
  9. df.dropna(inplace=True)
  10. players_missing_worldcup = ['K. Benzema''S. Mané''S. Agüero''Sergio Ramos',
  11.                             'P. Pogba''M. Reus''Diogo Jota''A. Harit'
  12.                             'N. Kanté''G. Lo Celso''Piqué']
  13. # 删除受伤的球员
  14. drop_index = df[df['short_name'].isin(players_missing_worldcup)].index
  15. df.drop(drop_index, axis=0, inplace=True)
  16. teams_worldcup = [
  17.     'Qatar''Brazil''Belgium''France''Argentina''England''Spain''Portugal',
  18.     'Mexico''Netherlands''Denmark''Germany''Uruguay''Switzerland''United States''Croatia',
  19.     'Senegal''Iran''Japan''Morocco''Serbia''Poland''South Korea''Tunisia',
  20.     'Cameroon''Canada''Ecuador''Saudi Arabia''Ghana''Wales''Costa Rica''Australia'
  21. ]
  22. # 筛选国家队
  23. df = df[df['nationality_name'].isin(teams_worldcup)]
  24. # 最佳球员
  25. df.sort_values(by=['overall''potential''value_eur'], ascending=False, inplace=True)

球员分布

  1. import numpy as np
  2. fig, ax = plt.subplots(figsize=(125), tight_layout=True)
  3. sns.histplot(df, x='overall', binwidth=1)
  4. bins = np.arange(df['overall'].min(), df['overall'].max(), 1)
  5. plt.xticks(bins)
  6. plt.show()
9dd24916e696dd5c9124ea02b2273bed.png

世界杯梦之队球员

df.drop_duplicates('player_positions')
51853e44de3f63c0c493bdf5bfba47d7.png

每个国家队中最有技能的球员

  1. df_best_players = df.copy()
  2. df_best_players = df_best_players.drop_duplicates('nationality_name').reset_index(drop=True)
  3. country_short =  df_best_players['nationality_name'].str.extract('(^\w{3})', expand=False).str.upper()
  4. df_best_players['name_nationality'] = df_best_players['short_name'] +' (' + country_short + ')'
  5. fig, ax = plt.subplots(figsize=(106), tight_layout=True)
  6. sns.barplot(df_best_players, x='overall', y='name_nationality',
  7.             palette=sns.color_palette('pastel'), width=0.5)
  8. plt.show()
0b02d3a4e866c89e6449871271f7b942.png

每支球队的最佳阵容

  1. def best_squad(nationality):
  2.     df_best_squad = df.copy()
  3.     df_best_squad = df_best_squad.groupby(['nationality_name''player_positions']).head(2)
  4.     df_best_squad = df_best_squad[df_best_squad['nationality_name']==nationality].sort_values(['player_positions''overall''potential'], ascending=False)
  5.     return df_best_squad
  6. best_squad('Brazil')
8ce0c3d0ddfc2297bce7e51a17f870bc.png
  1. average_overall = [best_squad(team)['overall'].mean() for team in teams_worldcup]
  2. df_average_overall = pd.DataFrame({'Teams': teams_worldcup, 'AVG_Overall': average_overall})
  3. df_average_overall = df_average_overall.dropna()
  4. df_average_overall = df_average_overall.sort_values('AVG_Overall', ascending=False)
  5. df_average_overall
上下滑动查看更多结果
9a5534b1bc01060b25d20af5e7ddf096.png
  1. fig, ax = plt.subplots(figsize=(125), tight_layout=True)
  2. sns.barplot(df_average_overall[:10], x='Teams', y='AVG_Overall',
  3.             palette=sns.color_palette('pastel'))
  4. plt.show()
c58aa9284e0c43d73cf7d3e2cd7ae20b.png

每支球队的最佳阵型

上下滑动查看更多源码
  1. def best_lineup(nationality, lineup):
  2.     lineup_count = [lineup.count(i) for i in lineup]
  3.     df_lineup = pd.DataFrame({'position': lineup, 'count': lineup_count})
  4.     positions_non_repeated = df_lineup[df_lineup['count'] <= 1]['position'].values
  5.     positions_repeated = df_lineup[df_lineup['count'] > 1]['position'].values
  6.     df_squad = best_squad(nationality)
  7.     df_lineup = pd.concat([
  8.         df_squad[df_squad['player_positions'].isin(positions_non_repeated)].drop_duplicates('player_positions', keep='first'),
  9.         df_squad[df_squad['player_positions'].isin(positions_repeated)]]
  10.     )
  11.     return df_lineup[['short_name''overall''club_name''player_positions']]
  12.   
  13.   
  14. dict_formation = {
  15.     '4-3-3': ['GK''RB''CB''CB''LB''CDM''CM''CAM''RW''ST''LW'],
  16.     '4-4-2': ['GK''RB''CB''CB''LB''RM''CM''CM''LM''ST''ST'],
  17.     '4-2-3-1': ['GK''RB''CB''CB''LB''CDM''CDM''CAM''CAM''CAM''ST'],
  18. }
  19. for index, row in df_average_overall[:9].iterrows():
  20.     max_average = None
  21.     for key, values in dict_formation.items():
  22.         average = best_lineup(row['Teams'], values)['overall'].mean()
  23.         if max_average is None or average>max_average:
  24.             max_average = average
  25.             formation = key
  26.     print(row['Teams'], formation, max_average)
  1. Spain 4-2-3-1 85.1
  2. Portugal 4-2-3-1 84.9
  3. England 4-4-2 84.45454545454545
  4. Brazil 4-3-3 84.81818181818181
  5. France 4-2-3-1 83.9
  6. Argentina 4-3-3 83.54545454545455
  7. Germany 4-2-3-1 84.1
  8. Belgium 4-3-3 82.54545454545455
  9. Netherlands 4-4-2 82.54545454545455
  1. # best_lineup('Spain', dict_formation['4-2-3-1'])
  2. # best_lineup('Argentina', dict_formation['4-3-3'])
  3. best_lineup('Brazil', dict_formation['4-3-3'])
90e205c363f85a8faa32894499c9bc56.png

由于在世界杯中,几乎所有的球队都在中立球场比赛,所以在这次分析中没有考虑主场/客场的因素。

一旦有了每个国家队的进/丢球数,就创建了一个函数,预测每支球队在小组赛中会得到多少分。

预测小组赛阶段

下面是我用来预测每个国家队在小组赛阶段会得到多少分的代码。

计算球队实力

  1. dict_table = pickle.load(open('dict_table','rb'))
  2. df_historical_data = pd.read_csv('clean_fifa_worldcup_matches.csv')
  3. df_fixture = pd.read_csv('clean_fifa_worldcup_fixture.csv')
  4. df_home = df_historical_data[['HomeTeam''HomeGoals''AwayGoals']]
  5. df_away = df_historical_data[['AwayTeam''HomeGoals''AwayGoals']]
  6. df_home = df_home.rename(columns={'HomeTeam':'Team''HomeGoals''GoalsScored''AwayGoals''GoalsConceded'})
  7. df_away = df_away.rename(columns={'AwayTeam':'Team''HomeGoals''GoalsConceded''AwayGoals''GoalsScored'})
  8. df_team_strength = pd.concat([df_home, df_away], ignore_index=True).groupby(['Team']).mean()
  9. df_team_strength
f4e2298affd7c9ee9e259d3086f89dcf.png
  1. from scipy.stats import poisson
  2. def predict_points(home, away):
  3.     if home in df_team_strength.index and away in df_team_strength.index:
  4.         lamb_home = df_team_strength.at[home,'GoalsScored'] * df_team_strength.at[away,'GoalsConceded']
  5.         lamb_away = df_team_strength.at[away,'GoalsScored'] * df_team_strength.at[home,'GoalsConceded']
  6.         prob_home, prob_away, prob_draw = 000
  7.         for x in range(0,11): #number of goals home team
  8.             for y in range(011): #number of goals away team
  9.                 p = poisson.pmf(x, lamb_home) * poisson.pmf(y, lamb_away)
  10.                 if x == y:
  11.                     prob_draw += p
  12.                 elif x > y:
  13.                     prob_home += p
  14.                 else:
  15.                     prob_away += p
  16.         
  17.         points_home = 3 * prob_home + prob_draw
  18.         points_away = 3 * prob_away + prob_draw
  19.         return (points_home, points_away)
  20.     else:
  21.         return (00)

通俗地说,predict_points 计算的是主队和客队会得到多少分。这里使用公式计算每支球队的lambda,即average_goals_scored * average_goals_conceded

然后模拟了一场比赛从0-0到10-10的所有可能的比分(最后的那个比分只是我的进球范围的极限)。一旦有了lambdax,就可以使用泊松分布的公式来计算p

prob_homeprob_drawprob_away分别累积了p的值,如果说比赛以1-0(主场获胜)、1-1(平局)或0-1(客场获胜)结束。最后,用下面的公式计算积分。

  1. point_home = 3 * prob_home + prob_draw
  2. point_away = 3 * prob_away + prob_draw

如果我们用predict_points来预测英格兰对美国的比赛,我们会得到这个结果。

  1. >>> print(predict_points('England''United States'))
  2. (2.23561476353260070.5922397535606193)

这意味着英格兰将得到2.23分,而美国将得到0.59分。因为这里使用的是概率,因此得到的是小数。

如果将这个predict_points函数应用于小组赛阶段的所有比赛,我们将得到每个小组的第1和第2名,从而得到以下淘汰赛的比赛。

  1. df_fixture_group_48 = df_fixture[:48].copy()
  2. df_fixture_knockout = df_fixture[48:56].copy()
  3. df_fixture_quarter = df_fixture[56:60].copy()
  4. df_fixture_semi = df_fixture[60:62].copy()
  5. df_fixture_final = df_fixture[62:].copy()
  6. for group in dict_table:
  7.     teams_in_group = dict_table[group]['Team'].values
  8.     df_fixture_group_6 = df_fixture_group_48[df_fixture_group_48['home'].isin(teams_in_group)]
  9.     for index, row in df_fixture_group_6.iterrows():
  10.         home, away = row['home'], row['away']
  11.         points_home, points_away = predict_points(home, away)
  12.         dict_table[group].loc[dict_table[group]['Team'] == home, 'Pts'] += points_home
  13.         dict_table[group].loc[dict_table[group]['Team'] == away, 'Pts'] += points_away
  14.     dict_table[group] = dict_table[group].sort_values('Pts', ascending=False).reset_index()
  15.     dict_table[group] = dict_table[group][['Team''Pts']]
  16.     dict_table[group] = dict_table[group].round(0)
  17.     
  18. dict_table['Group A']
c4d015c9a986f34c1b143e891995e1e7.png 0669760a4927011f8e977e2fb037996e.png

预测淘汰赛

df_fixture_knockout
1d95702d34300004a1299e44de9ee343.png
  1. for group in dict_table:
  2.     group_winner = dict_table[group].loc[0'Team']
  3.     runners_up = dict_table[group].loc[1'Team']
  4.     df_fixture_knockout.replace({f'Winners {group}':group_winner,
  5.                                  f'Runners-up {group}':runners_up}, inplace=True)
  6. df_fixture_knockout['winner'] = '?'
  7. df_fixture_knockout
16d02d2b4bc2d41158b28ddbed884345.png

对于淘汰赛,我不需要预测分数,而是预测每个小组的获胜者。这就是为什么我在之前的 predict_points 函数基础上创建了一个新的 get_winner 函数。

  1. def get_winner(df_fixture_updated):
  2.     for index, row in df_fixture_updated.iterrows():
  3.         home, away = row['home'], row['away']
  4.         points_home, points_away = predict_points(home, away)
  5.         if points_home > points_away:
  6.             winner = home
  7.         else:
  8.             winner = away
  9.         df_fixture_updated.loc[index, 'winner'] = winner
  10.     return df_fixture_updated

简单地说,如果主队的积分大于客队的积分,那么赢家就是主队,否则,赢家就是客队。

使用get_winner函数可以得到如下的结果。

8dcdee9bf0129112ae735979f49c1b9b.png

预测四分之一决赛、半决赛和决赛的情况

  1. def update_table(df_fixture_round_1, df_fixture_round_2):
  2.     for index, row in df_fixture_round_1.iterrows():
  3.         winner = df_fixture_round_1.loc[index, 'winner']
  4.         match = df_fixture_round_1.loc[index, 'score']
  5.         df_fixture_round_2.replace({f'Winners {match}':winner}, inplace=True)
  6.     df_fixture_round_2['winner'] = '?'
  7.     return df_fixture_round_2

四分之一决赛

d70a3cfdf85f14e4967873412cfd261d.png

半决赛

7d6ee6795beb451eaa5e401690da9634.png

决赛

9bce92f9e98d6aed30cb177ced9e248c.png

如果我使用 get_winner,我可以预测世界杯的冠军。这是最后的结果!!

c5a14447a368bb6d1a45c78f614b92cd.png

通过再一次运行该函数,我得到的赢家是...巴西!

 
 

fdadf330debe23fe0e54a4df5277afe3.jpeg

 
 
 
 
 
 
 
 
  1. 往期精彩回顾
  2. 适合初学者入门人工智能的路线及资料下载(图文+视频)机器学习入门系列下载机器学习及深度学习笔记等资料打印《统计学习方法》的代码复现专辑机器学习交流qq群955171419,加入微信群请扫码
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号