免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Python数据分析实战:用pandas和matplotlib复盘足球比赛事件

Python数据分析实战:用pandas和matplotlib复盘足球比赛事件 各位CSDN的读者朋友大家好。当我们在业务开发中遇到大量零散的比赛记录、事件日志或用户行为数据时往往会面临一个共同难题数据是有了但很难直观看到关键信息之间的关联。最近大家讨论较多的“欧洲超级杯巴黎2-1维拉K77杜埃破门登贝莱替补送助”这场焦点战恰好是一个非常适合用来做数据清洗与可视化练习的真实场景。与其只停留在比分播报层面不如我们来写一套Python数据分析实战把一场比赛的关键事件、球员贡献和战术走向用图表和统计指标还原出来。本文会从一个完整的项目角度出发带你完成从环境准备、数据建模、事件标注、可视化分析到常见报错排查的全流程。无论你是刚接触pandas的初学者还是想找一份“比赛数据 Python绘图”练手项目的开发者这篇文章都能直接复用。我们不会只贴代码还会逐步解释每一步背后的设计思路。1. 背景与核心概念为什么要分析比赛事件数据1.1 什么是比赛事件数据一场足球比赛看起来只是90分钟内的比分变化但如果把视野拉宽比赛其实是大量离散事件的集合。比如一次射门、一次助攻、一次换人、一张黄牌都是可以独立记录的事件。这些事件组合在一起才构成了“巴黎2-1维拉”这样的最终结果。在数据工程领域这类数据通常被称为“事件流数据”Event Stream Data。每条事件记录包含几个核心字段事件发生的时间点、所属球队、球员姓名、事件类型、关联球员等。如果我们把这些字段存进数据库或DataFrame就能按照时间轴还原比赛过程也能统计出不同球员的参与度、球队的进攻节奏等深度指标。1.2 为什么要用Python做体育赛事分析Python在数据分析领域有非常成熟的生态。pandas负责数据清洗和聚合统计matplotlib和seaborn负责可视化呈现这两者结合足以完成从原始记录到“可读结论”的整个链路。更重要的是Python代码的可复现性很强换一场比赛只要按照同样格式准备CSV文件就能复用整套分析逻辑。相比直接在Excel里手工筛选用Python脚本分析的好处是可以处理几十场甚至上百场比赛的批量数据。指标计算逻辑可以沉淀为函数统一口径。可视化输出可以嵌入Web服务也可以导出图片用于报告。便于和数据库、API接口对接实现自动化数据流水线。1.3 核心分析指标在本文的实战中我们会重点计算下面几类指标指标含义计算方式进球事件谁在什么时间破门从事件表中筛选 type goal助攻事件进球前的关键传球者筛选 type assist替补贡献度替补上场后参与的进球/助攻关联球员入场时间与事件时间进攻时间分布球队在哪些时间段更有威胁按5分钟区间统计射门与进球事件数关键球员评分综合进球、助攻、射门等事件自定义加权公式这些指标看起来简单但真正常见的问题出现在数据整理阶段。原始数据往往存在球员名字写法不一致、事件时间缺失、重复记录等情况下面我们就从环境准备开始走一遍完整流程。2. 环境准备与版本说明2.1 运行环境本文示例在以下环境中验证通过。如果你本机版本不完全一致不必担心只要保证主库版本差距不大即可。操作系统Windows 10 / macOS 13 / Ubuntu 22.04 均可Python 版本3.9 及以上开发工具VS Code 或 PyCharm终端Windows PowerShell / macOS Terminal / Linux Bash2.2 依赖库版本本文主要使用以下Python库库名称用途建议版本pandas数据清洗、聚合、统计分析2.0.x 及以上matplotlib基础可视化3.7.x 及以上numpy数值计算辅助1.24.x 及以上seaborn可选统计图表美化0.12.x 及以上如果还没安装可以直接执行下面这条命令一次性安装pip install pandas matplotlib numpy seaborn安装完成后可以用下面的小段代码确认版本import pandas as pd import matplotlib import numpy as np print(pandas version:, pd.__version__) print(matplotlib version:, matplotlib.__version__) print(numpy version:, np.__version__)版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你的环境中已经存在旧版本建议先升级再继续。2.3 项目结构为了让代码清晰易维护我们按照下面的目录结构组织项目super_cup_analysis/ ├── data/ │ └── match_events.csv ├── output/ │ └── (程序自动生成图片) ├── src/ │ ├── data_clean.py │ ├── analysis.py │ └── visualize.py └── main.py其中data/存放原始比赛事件数据。src/存放数据清洗、统计分析、可视化三个模块。output/存放生成的图表。main.py是入口脚本串联整个流程。3. 核心概念与数据建模从原始事件到结构化数据3.1 定义事件表结构在开始写代码前我们需要先设计事件表的结构。一张标准化的比赛事件表通常包含以下字段字段名类型说明event_idint事件唯一编号minuteint事件发生分钟teamstr球队名称playerstr关联球员event_typestr事件类型related_playerstr关联球员助攻等detailstr补充描述以“K77杜埃破门登贝莱替补送助”为例我们可以拆成两条事件第X分钟巴黎进球球员K77/杜埃助攻者登贝莱。从事件类型看前者是“goal”后者是“assist”。3.2 CSV数据样例我们在data/match_events.csv中准备一批示意数据。注意由于实际比赛数据可能需要从官方渠道获取这里的数据主要用于演示分析逻辑团队和球员名以标题信息为基础。event_id,minute,team,player,event_type,related_player,detail 1,12,Paris,K77,shot,None,射门偏出 2,23,Paris,K77,goal,Dembele,杜埃破门 3,35,Aston Villa,Watkins,shot,None,射门被扑出 4,41,Aston Villa,Rogers,goal,Tielemans,维拉扳平 5,55,Paris,Dembele,sub_in,None,登贝莱替补登场 6,67,Paris,Dembele,assist,None,登贝莱送出关键传球 7,68,Paris,Dembele,goal,None,登贝莱替补建功 8,75,Paris,Dembele,shot,None,远射偏出 9,82,Aston Villa,McGinn,yellow_card,None,黄牌 10,90,Paris,Dembele,assist,None,锁定胜局这里我们对标题所述“K77杜埃破门登贝莱替补送助”做了适当扩展新增了一些合理的比赛事件目的是让数据集更完整便于后续演示统计与可视化逻辑。实际使用时你可以替换成自己手中的真实数据。3.3 为什么需要数据清洗前面这份CSV看起来已经比较规整但真实数据往往有下面几种“脏”的情况同一球员名称在不同记录中写法不同比如“Dembele”和“O. Dembele”。事件类型大小写混用比如“Goal”和“goal”同时存在。minute字段存在空值。related_player字段对进球事件可能为空对助攻事件可能多余。存在重复提交的事件记录。因此我们的第一步不是画图而是清洗数据。下面进入完整实战。4. 完整实战案例用Python复盘欧洲超级杯巴黎2-1维拉4.1 创建项目结构并准备数据首先在本地新建项目文件夹并在其中创建data、src、output三个子目录。然后把上面的CSV内容保存到data/match_events.csv文件中。如果你是在Linux或macOS下可以用命令行创建mkdir -p super_cup_analysis/{data,src,output} cd super_cup_analysis在Windows下可以直接在资源管理器中新建文件夹也可以使用PowerShellNew-Item -ItemType Directory -Force -Path super_cup_analysis\data,super_cup_analysis\src,super_cup_analysis\output4.2 编写数据清洗模块创建src/data_clean.py文件写入以下代码# 文件路径src/data_clean.py import pandas as pd def load_match_events(file_path): 加载CSV文件并做基础清洗。 df pd.read_csv(file_path) # 1. 去除全为空的行 df df.dropna(howall) # 2. 去除重复记录基于除event_id外的字段判断 df df.drop_duplicates(subset[minute, team, player, event_type]) # 3. 统一事件类型为小写便于后续筛选 df[event_type] df[event_type].str.strip().str.lower() # 4. 球员名称统一去除首尾空格并统一为小写可选 df[player] df[player].str.strip() df[related_player] df[related_player].fillna().astype(str).str.strip() # 5. minute字段转为数值类型无法转换的设为NaN df[minute] pd.to_numeric(df[minute], errorscoerce) # 6. 删除分钟缺失的记录因为时间轴分析依赖minute字段 df df.dropna(subset[minute]) # 7. 按时间排序 df df.sort_values(minute).reset_index(dropTrue) return df if __name__ __main__: df load_match_events(../data/match_events.csv) print(df.head()) print(清洗后事件总数:, len(df))这段代码做了以下几件事dropna(howall)删除整行全空的记录。drop_duplicates按照关键字段去除重复事件。str.strip().str.lower()统一事件类型文本格式。pd.to_numeric将字符串型分钟数转为数值类型带errorscoerce可以安全处理非法值。最终按分钟排序方便后续时间轴分析。在项目根目录运行cd super_cup_analysis python src/data_clean.py预期输出是清洗后的前几行数据以及事件总数。如果你发现总数与我们最初CSV行数一致说明没有重复或脏数据被清理这属于正常情况。4.3 编写统计分析与指标计算模块创建src/analysis.py写入# 文件路径src/analysis.py import pandas as pd def calc_team_goals(df): 统计各球队进球数。 goals df[df[event_type] goal] return goals.groupby(team)[player].count().reset_index(namegoals) def calc_goal_contributors(df): 统计进球与助攻贡献返回参与进球事件的关键球员。 goals df[df[event_type] goal].copy() assists df[df[event_type] assist].copy() goal_records [] for _, goal in goals.iterrows(): goal_records.append({ minute: goal[minute], team: goal[team], scorer: goal[player], assist: goal.get(related_player, ), }) assist_player_set set() for _, assist in assists.iterrows(): assist_player_set.add(assist[player]) return pd.DataFrame(goal_records), assist_player_set def calc_player_event_count(df, player_name): 统计指定球员参与的各类型事件数量。 player_df df[(df[player] player_name) | (df[related_player] player_name)] return player_df[event_type].value_counts().to_dict() def calc_time_attack_distribution(df, interval5): 按时间区间统计射门、进球、助攻等进攻事件数量用于分析进攻节奏。 attack_events df[df[event_type].isin([shot, goal, assist])].copy() attack_events[interval] (attack_events[minute] // interval) * interval dist attack_events.groupby([interval, team]).size().reset_index(nameevent_count) return dist在这个模块中我们设计了四个函数calc_team_goals最基础的进球统计。calc_goal_contributors把进球和助攻关联起来返回一场比赛中“谁进球、谁助攻”的结构化表格。calc_player_event_count统计指定球员的参与度比如登贝莱替补登场后贡献了几次助攻、几次射门。calc_time_attack_distribution按5分钟区间统计进攻事件用于观察球队进攻波次。这些函数之后会被主程序调用也可以单独导入到Jupyter Notebook中做探索性分析。4.4 编写可视化模块创建src/visualize.py# 文件路径src/visualize.py import matplotlib.pyplot as plt import matplotlib # 设置中文字体避免绘图时出现方框 matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] matplotlib.rcParams[axes.unicode_minus] False def plot_team_goals(goal_summary, save_pathNone): 绘制各球队进球数柱状图。 fig, ax plt.subplots(figsize(6, 4)) ax.bar(goal_summary[team], goal_summary[goals], color[#1f77b4, #ff7f0e]) ax.set_title(各球队进球数对比) ax.set_xlabel(球队) ax.set_ylabel(进球数) for i, v in enumerate(goal_summary[goals]): ax.text(i, v 0.05, str(v), hacenter, vabottom) if save_path: plt.savefig(save_path, dpi150, bbox_inchestight) plt.show() def plot_attack_timeline(dist, save_pathNone): 绘制进攻事件时间分布折线图。 fig, ax plt.subplots(figsize(10, 5)) for team in dist[team].unique(): team_dist dist[dist[team] team] ax.plot(team_dist[interval], team_dist[event_count], markero, labelteam) ax.set_title(进攻事件时间分布按5分钟区间) ax.set_xlabel(比赛分钟区间) ax.set_ylabel(事件数) ax.legend() ax.grid(True, linestyle--, alpha0.6) if save_path: plt.savefig(save_path, dpi150, bbox_inchestight) plt.show()这里需要注意两个细节中文字体设置在不同操作系统上不一样。Windows常见SimHeimacOS常见PingFang SC。这里列了多个备选字体代码会按顺序尝试。axes.unicode_minus要设为False否则负号容易显示成方框。4.5 编写主程序串联流程创建main.py# 文件路径main.py import os from src.data_clean import load_match_events from src.analysis import ( calc_team_goals, calc_goal_contributors, calc_player_event_count, calc_time_attack_distribution, ) from src.visualize import plot_team_goals, plot_attack_timeline DATA_PATH data/match_events.csv OUTPUT_DIR output if __name__ __main__: os.makedirs(OUTPUT_DIR, exist_okTrue) # 1. 加载与清洗 df load_match_events(DATA_PATH) print(清洗后事件数据) print(df) print(\n) # 2. 进球统计 team_goals calc_team_goals(df) print(球队进球统计) print(team_goals) print(\n) # 3. 进球与助攻关系 goal_contributors, assist_players calc_goal_contributors(df) print(进球事件明细) print(goal_contributors) print(\n) # 4. 单个球员事件统计 dembele_events calc_player_event_count(df, Dembele) print(Dembele登贝莱参与的事件类型统计) for event_type, cnt in dembele_events.items(): print(f {event_type}: {cnt} 次) print(\n) # 5. 进攻时间分布 timeline calc_time_attack_distribution(df, interval5) print(进攻事件时间分布前10行) print(timeline.head(10)) # 6. 保存可视化结果 plot_team_goals(team_goals, save_pathos.path.join(OUTPUT_DIR, team_goals.png)) plot_attack_timeline(timeline, save_pathos.path.join(OUTPUT_DIR, attack_timeline.png)) print(\n分析完成图片已保存至 output/ 目录。)运行主程序python main.py如果一切正常控制台会依次输出清洗后的表格、球队进球统计、进球事件明细、登贝莱事件统计、进攻时间分布前10行同时在output/目录下生成两张图片。4.6 运行结果说明基于我们准备的示意数据预期能看到巴黎进球数为2维拉进球数为1。进球明细中巴黎第一球由K77打进助攻者是Dembele第二球由Dembele打进扩展数据。维拉的一球由Rogers打进助攻者为Tielemans。Dembele的事件类型包括assist、goal、shot、sub_in直观反映了“替补登场后参与多个关键事件”的数据特征。这里特别说明一下我们为了完整演示分析流程在原始CSV中保留了标题描述的核心事件“K77杜埃破门”和“登贝莱替补送助”同时扩展了部分合理事件。如果你希望用真实比赛数据可以在主流体育数据平台查询或通过官方API获取再用相同代码做替换即可。5. 常见问题与排查思路5.1 CSV文件读取报错问题现象常见原因解决思路FileNotFoundError文件路径不对没有在项目根目录运行使用绝对路径或在运行前确认当前目录UnicodeDecodeErrorCSV文件编码不是UTF-8在pd.read_csv中指定encodingutf-8或encodinggbk列名对不上CSV表头有空格或大小写不一致打印df.columns检查必要时用df.columns df.columns.str.strip()5.2 图表中文乱码这个问题非常常见。核心原因是matplotlib的默认字体不支持中文。解决办法import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] matplotlib.rcParams[axes.unicode_minus] False如果你的系统里没有这些字体可以安装中文字体后重启Python环境或者改用英文标题绕过问题。5.3 minute字段转换后出现NaN如果原始数据中部分分钟数写了“上半场补时第2分钟”这种文本pd.to_numeric会将其转为NaN。处理方式有两种直接删除这些记录适用于补时进球不影响整体分析的场景。使用正则表达式提取其中的数字再统一处理。示例import re def extract_minute(value): if isinstance(value, str): match re.search(r\d, value) if match: return int(match.group()) return None return value5.4 重复事件导致统计翻倍有些数据源会重复推送同一条事件记录。最简单的办法是在清洗阶段做去重df df.drop_duplicates(subset[minute, team, player, event_type])如果是复杂业务场景建议先按子集去重再人工检查去重数量是否在合理范围内。5.5 数据量大时运行慢如果分析对象是几百场比赛的全量事件数据建议使用pandas的分块读取或直接查询数据库避免把全部数据一次性加载进内存。另外对team、event_type这种分类字段可以先转为category类型能够节省内存并提升分组聚合速度。6. 最佳实践与工程建议6.1 数据层面在开展任何赛事分析项目前建议先明确数据来源和字段口径。比赛事件数据通常会分散在多个数据源中比如球员ID、球队ID在不同的数据库中可能不一致。最佳实践是建立一张“球员映射表”把不同来源的球员名称统一为内部ID避免后续统计分析出现口径混乱。同时时间字段要尽量统一存储为整数分钟不要混用“452”“HT”“FT”这类文本。如果必须保留补时信息建议拆成minute和stoppage_time两个字段分析时单独处理。6.2 代码层面清洗逻辑和业务计算逻辑要分离。像我们前面写的data_clean.py和analysis.py分别负责不同职责方便后续测试和维护。函数命名要语义化比如calc_player_event_count比func1清晰得多。每个函数只做一件事。load_match_events只处理加载和清洗plot_team_goals只负责绘图。这样在替换数据源或更换图表样式时不需要改动其他模块。对关键输出结果建议打印到一个日志文件中而不仅仅是控制台。比如每次跑完分析后可以生成一份report.txt包含进球统计、助攻统计、替补贡献等信息。6.3 可视化层面体育赛事分析图表的重点不在于装饰多华丽而在于信息传达准确。柱状图适合对比球队进球数折线图适合表现进攻时间节奏散点图可以分析球员射门位置分布。绘制时间线类图表时建议把比赛的三个阶段用背景色区分例如前45分钟、中场休息、后45分钟这样读者可以快速感知下半场攻势是否明显加强。6.4 生产环境注意事项如果这套分析逻辑要放进生产环境比如每日自动拉取比赛数据并生成报告还需要注意以下几点调用外部数据API时必须控制频率避免触发限流。数据文件应该按日期分目录存储例如data/2025-08-15/match_events.csv便于回溯。分析任务可以用定时调度器执行任务结束后发送成功或失败通知。所有涉及数据库写入的操作必须先备份原表并在测试库验证通过后再更新线上配置。7. 总结与下一步学习方向本文以一个具体的比赛场景“欧洲超级杯巴黎2-1维拉K77杜埃破门登贝莱替补送助”为切入点完整演示了如何用Python对足球比赛事件数据进行清洗、统计分析和可视化。我们设计了事件表结构编写了数据清洗模块、指标计算模块和绘图模块并串联成一个可直接运行的主程序。通过这个项目你应该掌握了以下关键点如何把一场比赛的零散事件转成结构化DataFrame。如何用pandas进行去重、类型转换、排序等清洗操作。如何统计球队进球、球员助攻、替补贡献度等指标。如何用matplotlib绘制柱状图和进攻时间分布折线图。如何排查CSV读取、中文乱码、时间字段转换等常见问题。下一步你可以尝试把这套代码应用到更多比赛数据中比如收集某支球队整个赛季的主客场比赛事件分析主场和客场的进攻节奏差异或者引入球员射门位置坐标绘制射门热力图。你也可以将清洗结果写入数据库配合Web框架做一套线上赛事分析看板。如果你在运行过程中遇到其他问题欢迎对照本文的排查思路逐步分析。动手把这些代码跑一遍再换成你自己的数据会比只看文章更有收获。
返回列表