python模拟足球射门_用Python模拟2018世界杯夺冠之路

2018俄罗斯世界杯小组抽签出炉,几家欢喜几家愁。世界杯从来就不乏看点,东道主俄罗斯能走多远、德国能否卫冕、西班牙是否有望东山再起、两位球王谁更接近大力神杯...距世界杯开幕还有半年时间,一切都是未知数,不过整个赛程已定,我完全按照赛程模拟了所有64场比赛比分1000次,得出了A~H组各自的出线形势、每支队伍进四强的概率、以及最终的夺冠概率。一切结果,先卖个关子。

做这件事分四个步骤:爬数据

计算球队进球、失球均值,构建泊松模型

模拟1000次世界杯赛事

统计出线概率、夺冠概率、四强概率

爬数据

上一篇文章用Python分析本赛季英超争四形势提到从OPTA抓取数据,由于接口权限不对外公开,现在我改用公开的免费数据,方便大家自行抓取。这次所有比赛数据、赛程数据是我从球探网上抓的。利用selenium库,我将每只参赛国家队最近一年的比赛数据都抓取下来,保存成Pandas库的数据框。举个例子,这是葡萄牙国家队的页面,以及下面一张截图是抓下来存储的干净数据框。葡萄牙国家队葡萄牙国家队的Pandas数据框

计算球队进球、失球均值,构建泊松模型

泊松模型是模拟比赛的核心算法,理论在用Python分析本赛季英超争四形势文章中介绍过。针对国家队,我做了以下修改:若进球数

,强制

。这是因为热身赛双方实力差距过大,德国8:0马来西亚,这种差距在世界杯决赛圈几乎不存在。

亚洲球队与欧洲球队水平存在一个差异值,需要整体乘以一个系数。韩国场均进2球,相比德国场均1.5球,韩国的对手亚洲球队居多,德国打过欧洲杯对手实力不俗,韩国的场均2球必须打折扣。

得到计算结果,按进攻实力排序,欧洲豪强与南美双雄占据前列。(尾部的球队没有列出来)

模拟1000次世界杯赛事

先解决如何模拟一场比赛。淘汰赛与小组赛不同,如果打成平局必须进行点球大战,决出胜负。点球大战就设定各自50%概率晋级,下面这个simulate_match函数传入knockout参数为True时,就会激发这个机制,返回晋级的球队名。如果不是knockout,就是小组赛,就是输出模拟的比分。

import scipy as sp

import pandas as pd

# 读取球队进球率、失球率参数

team_strength = pd.read_csv('球队攻防参数.csv')

# 每一场球生成几次泊松随机数,次数越多随机因素越小

n_sim = 5

def simulate_match(team_A, team_B, knockout=False):

"""模拟一场比赛,返回主队进球数、客队进球数"""

# 获取比赛双方进球率、失球率

home_scoring_strength = (team_strength.loc[team_A, 'alpha'] + \

team_strength.loc[team_B, 'beta']) / 2

away_scoring_strength = (team_strength.loc[team_A, 'beta'] + \

team_strength.loc[team_B, 'alpha']) / 2

# 模拟n次比赛进球数取众数

fs_A = sp.stats.mode(poisson.rvs(home_scoring_strength, size=n_sim))[0][0]

fs_B = sp.stats.mode(poisson.rvs(away_scoring_strength, size=n_sim))[0][0]

print(team_A, fs_A, team_B, fs_B)

# 进入淘汰赛,若平局,点球大战晋级概率50%:50%

if knockout:

if fs_A == fs_B:

return [team_A, team_B][sp.random.randint(0, 2)]

elif fs_A > fs_B:

return team_A

else:

return team_B

return fs_A, fs_B

# 例如:

simulate_match('阿根廷', '尼日利亚', knockout=True)

>> 阿根廷

接下来是赛程,小组赛有6场每个组,8组共48场。按照赛程我手动写入列表里,比如A组的比赛按顺序,对战双方分别是这样:

# 小组每场比赛对阵双方:[主队, 客队]

fixture_A = \

[['俄罗斯', '沙特阿拉伯'],

['埃及', '乌拉圭'],

['俄罗斯', '埃及'],

['乌拉圭', '沙特阿拉伯'],

['沙特阿拉伯', '埃及'],

['俄罗斯', '乌拉圭']

]

然后建了一个类,每个组分别各自初始化自己的类,传入参数fixture就是上面创建的赛程,只需调用play函数就可以模拟该小组6场比赛比分。self.table是小组积分榜,保存下来每次模拟的小组头两名球队名,后面统计每支队在1000次模拟里出线的次数,即出线概率。

class Group:

"""模拟小组赛阶段,直接调用.play方法。"""

def __init__(self, group_teams, group_name, fixture):

self.group_teams = group_teams

self.group_name = group_name

self.table = pd.DataFrame(0, columns=['场次', '积分', '进球', '失球', '净胜球'], index=self.group_teams)

self.fixture = fixture

self.result = None

def play(self):

result = []

for [team_A, team_B] in self.fixture:

fs_A, fs_B = simulate_match(team_A, team_B)

self.table.loc[team_A, '场次'] += 1

self.table.loc[team_B, '场次'] += 1

self.table.loc[team_A, '进球'] += fs_A

self.table.loc[team_B, '进球'] += fs_B

self.table.loc[team_A, '失球'] += fs_B

self.table.loc[team_B, '失球'] += fs_A

if fs_A > fs_B:

self.table.loc[team_A, '积分'] += 3

elif fs_A == fs_B:

self.table.loc[team_A, '积分'] += 1

self.table.loc[team_B, '积分'] += 1

elif fs_A < fs_B:

self.table.loc[team_B, '积分'] += 1

else:

raise ValueError('比赛比分模拟有误!')

result.append([team_A, team_B, fs_A, fs_B])

self.result = pd.DataFrame(result, columns=['主队', '客队', '主队进球', '客队进球'])

self.table['净胜球'] = self.table['进球'] - self.table['失球']

self.table.sort_values(by=['积分', '净胜球', '进球'],

ascending=[False, False, False], inplace=True)

随后淘汰赛,16进8、8进4、半决赛和决赛。赛程球探网给出了,包括进入16强的对阵形势,每场由哪组第一对阵哪组第二都写清楚了,只要继续用上面模拟比赛的方式继续按照赛程模拟就行。

至此,我可以完整模拟一届世界杯的所有64场比赛的比分。最重要的,我记录下每组的出线球队、以及冠亚军、季军、殿军分别是哪个国家。接下来就可以轻松循环1000次,并进行统计。

统计出线概率、夺冠概率、四强概率

A~H组各自的出线概率我已经统计完成,东道主俄罗斯的FIFA世界排名已跌至65位,不过俄罗斯抽签抽到上上签,有望小组出线进入下一轮。(由于32球队太多,图片拆分4波展示。)A组B组出线形势C组D组出线形势E组F组出线形势G组H组出线形势

以下是夺冠概率、及打进四强的概率。列出了所有夺冠热门球队。

最后,韩国队在1000次模拟中11次进入四强,并有1次夺冠。这种小概率事件不禁让我想起2015/16赛季英超,以赛季前1赔5000逆天夺冠的莱斯特城。所以,足球是圆的,任何事情都有它的可能性存在,中国国足什么时候再进世界杯呢?

你可能感兴趣的:(python模拟足球射门)