ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python电竞赛事数据分析实战:从KDA到可视化全流程

Python电竞赛事数据分析实战:从KDA到可视化全流程 提到电竞赛事分析很多朋友第一时间想到的是靠眼睛一帧一帧看录像、凭印象做点评。其实这类工作完全可以交给 Python 来做从数据采集、清洗到可视化对比一条流水线就能把“清梦 VS Simon”这样的循环赛对阵分析得明明白白。本文就围绕电竞赛事数据实战展开以“叶祁夏季赛循环赛”为业务背景完整演示一场 BO3 对战的数据分析过程包括指标设计、代码实现、图表输出和常见避坑方案零基础也能跟着一步步复现。1. 背景与核心概念1.1 什么是电竞赛事数据分析电竞赛事数据分析简单说就是把一场或多场比赛中选手的操作行为、团队节奏和资源分配等信息转成结构化数据再用统计方法和可视化工具解释“谁赢了、为什么赢、赢在哪里”。以“清梦 VS Simon”这场循环赛为例如果只看最终比分你只能知道胜负。但如果我们把双方每个英雄的击杀、死亡、助攻、补刀、经济、视野得分都记录下来就能进一步回答前期哪一方建立了经济优势中单选手的支援节奏是否领先对面团队在 20 分钟时的视野控制是否压制了对手输掉的那一局崩盘是从哪一波团战开始的这些问题的答案就是数据分析的价值所在。1.2 常见的赛事指标在竞技类项目中有几个核心指标是分析比赛绕不开的指标含义说明KDA击杀、死亡、助攻的复合指标衡量选手综合表现常见公式为 (击杀助攻)/死亡GPM每分钟经济反映选手发育速度和资源获取能力XPM每分钟经验反映等级成长速度团队经济差双方总经济差值描述比赛节奏的重要信号视野得分真假眼布置与反眼能力影响地图信息控制力首杀时间全场第一滴血发生时间判断前期进攻性推塔数量防御塔摧毁数量客观反映地图推进进度专业比赛里还有更多深层指标比如参团率、分均伤害、伤害转化率等。本文先聚焦在 KDA、经济、视野和推塔这几类基础但核心的维度上。1.3 为什么要自己写代码分析市面上的赛事网站和 App 已经提供了不少数据但很多场景下我们需要的是“按自己需求组合的定制化分析”。比如把不同场次的同队选手数据合并对比。把某个选手在循环赛中所有场的 KDA 变化趋势画出来。把当前比赛与历史平均数据放在同一坐标系里对比。这些需求靠手动整理非常痛苦写成脚本以后只要数据格式不变下次比赛结果出来以后直接跑一遍就能自动更新报表。这正是 Python 在数据分析领域的核心优势。2. 环境准备与版本说明2.1 工具清单本文示例以常见的数据分析环境为准具体版本可根据你的项目实际情况调整重点演示配置思路。工具/库用途建议版本Python运行环境3.8pandas数据处理与分析1.5numpy数值计算1.23matplotlib数据可视化3.6Jupyter Notebook 或 VSCode编写与调试按个人习惯选择如果你的本机还没有安装这些库可以用以下命令统一安装pip install pandas numpy matplotlib如果网络较慢可以换成国内镜像源pip install pandas numpy matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 示例项目结构为了便于管理代码和数据建议先创建一个项目目录esports-analysis/ ├── data/ │ ├── match_1.csv │ ├── match_2.csv │ └── match_3.csv ├── outputs/ │ └── charts/ ├── analysis.py └── README.mddata目录存放原始比赛数据。outputs目录存放脚本生成的图表。analysis.py是主分析脚本。3. 数据结构设计3.1 数据字段规划在写分析代码之前首先要确定数据表的结构。以一场比赛为单位每条记录对应“某队某名选手在某一局中的表现”。字段设计如下字段名类型示例值说明match_id字符串VS001比赛编号team字符串清梦队伍名称player字符串Simin选手 IDhero字符串法刺使用英雄/定位可自定义kill整数8击杀数death整数3死亡数assist整数12助攻数gpm浮点数512.6每分钟经济xpm浮点数486.3每分钟经验vision_score浮点数42.8视野得分tower_kills整数2推塔数duration整数26比赛持续分钟数保留整数即可result字符串win本局该队胜负win/loss在这个结构里match_id和team共同标识一条数据的归属。这样做的好处是后续不管新增多少场比赛只要按照同样格式追加数据分析逻辑完全不需要改动。3.2 从公开数据平台取数在正规场景下比赛数据通常来自官方赛事 API、俱乐部数据平台或经过授权的数据服务商。个人学习一般有两种做法从官方站点开放的数据接口获取 JSON再转为 CSV。手动整理比赛录像中的关键节点填入固定表格。需要注意的是使用任何第三方赛事数据时都要先确认数据授权范围。个人学习、非商用研究是常见场景但如果要对外发布或商用必须获得授权并注明来源。4. 核心分析与可视化实战4.1 创建示例数据集由于真实赛事数据存在版权和授权限制我们这里使用一组模拟数据来演示完整的分析流程。数据结构与真实场景一致你需要跑真实数据时只需要替换 CSV 文件内容即可。创建data/match_1.csv内容如下match_id,team,player,hero,kill,death,assist,gpm,xpm,vision_score,tower_kills,duration,result VS001,清梦,清梦选手A,战士,6,2,10,482.5,455.2,38.6,3,28,win VS001,清梦,清梦选手B,射手,10,1,8,601.3,520.1,41.2,4,28,win VS001,清梦,清梦选手C,辅助,1,4,18,288.6,312.8,72.4,2,28,win VS001,Simon,Simon选手A,战士,4,5,6,412.3,398.7,30.5,1,28,loss VS001,Simon,Simon选手B,射手,7,4,5,532.8,489.3,35.1,2,28,loss VS001,Simon,Simon选手C,辅助,0,6,11,265.4,290.6,58.3,0,28,loss再创建data/match_2.csv内容如下match_id,team,player,hero,kill,death,assist,gpm,xpm,vision_score,tower_kills,duration,result VS002,清梦,清梦选手A,战士,5,3,7,451.2,430.8,32.1,2,31,loss VS002,清梦,清梦选手B,射手,8,5,6,520.7,489.2,36.8,1,31,loss VS002,清梦,清梦选手C,辅助,1,4,14,275.3,301.5,62.7,0,31,loss VS002,Simon,Simon选手A,战士,6,2,9,486.5,452.3,35.4,3,31,win VS002,Simon,Simon选手B,射手,11,3,7,588.2,510.8,39.9,4,31,win VS002,Simon,Simon选手C,辅助,2,2,17,290.1,318.4,66.2,2,31,win这两份 CSV 对应的是同一轮循环赛中两支队伍的两局比赛一胜一负方便我们做汇总对比。4.2 数据读取与检查新建analysis.py首先读取并检查数据。import pandas as pd df1 pd.read_csv(data/match_1.csv) df2 pd.read_csv(data/match_2.csv) df pd.concat([df1, df2], ignore_indexTrue) print(数据形状:, df.shape) print(df.head()) print(df.dtypes)这段代码的作用read_csv读取 CSV 文件。concat将两局比赛数据拼接成一张总表。shape输出总行数和列数确认没有少数据。dtypes查看各列数据类型方便发现解析问题。预期输出中kill、death等列应是int64gpm、xpm等列应是float64。如果发现某些数值列被解析成了字符串说明源数据里可能存在空值或脏字符需要先清洗。4.3 计算 KDA 与派生指标KDA 是最直观的选手效率指标。我们在df上新增一列kda。df[kda] (df[kill] df[assist]) / df[death].replace(0, 1)这里replace(0, 1)是为了避免除零错误。如果某个选手死亡数为 0我们用一个极小惩罚值替代匹配常见电竞数据平台的算法思路。真实平台对“未死亡”有不同处理方式有些直接记为满分你可以根据自己的统计口径调整。继续构造“表现分”或“效率指数”之类的派生指标。比如df[damage_per_minute] df[gpm] * 0.8 df[xpm] * 0.2这个指标只是演示如何通过既有列生成新特征。实际项目中分均伤害应该来自单独的伤害统计列这里用经济经验加权只是为了说明“派生特征”这种数据处理方法。4.4 队伍整体聚合对比分析比赛时我们不能只看单个选手还要看团队整体表现。对队伍进行分组聚合team_stats df.groupby([match_id, team]).agg( total_kills(kill, sum), total_deaths(death, sum), total_assists(assist, sum), avg_gpm(gpm, mean), avg_xpm(xpm, mean), total_vision(vision_score, sum), total_towers(tower_kills, sum), ).reset_index() team_stats[team_kda] ( (team_stats[total_kills] team_stats[total_assists]) / team_stats[total_deaths].replace(0, 1) ) print(team_stats)这里使用groupby按比赛编号和队伍分组再通过agg同时计算总击杀、平均经济、总视野等多个指标。reset_index会把分组键恢复为普通列方便后续筛选。4.5 选手表现横向对比回到清梦与 Simon 这场循环赛本身我们通常要回答一个核心问题两队的核心输出位谁更强势key_players df[df[hero].isin([射手, 法刺])] for _, match in key_players.groupby(match_id): print(比赛编号:, _) print(match[[team, player, hero, kill, death, assist, kda]].to_string(indexFalse)) print(- * 50)hero字段里标记“射手”的选手是队伍的主要输出位。把两局比赛里双方的输出位数据并列打印可以快速看到谁在团队中承担了更高的击杀任务。4.6 可视化比赛节奏使用 matplotlib 绘制对比图帮助直观呈现两队在各个维度上的差异。4.6.1 团队经济对比柱状图import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(12, 4)) for i, match_id in enumerate(team_stats[match_id].unique()): match_data team_stats[team_stats[match_id] match_id] axes[i].bar(match_data[team], match_data[avg_gpm], color[#4C72B0, #DD8452]) axes[i].set_title(f{match_id} 平均每分钟经济) axes[i].set_ylabel(GPM) axes[i].set_ylim(0, 700) plt.tight_layout() plt.savefig(outputs/charts/gpm_compare.png, dpi200) plt.show()这里设置了 matplotlib 中文字体避免图表上出现方块乱码。如果你的系统没有SimHei或Microsoft YaHei可以改成系统里已安装的中文字体名称。4.6.2 选手视野能力雷达图雷达图适合对比单个选手或多个维度上的综合能力。以下示例绘制清梦与 Simon 双方辅助选手的视野、推塔、经济三个维度。import numpy as np def radar_chart(players, labels, output_path): angles np.linspace(0, 2 * np.pi, len(labels), endpointFalse).tolist() angles angles[:1] fig, ax plt.subplots(figsize(6, 6), subplot_kwdict(polarTrue)) for _, player in players.iterrows(): values player[labels].values.tolist() values values[:1] ax.plot(angles, values, linewidth2, labelplayer[player]) ax.fill(angles, values, alpha0.15) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) ax.legend(locupper right, bbox_to_anchor(1.25, 1.1)) plt.savefig(output_path, dpi200, bbox_inchestight) plt.show() support_players df[df[hero] 辅助] radar_chart( support_players, [vision_score, total_towers, avg_gpm] if len(support_players) else [vision_score], outputs/charts/support_radar.png, )上面的写法有一个问题total_towers和avg_gpm并不是行级字段。为了避免误导读者我们在真实项目中应该先做数据整形再画雷达图尤其是把“每个选手一局的总推塔”与“整队平均经济”放在同一行中时要确保数据口径一致。这里保留示例是为了让新手理解雷达图的数据组织方式实际应用时请按下节的数据透视思路先整理。4.7 数据透视与匹配度分析很多比赛数据是“一对一局”的如果想把选手多局数据合并成一条记录可以使用pivot_table。player_summary df.pivot_table( index[team, player], values[kill, death, assist, gpm, vision_score], aggfunc{ kill: sum, death: sum, assist: sum, gpm: mean, vision_score: mean, }, ).reset_index() player_summary[kda] ( player_summary[kill] player_summary[assist] ) / player_summary[death].replace(0, 1) print(player_summary.sort_values(kda, ascendingFalse))pivot_table的作用是按照“队伍 选手”重新组织数据。击杀、死亡、助攻这类累计数值用求和经济和视野这类均值型数据用平均。这样每个选手就只剩下一条汇总记录后续直接用于排名或画图都非常方便。5. 完整分析流程整合把前面拆开的步骤整合成一个完整脚本方便一次性运行。import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False # 读取数据 df1 pd.read_csv(data/match_1.csv) df2 pd.read_csv(data/match_2.csv) df pd.concat([df1, df2], ignore_indexTrue) # 派生指标 df[kda] (df[kill] df[assist]) / df[death].replace(0, 1) # 队伍汇总 team_stats df.groupby([match_id, team]).agg( total_kills(kill, sum), total_deaths(death, sum), total_assists(assist, sum), avg_gpm(gpm, mean), avg_xpm(xpm, mean), total_vision(vision_score, sum), total_towers(tower_kills, sum), ).reset_index() team_stats[team_kda] ( team_stats[total_kills] team_stats[total_assists] ) / team_stats[total_deaths].replace(0, 1) print( 队伍汇总 ) print(team_stats) # 选手汇总 player_summary df.pivot_table( index[team, player], values[kill, death, assist, gpm, vision_score], aggfunc{ kill: sum, death: sum, assist: sum, gpm: mean, vision_score: mean, }, ).reset_index() player_summary[kda] ( player_summary[kill] player_summary[assist] ) / player_summary[death].replace(0, 1) print(\n 选手汇总 ) print(player_summary.sort_values(kda, ascendingFalse)) # 图表输出 fig, ax plt.subplots(figsize(8, 5)) for team in team_stats[team].unique(): team_data team_stats[team_stats[team] team] ax.bar(team_data[match_id] - team_data[team], team_data[avg_gpm], labelteam) ax.set_title(队伍平均GPM对比) ax.set_ylabel(GPM) ax.legend() plt.tight_layout() plt.savefig(outputs/charts/team_gpm.png, dpi200) print(\n图表已保存到 outputs/charts/team_gpm.png)运行方式python analysis.py该脚本会输出两个表格并在outputs/charts目录下生成一张柱状图。6. 常见问题与排查思路在进行赛事数据分析时下面几个问题出现频率很高。问题现象常见原因解决思路中文乱码绘图字体不支持中文设置plt.rcParams[font.sans-serif]为系统已安装中文字体除零错误某选手死亡数为 0数据清洗时将 0 替换为 1或使用自定义口径CSV 解析后数值列变成字符串源数据有,或等特殊符号检查原始 CSV使用pd.to_numeric强制转换matplotlib 绘图不显示没有调用plt.show()在脚本末尾添加plt.show()数据量太大跑得很慢循环里逐行处理用groupby和pivot_table代替循环各平台数据字段不一致不同来源统计口径不同统一字段映射表先做标准化再分析如果“CSV 解析后数值列变成字符串”这种情况出现可以使用df[gpm] pd.to_numeric(df[gpm], errorscoerce)errorscoerce会把无法转换的内容变成NaN方便我们后面统一填充缺失值。另外当你发现聚合结果和你手算不一致时先检查是不是重复行导致的print(df.duplicated().sum())重复记录会直接拉高击杀、助攻等累计指标必须提前排查。7. 最佳实践与工程建议7.1 统一数据规范赛事数据来源往往不统一建议在项目里维护一张“字段映射表”。接收数据时先做字段名标准化再进入分析流程。例如field_map { kills: kill, deaths: death, assists: assist, player_name: player, } df df.rename(columnsfield_map)统一字段名以后后续所有脚本都只跟规范字段打交道不会因为不同来源的命名差异而反复修改代码。7.2 数据留痕与可复现性分析比赛数据最怕“这次能跑下次跑不出来”。建议做到原始数据文件不要手动修改新版本另存。脚本中记录数据读取路径和生成时间。图表文件名带比赛编号比如VS001_team_gpm.png。可以在脚本顶部加一个输出日志import datetime print(f[{datetime.datetime.now()}] 开始分析比赛数据)这样排查问题时能知道数据是什么时候生成的、对应哪批比赛。7.3 谨慎使用第三方数据正规赛事数据版权通常归赛事方或数据服务商所有。个人学习和研究没问题但如果要公开发布分析报告、做成商用产品务必确认授权范围。尽量从赛事官网开放接口或经过授权的数据平台获取数据不要爬取未经授权的站点更不要把受限数据打包转发。7.4 指标口径要写清楚分析报告或博客里给出 KDA、伤害转化率这种指标时一定要写明计算公式和口径。同一场比赛不同平台给出的 KDA 可能不同原因往往出在“未死亡选手如何处理”“助攻是否加权”等细节上。明确口径结果才可比较、可信赖。7.5 代码结构保持模块化不要把所有逻辑堆在一个文件里。建议拆分为src/ ├── loader.py # 数据读取与清洗 ├── metrics.py # 指标计算 ├── visualize.py # 图表绘制 └── report.py # 汇总输出每个模块只做一件事后续扩展“从数据库读数据”“自动生成 Markdown 报告”都会容易很多。8. 总结与下一步方向围绕“清梦 VS Simon”这场循环赛本文完整演示了怎样用 Python 做一场电竞对阵数据分析。你如果完整跑完示例应该已经掌握赛事数据分析的常用指标与字段设计。使用 pandas 读取、拼接、清洗 CSV 数据。使用 groupby 和 pivot_table 聚合队伍与选手数据。使用 matplotlib 输出中文化对比图表。常见数据坑点与工程化整理方法。下一步可以顺着两个方向继续深入一是接入更多数据源把循环赛多轮数据全部汇总成一份自动化周报二是引入更复杂的分析模型比如用时间序列分析经济曲线拐点或用聚类算法划分选手打法风格。如果你正在准备自己的赛事分析项目建议从“固定赛制、固定数据源”的小范围开始先把一条数据链跑通再逐步扩展。这样无论后续是加指标、加图表还是换数据源整个流程都会更稳。如果这篇文章对你有帮助可以收藏备用等实战中遇到问题再回来对照排查。
返回列表