
看比赛时我们习惯只关注最终比分。比如 NIP 2-1 WBG 这场 BO3比分很清楚但比赛过程呢每一局的经济走势、选手的击杀参与、两边的资源控制以及赛后观众给出的评分这些信息散落在不同的数据源里很难靠肉眼统一复盘。这篇文章就来做一个“电竞比赛数据复盘”的小项目用 Python 把一场 BO3 的选手数据整理成结构化表格计算核心指标再结合虎扑评分的思路生成可视化报告。整套流程不依赖商业平台字段来源清晰适合刚入门 pandas 的开发者也适合赛后想写更专业复盘内容的爱好者。1. 背景与核心概念1.1 什么是比赛数据复盘电竞比赛数据复盘简单说就是利用比赛过程中产生的客观数据来还原对局过程。比分只能回答“谁赢了”但数据可以进一步回答“为什么能赢”哪一路在前期建立了优势哪一方更早控制小龙哪位选手打出了关键伤害哪支队伍在后期决策上出现了失误。常见的比赛数据指标包括KDA击杀、死亡、助攻的综合值用来衡量选手参与战斗的效率。分均经济每分钟获取的金币数量反映选手发育速度。分均伤害每分钟打出的伤害值反映选手的输出能力。视野得分插眼、排眼带来的地图控制能力。团队资源控制防御塔、小龙、大龙、峡谷先锋等中立资源。单个指标很难说明问题但把多个指标组合起来就能形成一套完整的“数据复盘报告”。这也是赛后分析、队伍状态评估、版本理解、粉丝讨论中经常用到的方法。1.2 虎扑评分在复盘中的意义虎扑评分是观众对选手单场表现的主观打分。这类数据有一个特点它不完全等同于表现数据但能反映“观众眼中谁发挥更好”。比如一名选手可能数据中规中矩但因为关键团战的操作亮眼拿到很高的评分反过来一名选手数据不差可因为关键失误葬送好局评分会明显偏低。在数据复盘中把客观数据KDA、经济、伤害和主观评分放在一起看可以交叉验证很多问题高评分是否真的对应高 KDA高伤害选手是否一定获得高评分输掉比赛的队伍中有没有评分明显偏高的“尽力局”选手所以虎扑评分不是标准答案而是一份额外的“观众视角数据”。把它纳入分析流程能让复盘内容更有说服力也更贴近真实讨论场景。1.3 为什么选择 Python 来做做这种量级的数据分析不需要引入太重的大数据框架。Python 的 pandas 可以快速完成数据读取、清洗、分组聚合matplotlib 可以绘制评分对比、趋势变化等图表。整个过程在一个脚本里就能完成也方便后续扩展成定时任务或可视化看板。本文的示例会围绕下面几个目标展开用结构化数据描述一场 BO3 的比赛。用 pandas 计算 KDA、分均经济、分均伤害等指标。用 matplotlib 生成评分柱状图、分均经济趋势图和评分分布图。结合“观众评分”字段做简单的用户倾向分析。2. 环境准备与项目结构2.1 环境要求本文示例使用 Python 3 编写建议使用 Python 3.9 及以上版本。操作系统不限Windows、macOS、Linux 都可以。如果你本机没有安装 Python可以去 Python 官网下载安装包安装时记得勾选“Add Python to PATH”。为了避免和全局环境冲突建议创建一个虚拟环境python -m venv venvWindows 下激活虚拟环境venv\Scripts\activatemacOS 或 Linux 下激活虚拟环境source venv/bin/activate激活之后命令行提示符前面会出现(venv)字样。2.2 安装依赖本文主要用到三个库pandas、numpy、matplotlib。安装命令如下pip install pandas numpy matplotlib如果想一次性复现本文环境可以在项目目录下创建requirements.txtpandas numpy matplotlib然后执行pip install -r requirements.txt版本不需要完全一致只要这三个库能正常 import 即可。数据分析代码对版本兼容性要求并不高但如果遇到方法不存在的情况优先考虑升级库版本。2.3 项目目录结构建议创建一个独立项目目录方便管理原始数据和输出结果lol-match-analysis/ ├── data/ # 存放原始数据 │ └── nip_vs_wbg_bo3.csv # 示例比赛数据 ├── output/ # 存放可视化图片和结果文件 ├── generate_sample_data.py # 生成示例数据的脚本 ├── analysis.py # 核心分析脚本 └── requirements.txt # 依赖清单data目录用来放 CSV 数据output目录用来放图表和最终结果。代码和数据分开之后后续替换真实比赛数据时不需要修改分析逻辑。3. 数据收集与字段设计3.1 需要哪些字段做比赛数据复盘之前第一步是设计数据表结构。本文使用一份记录选手单局表现的演示数据字段如下字段名类型说明gameint第几局比赛teamstr队伍名称sidestr蓝色方 / 红色方playerstr选手标识rolestr位置killsint击杀数deathsint死亡数assistsint助攻数goldint总经济damageint总伤害duration_minfloat比赛时长分钟scorefloat观众评分模拟虎扑评分需要说明的是本文中的score字段用于模拟“观众评分”这一类数据。实际项目中评分数据可以从公开渠道整理后形成 CSV但要遵守对应平台的规则不绕过登录、不暴力请求、不采集非公开数据只使用自己有权使用的数据。3.2 生成示例数据为了让演示脚本可以独立运行我写了一个generate_sample_data.py它会生成一份完整的 BO3 数据。这里的核心思路是为每一局每一支队伍设置一个“表现强度系数”让不同局的表现产生差异从而模拟出类似 2-1 的比赛走势。# 文件路径generate_sample_data.py # 作用生成演示用 BO3 比赛数据仅用于展示数据分析方法。 import pandas as pd import numpy as np np.random.seed(2025) games [1, 2, 3] teams [NIP, WBG] roles [TOP, JUG, MID, ADC, SUP] sides {NIP: blue, WBG: red} rows [] # 模拟 2-1 的赛果表现 # NIP 在第 1 局、第 3 局表现更好WBG 在第 2 局表现更好。 # 注意这是演示数据不代表真实比赛过程。 game_strength { (1, NIP): 1.30, (1, WBG): 0.90, (2, NIP): 0.80, (2, WBG): 1.20, (3, NIP): 1.25, (3, WBG): 0.95, } for game in games: for team in teams: strength game_strength[(game, team)] for role in roles: kills int(np.random.poisson(2.2) * strength) deaths int(np.random.poisson(2.0) * (2 - strength)) assists int(np.random.poisson(3.0) * strength) gold int(np.random.normal(12000, 2000) * strength) damage int(np.random.normal(18000, 4000) * strength) duration_min float(round(np.random.uniform(25, 38), 1)) # 表现强度越高评分均值越高同时添加随机波动 score round(float(np.clip( 7.0 (strength - 1) * 4 np.random.normal(0, 0.5), 5.0, 10.0 )), 1) rows.append({ game: game, team: team, side: sides[team], player: f{team}_{role}, role: role, kills: kills, deaths: deaths, assists: assists, gold: gold, damage: damage, duration_min: duration_min, score: score, }) df pd.DataFrame(rows) df.to_csv(data/nip_vs_wbg_bo3.csv, indexFalse, encodingutf-8-sig) print(示例数据已生成共, len(df), 行) print(df.head(10))执行生成脚本python generate_sample_data.py运行之后data目录下会出现nip_vs_wbg_bo3.csv。这份数据一共 30 行也就是 3 局 × 2 支队伍 × 5 名选手。utf-8-sig编码可以避免 Excel 打开 CSV 时出现中文乱码这一点在实际项目中比较实用。如果你有自己的比赛数据建议按照同样的字段结构整理成 CSV后续分析脚本可以直接复用。3.3 读取与预处理分析脚本的第一步是读取 CSV 并做基础检查。这里重点确认三件事数据行数是否正确、字段类型是否符合预期、是否存在缺失值。# 文件路径analysis.py前半部分 import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False df pd.read_csv(data/nip_vs_wbg_bo3.csv) print(数据总行数:, len(df)) print(字段列表:, list(df.columns)) print(\n数据基本信息:) print(df.info()) print(\n缺失值数量:) print(df.isnull().sum())df.info()可以看到每一列的非空数量和数据类型。正常情况下数值列应该是int64或float64队伍和位置列是object。如果发现某列类型不对比如kills被读成了字符串可以在读取后手动转换df[kills] pd.to_numeric(df[kills], errorscoerce)errorscoerce会把无法转换的值变成 NaN方便后续统一处理。4. 比赛核心指标计算4.1 计算个人 KDAKDA 是复盘中最常用的指标公式是KDA (击杀数 助攻数) / 死亡数死亡数不能为 0否则会出现除零错误。实际比赛里确实存在“一次不死”的选手所以计算时需要做兜底处理。# 文件路径analysis.py延续 # 计算个人 KDA死亡数为 0 时按 1 处理避免除零 df[kda] (df[kills] df[assists]) / df[deaths].clip(lower1) # 计算分均经济和分均伤害 df[gpm] df[gold] / df[duration_min] df[dpm] df[damage] / df[duration_min] print(\n前 5 行核心指标) print(df[[player, role, kills, deaths, assists, kda, gpm, dpm]].head())clip(lower1)的作用是当deaths为 0 时用 1 参与运算。这样既避免了除零异常又不会让死亡数为 0 的选手 KDA 变成无穷大。4.2 按队伍和局数汇总比赛复盘不能只看选手个体还要看团队维度。按“局数 队伍”分组可以汇总出每一局两边团队的总击杀、总经济、平均评分等数据。# 按队伍和比赛局汇总团队数据 team_game df.groupby([game, team]).agg( total_kills(kills, sum), total_deaths(deaths, sum), total_assists(assists, sum), total_gold(gold, sum), avg_gpm(gpm, mean), avg_score(score, mean), ).reset_index() print(\n每局团队汇总) print(team_game)输出结果会是一个 6 行左右的表格每一行对应某一局中某支队伍的汇总数据。通过total_kills和total_deaths可以判断这一局哪支队伍在击杀上占优通过avg_score可以看出观众对整体表现的评分。4.3 汇总每位选手的分析结果为了方便后续查看和输出报告可以把计算完的指标保存成新的 CSV# 保存分析结果便于 Excel 或后续可视化使用 df.to_csv(data/nip_vs_wbg_bo3_analysis.csv, indexFalse, encodingutf-8-sig) print(\n分析结果已保存到 data/nip_vs_wbg_bo3_analysis.csv)这一步不是必须的但在实际项目中很有用。保存中间结果之后后面再做可视化或写报告时不需要重新执行一遍计算。5. 数据可视化数据分析完成后图表能让结论更直观。本文绘制三张图选手评分对比、团队分均经济趋势、评分分布直方图。5.1 选手评分对比柱状图评分数据适合用柱状图展示。横轴是“选手 局数”纵轴是评分一眼就能看出哪些选手在观众眼中表现更好。# 文件路径analysis.py可视化部分 # 1. 选手评分对比柱状图 df_sorted df.sort_values(score, ascendingFalse) fig, ax plt.subplots(figsize(12, 5)) ax.bar( df_sorted[player] G df_sorted[game].astype(str), df_sorted[score], color#4C72B0 ) ax.set_title(选手评分对比示例数据) ax.set_xlabel(选手局数) ax.set_ylabel(评分) plt.xticks(rotation45, haright) plt.tight_layout() plt.savefig(output/player_score_bar.png, dpi150) plt.show()这里把player和game拼在一起是为了区分同一名选手在不同局的表现。实际项目中如果你想聚焦某一局可以先用df[df[game] 1]过滤数据。5.2 团队分均经济趋势折线图分均经济能反映队伍整体发育速度。把三局比赛、两支队伍的分均经济画成折线图可以观察哪支队伍在前中期的经济获取效率更高。# 2. 团队分均经济趋势折线图 pivot_gpm team_game.pivot(indexgame, columnsteam, valuesavg_gpm) pivot_gpm.plot(kindline, markero, figsize(8, 5)) plt.title(团队分均经济变化示例数据) plt.xlabel(局数) plt.ylabel(分均经济) plt.grid(True) plt.tight_layout() plt.savefig(output/team_gpm_line.png, dpi150) plt.show()使用pivot之后行是局数列是队伍值是该队伍在这一局中的平均分均经济。这种方式很适合多支队伍对比。5.3 观众评分分布直方图评分分布可以反映观众整体打分倾向。如果大部分评分集中在 7 到 9 分说明观众认为选手整体发挥不错如果分布非常分散说明不同观众对同一场比赛的观感差异很大。# 3. 观众评分分布直方图 fig, ax plt.subplots(figsize(8, 5)) ax.hist(df[score], bins8, edgecolorblack, alpha0.7) ax.set_title(观众评分分布示例数据) ax.set_xlabel(评分) ax.set_ylabel(人数) plt.tight_layout() plt.savefig(output/score_dist.png, dpi150) plt.show()运行完这 6 个步骤后output目录下会出现三张图片。CSDN 写作场景中你可以直接引用这些截图日常复盘时也可以把它们当作分析结论的附件。6. 结合观众评分做用户倾向分析6.1 评分统计描述先对评分字段做一组描述性统计了解整体分数水平。# 文件路径analysis.py评分分析部分 print(\n按队伍统计观众评分) score_desc df.groupby(team)[score].describe() print(score_desc) print(\n按位置统计平均评分) role_score df.groupby(role)[score].mean().sort_values(ascendingFalse) print(role_score)describe()会输出均值、标准差、最小值、四分位数、最大值。如果某支队伍的平均评分明显更高说明观众对该队伍整体发挥更认可。按位置分组可以进一步观察哪条路的选手更容易获得高评分比如 ADC 选手经常因为输出高而拿到高评分辅助选手则可能因为做视野更多而被关注。当然这取决于具体比赛内容。6.2 评分与表现数据的相关性把评分和 KDA、伤害、经济做相关性分析可以初步判断“观众评分是否和客观数据一致”。# 计算评分与核心表现指标的相关性 corr df[[kda, damage, gold, score]].corr() print(\n评分与表现指标相关性矩阵) print(corr)相关性矩阵中score和kda如果呈现正相关说明观众的打分和选手击杀、助攻数据比较一致如果相关性很弱说明观众评分可能更看重团战表现、关键时刻操作等数据之外的因素。这里需要注意相关性不等于因果关系。即便score和damage高度相关也不能直接说“伤害高所以评分高”。更合理的解释是伤害高的选手通常参与了更多团战更容易被观众注意到。6.3 输方队伍中的“高评分选手”一个很实用的分析角度是在输掉比赛的那一局中找出评分最高的选手。这类选手常被观众称为“尽力局选手”。# 找出每局评分最高的选手 idx_max df.groupby([game])[score].idxmax() best_players df.loc[idx_max, [game, team, player, score ]] print(\n每局评分最高选手) print(best_players)通过这个结果可以快速定位每一局观众心中的“最佳选手”。如果再结合该选手的 KDA、伤害数据就能写出一段更有内容的复盘解读。7. 常见问题与排查思路实际运行时可能会遇到一些环境或数据问题下面整理常见报错和解决思路。问题现象常见原因解决思路运行pip install提示找不到命令Python 未加入 PATH或未激活虚拟环境重新安装 Python 并勾选“Add Python to PATH”或先激活虚拟环境读取 CSV 后中文列名或数据乱码文件编码不是 UTF-8读取时指定编码pd.read_csv(xxx.csv, encodingutf-8)或encodinggbk图表中的中文变成方框matplotlib 默认字体不支持中文设置中文字体例如plt.rcParams[font.sans-serif] [SimHei]图表的负号显示异常中文字体导致负号映射错误加上plt.rcParams[axes.unicode_minus] FalseFileNotFoundError: data/nip_vs_wbg_bo3.csv没有先运行生成脚本或运行路径不对先执行python generate_sample_data.py并确保在项目根目录运行脚本deaths为 0 导致 KDA 无穷大死亡数为 0 时除零使用df[deaths].clip(lower1)做保护CSV 某列类型不符合预期数据中包含非数字字符使用pd.to_numeric(..., errorscoerce)转换并检查 NaN这里最容易被忽略的是“工作目录”。如果在 IDE 中直接运行脚本当前工作目录可能不是项目根目录。建议在代码开头输出一下当前路径避免路径问题影响排错。8. 最佳实践与工程建议8.1 数据获取要合规电竞数据复盘的第一步是拿数据。不要为了获取数据去绕过登录、破解加密参数或高频请求公开接口。更稳妥的方式是使用官方公开数据接口。手动从观赛页面整理数据。使用自己实际参与或授权的数据。本文中的示例数据只是用于演示分析流程并不代表真实比赛数据。替换成真实数据前务必确认数据来源的合规性。8.2 数据文件和分析脚本分离把数据放在data/目录、代码放在根目录、图表输出到output/可以让项目结构更清晰。数据变更时只需要替换 CSV 文件不用修改代码反之代码升级时也不会影响已有数据。8.3 避免硬编码路径脚本里出现data/nip_vs_wbg_bo3.csv这种相对路径一定不能在任意目录下运行。更规范的做法是用pathlib拼接路径from pathlib import Path BASE_DIR Path(__file__).resolve().parent DATA_FILE BASE_DIR / data / nip_vs_wbg_bo3.csv OUTPUT_DIR BASE_DIR / output OUTPUT_DIR.mkdir(exist_okTrue) df pd.read_csv(DATA_FILE)这样无论你在哪个目录下执行脚本都能正确定位文件。8.4 异常处理要放在关键位置读取外部文件时建议加一个简单的异常处理至少让报错信息更容易理解try: df pd.read_csv(DATA_FILE) except FileNotFoundError: print(数据文件不存在请先运行 generate_sample_data.py 生成示例数据。) raise实际项目中异常处理不需要覆盖每一行但文件读取、数据转换、核心计算结果这三类位置值得重点关注。8.5 输出报告要有版本意识同一场比赛的数据可能被反复分析。建议在输出文件命名时加上日期或局数比如output/20250101_nip_vs_wbg_bo3_score_bar.png这样能避免旧图表被覆盖也方便复盘时对照不同版本的数据。9. 总结与学习路线这篇文章以 NIP 2-1 WBG 这场 BO3 为背景完成了一条完整的电竞数据复盘链路从环境准备、字段设计、模拟数据生成到读取 CSV、计算 KDA 和分均经济、绘制评分柱状图和趋势图再到结合观众评分做用户倾向分析。整套代码全部基于 Python 的 pandas、numpy、matplotlib代码量不大但结构完整替换成自己的数据后可以直接复用。下一步可以继续学习的内容包括用 pandas 处理更复杂的多表数据例如对局事件明细、选手历史数据。用合法的公开数据源构建更完整的数据集。用 Streamlit 或 Flask 把分析脚本封装成网页版复盘看板。用 pyecharts 生成更丰富的交互式图表。在真实项目落地时优先关注三件事数据来源是否合规、脚本路径是否稳定、输出报告是否有版本标识。这三个问题解决了复盘模板的可维护性就会好很多。建议你现在就动手把这份模板跑通再换一场自己熟悉的比赛试一下。数据格式不变分析方法不变你只需要替换 CSV 里的内容就能得到一份完全属于自己的比赛复盘报告。