尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python网络分析实战:从足球传球数据到专业级网络图可视化

Python网络分析实战:从足球传球数据到专业级网络图可视化 1. 项目缘起从足球数据到网络图如果你参加过美国大学生数学建模竞赛MCM/ICM或者对数据分析和可视化感兴趣那么“足球传递网络图”这个题目一定不陌生。这不仅仅是画一张图那么简单它背后是一整套从原始数据清洗、到网络模型构建、再到高级可视化和深入分析的完整流程。很多同学拿到一堆球员传球数据第一反应就是用Excel画个简单的连线图但这离竞赛要求的高质量、可解释、有洞察力的网络分析还差得很远。网络图的核心价值在于它能将抽象的传球关系谁传给了谁传了多少次转化为直观的拓扑结构让你一眼看出球队的进攻核心、关键传球路线以及潜在的战术弱点。我最初接触这个题目时也走了不少弯路。比如直接用matplotlib的scatter和plot函数硬画结果代码冗长调整布局极其痛苦出来的图也毫无美感更别提进行复杂的网络指标计算了。后来我发现了networkx这个专门为复杂网络分析而生的Python库配合matplotlib进行绘图才真正打开了新世界的大门。然而工具链的搭建又是一道坎——Anaconda、Spyder、conda环境、各种包版本冲突……这些看似基础的问题往往最能消耗时间和消磨热情。所以这篇内容我想从一个过来人的角度不仅分享如何用networkx和matplotlib做出专业级的足球传球网络图更会详细拆解整个环境搭建、数据处理、绘图美化到深度分析的全流程特别是那些官方文档里不会写的“坑”和“技巧”。2. 环境搭建避开Anaconda与Conda的常见陷阱工欲善其事必先利其器。一个稳定、隔离的Python环境是项目成功的基石。对于科学计算和数据分析Anaconda发行版是很多人的首选因为它集成了Spyder、Jupyter Notebook等工具和大量预装科学计算包。但安装和配置过程尤其是网络环境特殊的情况下堪称新手的第一道“劝退关卡”。2.1 Anaconda安装与国内镜像配置直接从Anaconda官网下载安装器速度可能慢如蜗牛甚至中途失败。最稳妥的方法是使用国内镜像源。以清华大学开源软件镜像站为例具体步骤如下下载访问清华镜像站Anaconda页面选择与你的操作系统Windows/macOS/Linux和系统位数通常是64位对应的最新版本安装包。注意优先选择图形化安装程序.exe或.pkg。安装安装过程中有两个关键选项务必注意“Add Anaconda to my PATH environment variable”不建议勾选。勾选后可能会与你系统已有的Python环境产生冲突导致命令行混乱。我们后续通过Anaconda自带的“Anaconda Prompt”或配置好的终端来使用conda即可。“Register Anaconda as my default Python”可以勾选。这会让Anaconda的Python成为系统默认通常问题不大。验证安装安装完成后在Windows开始菜单中找到并打开“Anaconda Prompt (Anaconda3)”。这是一个已经配置好conda环境的命令行窗口。输入conda --version和python --version能正确显示版本号即表示安装成功。安装后为了后续安装其他包时速度更快必须立即配置conda的国内镜像通道。在Anaconda Prompt中依次执行以下命令conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ conda config --set show_channel_urls yes注意曾经流行的https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/通道可能已失效或内容不全如果你遇到“unavailableinvalidchannel: http 404 not found for channel anaconda/pkgs/free”这类错误请直接使用上面推荐的conda-forge和main通道它们更全、更新。conda-forge社区维护了大量高质量的软件包是首选的补充通道。2.2 创建专属的虚拟环境永远不要在base基础环境下直接进行项目开发创建一个独立的虚拟环境可以完美隔离不同项目所需的包版本避免冲突。我们为足球网络分析项目创建一个新环境conda create -n soccer_network python3.9这里-n soccer_network指定了环境名称python3.9指定了Python版本3.8或3.9都是稳定兼容的选择。创建过程中会提示确认输入y即可。创建完成后激活该环境conda activate soccer_network激活后命令行提示符前通常会显示环境名(soccer_network)表示你已进入该环境。所有后续的包安装和代码运行都应在此激活的环境下进行。2.3 安装核心工具包NetworkX, Matplotlib, NumPy在激活的soccer_network环境中安装我们所需的包conda install networkx matplotlib numpy pandas这里用一条命令同时安装了四个核心包。pandas虽然标题未直接提及但它是数据处理的事实标准用于读取和清洗原始的传球CSV或Excel数据必不可少。conda会自动解决这些包之间的依赖关系。常见问题与解决“CondaError: Run conda init before conda activate” 这说明你的shell如PowerShell或bash没有初始化conda。关闭当前终端重新打开“Anaconda Prompt”它已经初始化好了。如果想在普通PowerShell或CMD中使用conda你需要先在Anaconda Prompt中运行conda init powershell或conda init cmd.exe然后重启对应的终端。“The channel is not accessible” 通常是镜像地址有误或网络暂时性问题。请检查上述镜像地址是否正确并尝试使用conda config --remove channels 问题通道URL移除有问题的通道再重新添加正确的镜像。环境位置错误 如果你在特定目录下遇到“EnvironmentLocationNotFound: Not a conda environment”错误可能是因为你误在该目录下执行了激活命令。conda环境是全局管理的在任何位置直接conda activate soccer_network即可无需进入特定目录。2.4 选择IDESpyder还是其他Anaconda自带Spyder它是一个模仿MATLAB风格的轻量级IDE对于科学计算和数据分析非常友好变量浏览器功能很好用。如果你喜欢可以直接在开始菜单的Anaconda文件夹下找到“Spyder (soccer_network)”来启动如果创建了快捷方式或者先激活环境再在命令行输入spyder。如果你发现Anaconda安装后没有Spyder或者想用其他IDE如PyCharm, VSCode也完全没问题PyCharm配置在PyCharm中新建项目时选择“Previously configured interpreter”点击添加选择“Conda Environment”然后定位到你的Anaconda安装目录\envs\soccer_network\python.exe。VSCode配置在VSCode中按CtrlShiftP输入“Python: Select Interpreter”选择路径为...\envs\soccer_network\python的解释器。我个人在完成这类数据分析可视化的项目时更喜欢使用Jupyter Lab可通过conda install jupyterlab安装。因为它能分段执行代码、即时显示图表非常适合探索性数据分析和迭代调整可视化效果。你可以根据习惯选择。3. 数据准备与网络构建从传球列表到Graph对象假设我们有一份比赛传球数据passes.csv包含字段passer传球者receiver接球者minute比赛分钟x,y传球起始坐标end_x,end_y传球结束坐标success是否成功。我们的目标是构建一个加权有向图其中节点是球员边是从传球者指向接球者权重是传球次数。3.1 使用Pandas进行数据加载与清洗import pandas as pd # 加载数据 df pd.read_csv(passes.csv) # 基础清洗查看数据概览处理缺失值 print(df.info()) print(df.head()) # 假设我们只分析成功的传球 df_success df[df[success] True].copy() # 检查是否有球员名称为空或未知 df_success df_success.dropna(subset[passer, receiver])3.2 构建NetworkX图对象这是核心步骤。我们将使用pandas的groupby功能来统计球员之间的传球次数然后将其添加到图中。import networkx as nx # 创建一个有向图DiGraph因为传球方向很重要 G nx.DiGraph() # 方法一逐条遍历添加适用于小数据量或需要添加边属性时 # for _, row in df_success.iterrows(): # G.add_edge(row[passer], row[receiver], weight1) # 先默认权重为1 # # 然后需要再合并相同边的权重比较繁琐 # 方法二先聚合再添加高效推荐 # 统计每对传球者接球者的出现次数 pass_counts df_success.groupby([passer, receiver]).size().reset_index(nameweight) # 将聚合后的数据添加到图中 for _, row in pass_counts.iterrows(): G.add_edge(row[passer], row[receiver], weightrow[weight]) # 也可以使用 from_pandas_edgelist 一步到位但需要注意权重列 # G nx.from_pandas_edgelist(pass_counts, sourcepasser, targetreceiver, edge_attrweight, create_usingnx.DiGraph) print(f网络包含 {G.number_of_nodes()} 名球员节点和 {G.number_of_edges()} 条传球关系边。)3.3 为节点添加属性球员位置与角色为了让可视化更丰富我们通常希望根据球员的位置如后卫、中场、前锋或号码来设置不同的颜色或形状。假设我们有一个球员信息表players.csv包含name和position。player_info pd.read_csv(players.csv) position_dict player_info.set_index(name)[position].to_dict() # 将位置信息作为属性添加到每个节点上 nx.set_node_attributes(G, position_dict, position) # 验证查看某个节点的属性 print(G.nodes[梅西])4. 基础可视化用Matplotlib绘制第一张网络图有了图对象我们就可以进行可视化了。networkx提供了多种布局算法来排列节点matplotlib负责渲染。4.1 选择布局算法布局算法决定了节点的位置对图的可读性影响巨大。spring_layout 力导向布局模拟弹簧和斥力是默认也是最常用的布局能使连接紧密的节点聚集在一起。circular_layout 将所有节点均匀放在一个圆环上适合展示循环关系或强调所有节点平等。shell_layout 将节点放在多个同心圆上可以按节点属性如位置分层。kamada_kawai_layout 另一种力导向布局试图优化全局能量对于中小型图效果很好。对于足球传球网络我们通常希望核心球员传球枢纽能自然地位于中心spring_layout是一个不错的起点。import matplotlib.pyplot as plt # 设置中文字体如果需要显示中文标签 # plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 # plt.rcParams[axes.unicode_minus] False # 用来正常显示负号 # 计算节点位置 pos nx.spring_layout(G, seed42) # seed参数确保每次运行布局一致 # 绘制基本图形 plt.figure(figsize(12, 10)) # 设置画布大小 # 1. 绘制节点 nx.draw_networkx_nodes(G, pos, node_size500, node_colorlightblue, alpha0.9) # 2. 绘制边 nx.draw_networkx_edges(G, pos, edge_colorgray, width1.0, alpha0.7, arrowsTrue, arrowsize15) # 3. 绘制节点标签 nx.draw_networkx_labels(G, pos, font_size10, font_familysans-serif) plt.title(Basic Soccer Passing Network) plt.axis(off) # 关闭坐标轴 plt.tight_layout() plt.show()这张图已经能看出大概但信息量有限所有节点和边看起来都一样。4.2 根据网络属性增强可视化真正的洞察来自于根据图本身的属性来调整可视化元素。节点大小反映“重要性”我们可以用节点的“度中心性”Degree Centrality来衡量一名球员在传球网络中的直接影响力。入度In-Degree高代表接球多出度Out-Degree高代表传球发起多。通常用总度数。# 计算每个节点的度数有向图是入度出度 node_degree dict(G.degree()) # 或者使用出度强调传球组织者node_degree dict(G.out_degree()) # 或者使用入度强调终结者node_degree dict(G.in_degree()) # 将度数映射到节点大小例如乘以一个系数 node_sizes [node_degree[node] * 100 for node in G.nodes()] # 边宽度反映传球次数权重 edge_weights [G[u][v][weight] for u, v in G.edges()] # 将权重归一化到合适的宽度范围例如1-5 max_weight max(edge_weights) edge_widths [3 * w / max_weight for w in edge_weights]节点颜色反映球员位置我们需要一个将位置映射到颜色的字典。# 定义位置到颜色的映射 position_color_map { GK: yellow, # 门将 DF: green, # 后卫 MF: blue, # 中场 FW: red # 前锋 } # 为每个节点获取对应的颜色 node_colors [position_color_map.get(G.nodes[node].get(position, Unknown), gray) for node in G.nodes()]现在用这些属性重新绘图plt.figure(figsize(14, 12)) # 绘制节点带颜色和大小 nx.draw_networkx_nodes(G, pos, node_sizenode_sizes, node_colornode_colors, alpha0.9, edgecolorsblack, linewidths1) # 绘制边带宽度 nx.draw_networkx_edges(G, pos, edge_colordarkgray, widthedge_widths, alpha0.6, arrowsTrue, arrowsize15, connectionstylearc3,rad0.1) # 稍微弯曲的边避免重叠 # 绘制标签 nx.draw_networkx_labels(G, pos, font_size9, font_weightbold) plt.title(Enhanced Soccer Passing Network (Node Size Degree, Color Position, Edge Width Pass Count)) plt.axis(off) # 添加图例手动创建 from matplotlib.patches import Patch legend_elements [Patch(facecolorcolor, labelpos) for pos, color in position_color_map.items()] plt.legend(handleslegend_elements, locupper right) plt.tight_layout() plt.show()现在这张图的信息量就丰富多了我们可以一眼看出谁是传球核心大节点、前后场如何连接边、以及不同位置球员的分布。5. 高级分析与定制化绘图基础可视化之后我们需要深入挖掘网络指标并制作更具洞察力和出版质量的图表。5.1 计算关键网络指标这些指标是数学建模论文中定量分析的核心。# 1. 中心性指标 # 度中心性 (Degree Centrality) degree_cent nx.degree_centrality(G) # 标准化后的度数 # 接近中心性 (Closeness Centrality): 到网络中所有其他节点平均距离的倒数值高表示处于网络中心。 closeness_cent nx.closeness_centrality(G) # 中介中心性 (Betweenness Centrality): 衡量节点作为“桥梁”的重要性即多少最短路径经过该节点。 betweenness_cent nx.betweenness_centrality(G, weightweight) # 考虑权重 # 将结果存入DataFrame便于查看和排序 import pandas as pd cent_df pd.DataFrame({ Degree: degree_cent, Closeness: closeness_cent, Betweenness: betweenness_cent }) print(中心性指标排名前10:) print(cent_df.sort_values(byBetweenness, ascendingFalse).head(10)) # 2. 寻找关键边边中介中心性 edge_betweenness nx.edge_betweenness_centrality(G, weightweight) critical_edges sorted(edge_betweenness.items(), keylambda x: x[1], reverseTrue)[:5] print(\n关键边中介中心性最高:) for (u, v), score in critical_edges: print(f {u} - {v}: {score:.4f}) # 3. 社区发现球队内部的传球小组 # 使用Louvain算法需要安装python-louvain包: pip install python-louvain # import community as community_louvain # partition community_louvain.best_partition(G.to_undirected()) # 需要无向图 # nx.set_node_attributes(G, partition, community) # print(f发现了 {max(partition.values())1} 个社区。)5.2 绘制带指标标签的定制化网络图我们可以将计算出的中心性直接标注在图上或者用颜色映射来表现。import matplotlib.pyplot as plt import numpy as np plt.figure(figsize(16, 14)) # 使用中介中心性来决定节点颜色使用viridis色谱 betweenness_values list(betweenness_cent.values()) node_colors_between betweenness_values # 绘制节点颜色映射中介中心性大小映射度中心性 nodes nx.draw_networkx_nodes(G, pos, node_size[d * 3000 for d in degree_cent.values()], # 放大倍数 node_colornode_colors_between, cmapplt.cm.plasma, # 使用plasma色谱 alpha0.85, edgecolorswhite, linewidths1.5) # 绘制边用颜色区分关键边 edge_colors [lightgray] * len(G.edges()) for i, (u, v) in enumerate(G.edges()): if (u, v) in dict(critical_edges[:3]): # 高亮前3关键边 edge_colors[i] red nx.draw_networkx_edges(G, pos, edge_coloredge_colors, widthedge_widths, # 之前计算的宽度 alpha0.7, arrowsTrue, arrowsize18, connectionstylearc3,rad0.1) # 绘制标签可以只显示关键球员的标签避免重叠 important_players list(cent_df.nlargest(8, Betweenness).index) # 中介中心性前8 labels {node: node if node in important_players else for node in G.nodes()} nx.draw_networkx_labels(G, pos, labels, font_size11, font_weightbold) plt.title(Advanced Soccer Passing Network Analysis\n(Node Color Betweenness Centrality, Node Size Degree Centrality, Red Edges Most Critical), fontsize16) plt.axis(off) # 添加颜色条 sm plt.cm.ScalarMappable(cmapplt.cm.plasma, normplt.Normalize(vminmin(betweenness_values), vmaxmax(betweenness_values))) sm.set_array([]) cbar plt.colorbar(sm, axplt.gca(), shrink0.8, labelBetweenness Centrality) cbar.ax.tick_params(labelsize10) plt.tight_layout() plt.show()5.3 绘制辅助分析图表网络图本身可能信息过载配合一些统计图表能更好地传达信息。fig, axes plt.subplots(2, 2, figsize(15, 12)) # 子图1球员传球次数出度排名 out_degree dict(G.out_degree(weightweight)) # 加权出度 top_passers pd.Series(out_degree).nlargest(10) axes[0, 0].barh(range(len(top_passers)), top_passers.values, colorskyblue) axes[0, 0].set_yticks(range(len(top_passers))) axes[0, 0].set_yticklabels(top_passers.index) axes[0, 0].invert_yaxis() axes[0, 0].set_xlabel(Total Passes Made) axes[0, 0].set_title(Top 10 Passers (Out-Degree)) axes[0, 0].grid(axisx, linestyle--, alpha0.7) # 子图2球员接球次数入度排名 in_degree dict(G.in_degree(weightweight)) # 加权入度 top_receivers pd.Series(in_degree).nlargest(10) axes[0, 1].barh(range(len(top_receivers)), top_receivers.values, colorlightcoral) axes[0, 1].set_yticks(range(len(top_receivers))) axes[0, 1].set_yticklabels(top_receivers.index) axes[0, 1].invert_yaxis() axes[0, 1].set_xlabel(Total Passes Received) axes[0, 1].set_title(Top 10 Receivers (In-Degree)) axes[0, 1].grid(axisx, linestyle--, alpha0.7) # 子图3中心性指标散点图度 vs 中介 axes[1, 0].scatter(cent_df[Degree], cent_df[Betweenness], s100, alpha0.6, edgecolorsk) for player, row in cent_df.nlargest(5, Betweenness).iterrows(): # 标注前5 axes[1, 0].annotate(player, (row[Degree], row[Betweenness]), xytext(5,5), textcoordsoffset points) axes[1, 0].set_xlabel(Degree Centrality) axes[1, 0].set_ylabel(Betweenness Centrality) axes[1, 0].set_title(Degree vs. Betweenness Centrality) axes[1, 0].grid(True, linestyle--, alpha0.5) # 子图4边权重的分布直方图 axes[1, 1].hist(edge_weights, bins15, colormediumseagreen, edgecolorblack, alpha0.7) axes[1, 1].axvline(np.mean(edge_weights), colorred, linestyle--, linewidth2, labelfMean: {np.mean(edge_weights):.1f}) axes[1, 1].set_xlabel(Pass Count per Pair (Edge Weight)) axes[1, 1].set_ylabel(Frequency) axes[1, 1].set_title(Distribution of Passing Frequency Between Pairs) axes[1, 1].legend() axes[1, 1].grid(axisy, linestyle--, alpha0.7) plt.suptitle(Supplementary Analysis of Passing Network, fontsize18, y1.02) plt.tight_layout() plt.show()6. 实战心得与避坑指南走完整个流程你会发现从数据到见解的路上布满细小的“坑”。这里分享几个我踩过之后才明白的关键点1. 布局算法的“随机性”与复现性spring_layout默认是随机的每次运行节点位置都可能不同。这对于探索没问题但写论文时需要固定的图。务必设置seed参数如seed42来确保结果可复现。如果对布局不满意可以尝试k参数节点间最优距离和iterations参数来调整。2. 可视化美化是永无止境的默认的draw_networkx函数出来的图很“学术”。要做出美观的图需要精细调整几乎所有参数node_size,node_color,edge_color,width,alpha透明度,font_size,arrows箭头大小和样式。一个技巧是先画一个简单的草图然后逐个参数调整并频繁使用plt.show()查看效果。3. 标签重叠的解决之道当节点密集时标签会重叠得一塌糊涂。有几种策略 *选择性标注只显示最重要的节点如中心性最高的前N个的标签如上文代码所示。 *调整标签位置draw_networkx_labels有一个horizontalalignment和verticalalignment参数可以微调标签相对于节点的位置。 *使用偏移更高级的方法是计算一个避免重叠的标签位置但这需要额外算法。一个简单的替代方案是在pos字典的基础上为标签创建一个稍微偏移的新位置字典。4. 处理大规模网络如果分析整场比赛所有球员包括对手的传球节点和边会很多图将变得无法阅读。此时必须进行过滤 *权重过滤只绘制传球次数超过某个阈值例如3次的边H G.edge_subgraph([(u, v) for u, v, d in G.edges(dataTrue) if d[weight] 3])。 *核心子图提取网络的核心部分例如k-corecore_number nx.core_number(G)然后提取k值大的节点诱导的子图。 *聚焦分析不要试图在一张图上展示所有。可以分别绘制前场、中场、后场的传球网络或者只分析某个时间段如上半场的数据。5. 性能优化对于非常大的图成千上万个节点networkx的某些算法和绘图可能会变慢。考虑使用igraph性能更好进行底层计算再用networkx或plotly绘图。对于超大规模网络静态可视化可能已不适用需要转向交互式可视化库如plotly或pyvis。6. 从图表到洞察画出一张漂亮的图只是开始。在论文中你需要结合图表阐述发现 * “节点A最大说明他是进攻组织的绝对核心。” * “连接后卫和前锋的边B-C颜色最深权重最大说明这是最主要的后场到前场的推进路线。” * “球员D的中介中心性极高但他度数不高说明他扮演着关键的‘转换枢纽’角色虽然直接传球不多但很多进攻都经由他转换方向。” * “社区发现显示左路和右路形成了两个相对独立的传球小组说明球队战术倾向于边路进攻。”最后环境问题永远是第一道拦路虎。如果conda安装包总是失败记住终极法宝换源用清华、中科大镜像和用pip。在conda环境中你可以使用pip install package_name但需注意潜在的依赖冲突。对于networkx和matplotlib这类基础包conda安装通常更稳妥。如果遇到无法解决的冲突最干净的办法是新建一个环境从头开始。
返回列表