用Python模擬2018世界盃奪冠之路

2018俄羅斯世界盃小組抽籤出爐,幾家歡喜幾家愁。世界盃從來就不乏看點,東道主俄羅斯能走多遠、德國能否衛冕、西班牙是否有望東山再起、兩位球王誰更接近大力神杯...距世界盃開幕還有半年時間,一切都是未知數,不過整個賽程已定,我完全按照賽程模擬了所有64場比賽比分1000次,得出了A~H組各自的出線形勢、每支隊伍進四強的概率、以及最終的奪冠概率。一切結果,先賣個關子。

做這件事分四個步驟:

  1. 爬數據
  2. 計算球隊進球、失球均值,構建泊松模型
  3. 模擬1000次世界盃賽事
  4. 統計出線概率、奪冠概率、四強概率

爬數據

上一篇文章用Python分析本賽季英超爭四形勢提到從OPTA抓取數據,由於介面許可權不對外公開,現在我改用公開的免費數據,方便大家自行抓取。這次所有比賽數據、賽程數據是我從球探網上抓的。利用selenium庫,我將每隻參賽國家隊最近一年的比賽數據都抓取下來,保存成Pandas庫的數據框。舉個例子,這是葡萄牙國家隊的頁面,以及下面一張截圖是抓下來存儲的乾淨數據框。

葡萄牙國家隊

葡萄牙國家隊的Pandas數據框

計算球隊進球、失球均值,構建泊松模型

泊松模型是模擬比賽的核心演算法,理論在用Python分析本賽季英超爭四形勢文章中介紹過。針對國家隊,我做了以下修改:

  • 若進球數 x>4 ,強制 x=4 。這是因為熱身賽雙方實力差距過大,德國8:0馬來西亞,這種差距在世界盃決賽圈幾乎不存在。
  • 亞洲球隊與歐洲球隊水平存在一個差異值,需要整體乘以一個係數。韓國場均進2球,相比德國場均1.5球,韓國的對手亞洲球隊居多,德國打過歐洲杯對手實力不俗,韓國的場均2球必須打折扣。

得到計算結果,按進攻實力排序,歐洲豪強與南美雙雄佔據前列。(尾部的球隊沒有列出來)

模擬1000次世界盃賽事

先解決如何模擬一場比賽。淘汰賽與小組賽不同,如果打成平局必須進行點球大戰,決出勝負。點球大戰就設定各自50%概率晉級,下面這個simulate_match函數傳入knockout參數為True時,就會激發這個機制,返回晉級的球隊名。如果不是knockout,就是小組賽,就是輸出模擬的比分。

import scipy as spnimport pandas as pdnn# 讀取球隊進球率、失球率參數nteam_strength = pd.read_csv(球隊攻防參數.csv)nn# 每一場球生成幾次泊松隨機數,次數越多隨機因素越小nn_sim = 5nndef simulate_match(team_A, team_B, knockout=False):n """模擬一場比賽,返回主隊進球數、客隊進球數"""n # 獲取比賽雙方進球率、失球率n home_scoring_strength = (team_strength.loc[team_A, alpha] + n team_strength.loc[team_B, beta]) / 2n away_scoring_strength = (team_strength.loc[team_A, beta] + n team_strength.loc[team_B, alpha]) / 2n # 模擬n次比賽進球數取眾數n fs_A = sp.stats.mode(poisson.rvs(home_scoring_strength, size=n_sim))[0][0]n fs_B = sp.stats.mode(poisson.rvs(away_scoring_strength, size=n_sim))[0][0]n print(team_A, fs_A, team_B, fs_B)n # 進入淘汰賽,若平局,點球大戰晉級概率50%:50%n if knockout:n if fs_A == fs_B:n return [team_A, team_B][sp.random.randint(0, 2)]n elif fs_A > fs_B:n return team_An else:n return team_Bn return fs_A, fs_Bnn# 例如:nsimulate_match(阿根廷, 奈及利亞, knockout=True)n>> 阿根廷n

接下來是賽程,小組賽有6場每個組,8組共48場。按照賽程我手動寫入列表裡,比如A組的比賽按順序,對戰雙方分別是這樣:

# 小組每場比賽對陣雙方:[主隊, 客隊]nfixture_A = n[[俄羅斯, 沙烏地阿拉伯], n [埃及, 烏拉圭], n [俄羅斯, 埃及], n [烏拉圭, 沙烏地阿拉伯], n [沙烏地阿拉伯, 埃及], n [俄羅斯, 烏拉圭]n]n

然後建了一個類,每個組分別各自初始化自己的類,傳入參數fixture就是上面創建的賽程,只需調用play函數就可以模擬該小組6場比賽比分。self.table是小組積分榜,保存下來每次模擬的小組頭兩名球隊名,後面統計每支隊在1000次模擬里出線的次數,即出線概率。

class Group:n """模擬小組賽階段,直接調用.play方法。"""n def __init__(self, group_teams, group_name, fixture):n self.group_teams = group_teamsn self.group_name = group_namen self.table = pd.DataFrame(0, columns=[場次, 積分, 進球, 失球, 凈勝球], index=self.group_teams)n self.fixture = fixturen self.result = Nonen def play(self):n result = []n for [team_A, team_B] in self.fixture:n fs_A, fs_B = simulate_match(team_A, team_B)n self.table.loc[team_A, 場次] += 1n self.table.loc[team_B, 場次] += 1n self.table.loc[team_A, 進球] += fs_An self.table.loc[team_B, 進球] += fs_Bn self.table.loc[team_A, 失球] += fs_Bn self.table.loc[team_B, 失球] += fs_An if fs_A > fs_B:n self.table.loc[team_A, 積分] += 3n elif fs_A == fs_B:n self.table.loc[team_A, 積分] += 1n self.table.loc[team_B, 積分] += 1n elif fs_A < fs_B:n self.table.loc[team_B, 積分] += 1n else:n raise ValueError(比賽比分模擬有誤!)n result.append([team_A, team_B, fs_A, fs_B])n self.result = pd.DataFrame(result, columns=[主隊, 客隊, 主隊進球, 客隊進球])n self.table[凈勝球] = self.table[進球] - self.table[失球]n self.table.sort_values(by=[積分, 凈勝球, 進球], n ascending=[False, False, False], inplace=True)n

隨後淘汰賽,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逆天奪冠的萊斯特城。所以,足球是圓的,任何事情都有它的可能性存在,中國國足什麼時候再進世界盃呢?


推薦閱讀:

替U23出氣,上港6比0吊打西亞球隊,將教育馬拉多納
大連足球:每次球隊有困難,都會有當地來兜底,這次會例外嗎
如何評價2016年7月3日歐洲杯德國隊對陣義大利的點球決勝比賽?
盤點天下足球經典解說詞(1)

TAG:Python | 足球 | 数据分析 |