題變成結(jié)構(gòu)化數(shù)據(jù):統(tǒng)計(jì)與可視化實(shí)戰(zhàn))
歐洲超級(jí)杯巴黎 2-1 維拉K77 克瓦拉茨赫利亞和杜埃先后破門(mén)登貝萊替補(bǔ)登場(chǎng)送出助攻。這條新聞標(biāo)題在球迷眼里是比賽結(jié)果在開(kāi)發(fā)者眼里卻是一個(gè)典型的數(shù)據(jù)建模場(chǎng)景比分、進(jìn)球者、助攻者、替補(bǔ)登場(chǎng)四個(gè)信息分別對(duì)應(yīng)不同的數(shù)據(jù)字段和事件類(lèi)型。文章以這場(chǎng)比賽為案例不討論戰(zhàn)術(shù)和判罰只講一件事如何用 Python 把一場(chǎng)比賽從一句話(huà)標(biāo)題整理成結(jié)構(gòu)化數(shù)據(jù)并完成進(jìn)球貢獻(xiàn)統(tǒng)計(jì)、替補(bǔ)效應(yīng)量化和比賽時(shí)間線可視化。下面給出完整代碼、數(shù)據(jù)文件和分析腳本讀者可以在本地環(huán)境直接跑通再替換成自己關(guān)注的比賽數(shù)據(jù)。這篇文章適合兩類(lèi)讀者。一類(lèi)是剛接觸數(shù)據(jù)分析、想用真實(shí)場(chǎng)景練手的開(kāi)發(fā)者另一類(lèi)是足球內(nèi)容創(chuàng)作者或數(shù)據(jù)愛(ài)好者希望把比賽報(bào)道改造成可復(fù)用的數(shù)據(jù)模板。閱讀前只需要有 Python 基礎(chǔ)了解變量、函數(shù)和 JSON 即可。整條技術(shù)主線是先確定比賽數(shù)據(jù)的字段和數(shù)據(jù)模型再準(zhǔn)備環(huán)境然后用 dataclass 建模接著把巴黎 2-1 維拉寫(xiě)入 JSON 并加載最后完成統(tǒng)計(jì)與可視化并補(bǔ)齊常見(jiàn)坑和擴(kuò)展方向。1. 先想清楚一場(chǎng)比賽的數(shù)據(jù)要從哪些維度描述1.1 新聞標(biāo)題是人讀的統(tǒng)計(jì)系統(tǒng)需要字段和事件“歐洲超級(jí)杯巴黎 2-1 維拉K77 杜埃破門(mén)登貝萊替補(bǔ)送助”這句話(huà)里面至少包含四層信息比賽名稱(chēng)歐洲超級(jí)杯。對(duì)陣與比分巴黎 2-1 維拉。進(jìn)球球員K77、杜埃。替補(bǔ)與助攻登貝萊替補(bǔ)登場(chǎng)并送出助攻。如果直接把這串文字存到數(shù)據(jù)庫(kù)里檢索時(shí)只能做模糊匹配無(wú)法回答“登貝萊的替補(bǔ)登場(chǎng)是否直接改變了比賽結(jié)果”“杜埃的進(jìn)球發(fā)生在第幾分鐘”這類(lèi)問(wèn)題。所以第一步是把信息拆成字段再根據(jù)字段之間的關(guān)系設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)。信息標(biāo)題中的表述對(duì)應(yīng)字段是否需要單獨(dú)事件比賽名稱(chēng)歐洲超級(jí)杯name否對(duì)陣雙方巴黎 / 維拉h(huán)ome_team / away_team否比分2-1home_score / away_score否但需要進(jìn)球事件支撐進(jìn)球者K77、杜埃goal 事件的 player 字段是替補(bǔ)登場(chǎng)登貝萊替補(bǔ)substitution 事件的 player 字段是助攻登貝萊送助goal 事件的 related_player 字段由 goal 事件承擔(dān)比賽名稱(chēng)、對(duì)陣和比分是比賽級(jí)別的屬性只保存一次即可。進(jìn)球者和替補(bǔ)登場(chǎng)必須用事件來(lái)描述因?yàn)橥幻騿T可以在多個(gè)事件里出現(xiàn)而且事件自帶“第幾分鐘發(fā)生”這個(gè)時(shí)間維度。1.2 事件數(shù)據(jù)進(jìn)球、替補(bǔ)、助攻如何組織足球比賽本質(zhì)上是按時(shí)間排序的事件流。本文只需要三類(lèi)事件但實(shí)際項(xiàng)目中至少要知道它們各自的組織方式goal 事件記錄進(jìn)球方、進(jìn)球者、分鐘、助攻者、當(dāng)時(shí)比分。substitution 事件記錄登場(chǎng)球員、被換下球員、分鐘。assist 信息建議合并進(jìn) goal 事件用 related_player 字段表示不需要單獨(dú)建事件。助攻之所以不建議拆成獨(dú)立事件是因?yàn)橹ズ瓦M(jìn)球在業(yè)務(wù)上是一對(duì)一關(guān)聯(lián)。拆成兩個(gè)事件后統(tǒng)計(jì)“某球員的參與進(jìn)球數(shù)”時(shí)還要做一次關(guān)聯(lián)查詢(xún)徒增復(fù)雜度。合并進(jìn) goal 事件后一次循環(huán)就能同時(shí)統(tǒng)計(jì)進(jìn)球和助攻。事件類(lèi)型必填字段典型取值說(shuō)明goalevent_type, team, minute, playergoal, home, 23, 克瓦拉茨赫利亞(K77)player 是進(jìn)球者related_player 可存助攻者substitutionevent_type, team, minute, player, related_playersubstitution, home, 46, 登貝萊, 被換下球員player 是登場(chǎng)球員related_player 是離場(chǎng)球員1.3 本文的最小技術(shù)閉環(huán)從這場(chǎng)比賽出發(fā)代碼要完成五個(gè)動(dòng)作加載 JSON、校驗(yàn)比分、統(tǒng)計(jì)進(jìn)球和助攻、計(jì)算替補(bǔ)登場(chǎng)時(shí)間、畫(huà)時(shí)間線和貢獻(xiàn)圖。數(shù)據(jù)規(guī)模小用 Python 自帶的 dataclass 加 pandas、matplotlib 就足夠不需要引入 Spark、Flink 之類(lèi)的大數(shù)據(jù)組件。2. 環(huán)境準(zhǔn)備Python 版本與依賴(lài)庫(kù)2.1 版本和依賴(lài)本文代碼使用 Python 3.10 及以上版本主要因?yàn)?dataclass 和類(lèi)型注解在 3.10 上已經(jīng)非常成熟。依賴(lài)庫(kù)只有三個(gè)安裝成本很低。依賴(lài)用途建議版本Python運(yùn)行環(huán)境3.10pandas表格統(tǒng)計(jì)與聚合2.xmatplotlib圖表繪制3.8numpy數(shù)值計(jì)算pandas 依賴(lài)1.26在項(xiàng)目目錄下創(chuàng)建 requirements.txtpandas2.0 matplotlib3.8 numpy1.26pandas 不是必須的只做單場(chǎng)比賽統(tǒng)計(jì)時(shí)用純 Python 字典也夠。但文章后面會(huì)把“多場(chǎng)比賽、多名球員”的聚合作為擴(kuò)展方向pandas 的 groupby 能力在數(shù)據(jù)量變大后會(huì)顯著減少代碼量所以這里直接引入。2.2 創(chuàng)建虛擬環(huán)境并安裝建議先建虛擬環(huán)境避免污染系統(tǒng) Pythonmkdir match_analysis cd match_analysis python -m venv .venv # Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate pip install -r requirements.txt安裝完成后驗(yàn)證版本python -c import pandas, matplotlib; print(pandas.__