ARTICLE DETAIL

资讯详情

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

R语言ggradar包实战:从数据归一化到NBA球员雷达图绘制

R语言ggradar包实战:从数据归一化到NBA球员雷达图绘制 雷达图这个东西在体育数据分析里出场率是真的高。不管是NBA官网的球员对比还是各种数据媒体的赛季盘点你总能看到这种从中心向外发散、像蜘蛛网一样的多边形图表。我第一次在项目报告里想复现这种图用的是R语言翻了好几个包最后真正让我稳定出图的还是ggradar。这个包基于ggplot2封装写起来很顺手但对数据格式有严格的要求新手第一次用基本都会在归一化和列类型上栽跟头。这篇博文我打算用NBA季后赛的真实球员数据整理后的近似值仅作演示来做例子从数据结构讲到出图调优把ggradar的常见坑一次性聊透。内容适合两类人一是R已经入门、想画多维对比图但不太清楚怎么组织数据的人二是已经会ggplot2、想快速搞定极坐标雷达图又不想手搓坐标变换的人。看完你应该能直接照着代码跑出一张能放进报告里的图也知道出问题时该往哪查。1. 项目思路与数据集设计1.1 为什么选雷达图看NBA季后赛数据雷达图的核心价值一句话就能说清把多个维度的数值放到同一张图上用“形状”代替“数字”做对比。常规赛数据样本大、球员定位模糊但季后赛不一样轮换缩短、战术针对性极强每个球员在球队里的角色会被放大——有人是篮板怪兽有人是外线炮台有人是组织大脑。这些差异用表格看是一堆数字用雷达图看就是完全不同的几何形状。拿一张图对比约基奇和戴维斯得分、篮板、助攻、盖帽几个维度摊开约老师的多边形明显更“膨胀”浓眉则在盖帽这一根轴上拉满。这种视觉冲击力是条形图和散点图给不了的。我还喜欢拿雷达图做“球员能力模型”就像学生成绩单上六科雷达图一样哪一科偏科、哪一科全面一眼就能捕捉到。1.2 数据从哪里来怎么构造这次演示我选了三位2023-24赛季季后赛代表球员约基奇、戴维斯、爱德华兹。每个球员记录六个维度的场均数据得分、篮板、助攻、抢断、盖帽、三分命中率。为什么选这六项得分和篮板是基础产量助攻和抢断代表组织与防守积极性盖帽体现护框能力三分命中率则投射效率——维度之间相关性不能太强否则雷达图的“形状”会被某一个隐藏因子主导看不出差异。在R里面我推荐用tibble直接构造library(tidyverse) playoff - tibble( player c(Jokic, Davis, Edwards), points c(28.7, 27.8, 27.6), rebounds c(13.4, 15.6, 7.0), assists c(8.7, 4.1, 6.5), steals c(1.2, 1.5, 1.5), blocks c(0.9, 1.6, 0.8), three_pt_pct c(0.355, 0.235, 0.400) )这里有个细节值得注意三分命中率已经是0到1之间的小数但得分、篮板这些原始单位和量级完全不同。直接拿去画图ggradar会很难受整张图会被得分这一根轴撑爆篮板、助攻全部被压扁。所以数据准备阶段最重要的动作不是计算而是归一化。1.3 归一化ggradar绘图的隐藏前提ggradar本质上是在ggplot2的极坐标系上画点连线所有维度共用同一个半径方向上的刻度。如果你把“28.7分”和“13.4篮板”直接塞进去两者量级差异太大会导致视觉失衡——大数值轴把整个圆撑满小数值轴贴在内圈动都动不了。解决方式就是min-max归一化把每一列线性映射到0到1区间。公式很简单x_scaled (x - min(x)) / (max(x) - min(x))用dplyr处理非常顺手playoff_scaled - playoff %% mutate(across(points:three_pt_pct, ~ (.x - min(.x)) / (max(.x) - min(.x))))这里across的作用是批量对指定列做变换points:three_pt_pct是一个连续选列的范围写法刚好把六项指标都选进去而player列因为是字符型不会被误处理。执行后你可以用summary(playoff_scaled)检查每一列的min都应该是0、max都应该是1。做完这一步真正的ggradar绘图才能开始。2. ggradar绘图原理与核心细节2.1 ggradar到底做了什么很多初学者第一次用ggradar以为它是个独立的绘图系统其实它是对ggplot2能力的封装。ggplot2画雷达图本身是能画的但要自己写coord_polar、自己安排轴标签位置、自己调网格线代码量不小。ggradar把这些都包好了你只需要把数据格式整理对剩下的事情它来。它的绘图逻辑是数据框的每一行对应图上的一组多边形比如一个球员每一列对应从圆心向外发散的一条半径轴数值大小决定这根轴上点的远近最后依次把每个维度上的点连起来闭合形成一个多边形。多个球员的数据放在同一个数据框里图就会叠加出多个多边形方便对比。这就像你用圆规画多边形中心是圆心每条刻度线是一根半径数据值就相当于半径的长度。ggradar只是把“读数、点标、连线”自动化了而已。2.2 数据规范先过这关再谈美化ggradar对数据格式的要求非常严格我在第一次跑的时候被报错折磨了半天所以这里单独拿出来说必须是一个data.frame或tibble第一列是字符型/因子型的分组变量其余列必须全是数值型。不能包含NA值只要有一个缺失极坐标计算时就会报错或者画出断线。归一化后所有数值列的范围最好落在0到1之间并且要对应grid.min、grid.max的参数。如果有多行数据每一行就是一个独立的多边形行数太多会糊成一团建议不超过5组。有一个经常被忽略的问题是数值列的类型。从CSV读入的数据某些列可能会被解析成字符型尤其是三分命中率这种0.355的数据。如果直接画图ggradar会报类似“must be numeric”的错误。所以读入数据后我习惯先用str()看一遍每列类型确认没问题才继续。2.3 关键参数拆解ggradar的绘图函数参数很多但真正影响成图质量的就那么几个。我按使用频率排个序参数作用我的常用值grid.min内圈最小值0grid.mid中圈网格线0.5grid.max外圈最大值1values.radar刻度标签c(0, 0.5, 1)axis.labels每根轴显示的变量名c(得分, 篮板)group.colours每组数据的颜色自定义色板fill是否填充多边形内部TRUEfill.alpha填充透明度0.15group.line.width多边形边框粗细1.2group.point.size顶点圆点大小2.5axis.label.size轴标签字号4.5legend.position图例位置bottom这里最关键的理解是grid.min、grid.mid和grid.max必须与你的数据范围对应。归一化后数据都在0到1之间那这三个参数就设为0、0.5、1。如果你的数据做了0-100的百分制归一化那么这三个参数也要改成0、50、100否则数据点会跑到网格之外。values.radar是另一个容易踩坑的地方。它控制的是网格线上的刻度文本数量必须跟网格线的数量一致。比如你只设了grid.min、grid.mid、grid.max三条网格线那么values.radar传三个标签就够了传五个反而会错位。2.4 为什么不用ggplot2手搓可能有人会问既然ggradar只是封装我直接用ggplot2画不行吗答案是能但性价比太低。手搓雷达图需要做这几件事把宽表数据转成长表、用coord_polar把笛卡尔坐标变成极坐标、调整scale_y_continuous让刻度对齐、再手动控制多边形闭合时首尾坐标的重复。这些步骤加在一起足够写五六十行代码而且调试起来很容易出鬼问题。ggradar把这些隐藏细节全部处理好了。你只需要组织好数据框架把绘图参数调一调代码量能压缩到十几行以内。尤其是做快速探索性分析的时候速度优势非常明显。但代价就是它对数据格式的容忍度低不符合规则就罢工。这恰恰是本文要重点讲透的部分。3. 从零到一完整绘图实操3.1 环境安装与包加载先解决安装问题。ggradar目前不在CRAN的正式源里需要通过GitHub安装。不过在安装之前确保你已经装好了ggplot2、dplyr这些基础包。install.packages(ggplot2) install.packages(dplyr) # 方法一从GitHub安装推荐 if (!requireNamespace(devtools, quietly TRUE)) { install.packages(devtools) } devtools::install_github(ricardo-bion/ggradar)如果网络环境不好GitHub安装失败可以试试国内镜像加速或者直接用install.packages(ggradar)碰碰运气——部分镜像站会收录它。加载的时候也很简单library(ggradar) library(tidyverse)我自己实际用下来最稳妥的还是devtools从GitHub装一次性把依赖包都拉齐。装好之后建议跑一下packageVersion(ggradar)确认版本号避免后续报错时不知道是哪里的问题。3.2 数据准备完整代码还是用刚才构造的playoff数据完整的处理流程我写一遍。这个流程可以当成模板用换任何数据集都成立library(tidyverse) library(ggradar) playoff - tibble( player c(Jokic, Davis, Edwards), points c(28.7, 27.8, 27.6), rebounds c(13.4, 15.6, 7.0), assists c(8.7, 4.1, 6.5), steals c(1.2, 1.5, 1.5), blocks c(0.9, 1.6, 0.8), three_pt_pct c(0.355, 0.235, 0.400) ) # 缺失值检查与处理 playoff - playoff %% drop_na() # 归一化每一列缩放到0-1 playoff_scaled - playoff %% mutate(across(points:three_pt_pct, ~ (.x - min(.x)) / (max(.x) - min(.x)))) # 验证结果 summary(playoff_scaled)这段代码里drop_na()是我后来加的习惯。真实数据基本都不干净手动整理时漏掉一个缺失值后面ggradar画图就会开始报错所以提前清理能省很多事。用across(points:three_pt_pct, ...)这种方式比一个列一个列地写mutate要干净得多也方便扩展到更多维度。3.3 基准图绘制数据处理完后绘图本身非常简洁playoff_scaled %% ggradar( grid.min 0, grid.mid 0.5, grid.max 1, values.radar c(0, 0.5, 1), axis.labels c(Points, Rebounds, Assists, Steals, Blocks, 3P%) )跑完这段你会得到一张具有三个彩色多边形的基本雷达图。这时候只是“能看”的阶段。三个球员的多边形叠在一起靠默认颜色区分图例在右侧。但默认样式有几个明显问题配色不够高级、多边形填充太满、标签字号偏小。所以下一步做视觉调优。3.4 视觉参数调优让图能放进报告我自己画图有个习惯默认参数只是起点最终出图一定按使用场景再调一遍。下面这个版本是我常用的“报告级”配置playoff_scaled %% ggradar( grid.min 0, grid.mid 0.5, grid.max 1, values.radar c(0, 0.5, 1), axis.labels c(得分, 篮板, 助攻, 抢断, 盖帽, 三分命中率), group.colours c(#E64B35, #4DBBD5, #00A087), fill TRUE, fill.alpha 0.15, group.line.width 1.2, group.point.size 2.5, axis.label.size 4.5, gridline.label.offset 0.12, legend.position bottom ) theme_minimal(base_size 14)这里有几个调优思路值得展开说第一group.colours一定要手动指定。ggradar默认配色是ggplot2的调色板在极坐标下颜色会偏亮、偏刺眼。我选了三组比较耐看的颜色红色系、蓝色系、绿色系彼此区分度高打印出来也不会混淆。第二fill TRUE配合fill.alpha 0.15。填充色能让多边形看起来更加“实”但alpha值不能太高否则多个球员重叠区域会变成一团深色看不清边界。0.15是我试过很多次之后觉得比较舒服的透明度。第三legend.position bottom。雷达图本身就是圆的右侧图例容易把整体视觉重心带偏放底部更协调。如果你想更彻底还可以用legend.position none去掉图例改用图形标签直接标注但那样需要额外用annotate代码会复杂不少。第四这个包返回的对象本身是ggplot对象所以后面还能继续叠加theme相关的调整。我在例子末尾加了theme_minimal(base_size 14)让整张图的字体更协调。3.5 进阶多个球员叠加与分面小图如果你的数据里不止三位球员比如想对比一整支首发阵容多边形的叠加会变得拥挤。这时候有两个处理思路一是直接在同一个图里画但只保留2到4组。超过四组图基本就看不清谁是谁了。二是做分面小图每个球员一个面板。不过要注意ggradar对分面支持不算友好因为每个面板都会重复绘制极坐标网格版面利用率不高。实际操作中我更推荐用列表循环加patchwork拼图而不是facet_wraplibrary(patchwork) plots - split(playoff_scaled, playoff_scaled$player) %% map(~ ggradar(.x, grid.min 0, grid.mid 0.5, grid.max 1, values.radar c(0, 0.5, 1), axis.labels c(得分, 篮板, 助攻, 抢断, 盖帽, 三分命中率), fill TRUE, fill.alpha 0.15 )) reduce(plots, ) plot_layout(ncol 1)这个技巧在做球员简历或者报告附录的时候很好用。单球员独图信息密度更高也能顺便加上球员照片、球衣号码等元素直接变成一页可视化卡片。4. 常见报错与排查技巧实录4.1 最经典的坑数值没归一化我先说一下这个坑的典型表现图形“扁了”某个方向被拉得特别长其他方向全部贴在内圈整体几乎看不出雷达图的形状。如果你看到这种情况十有八九是直接把原始数据丢给ggradar了没有做min-max归一化。排查方式很简单跑一遍summary()或者range()逐列检查数值范围。比如points列是28左右three_pt_pct列是0.3左右两者量级差100倍极坐标下小数值轴自然会被压缩成一条线。把归一化补上图立刻恢复正常。4.2 中文标签变方块这是中文本地化最常见的问题。axis.labels可以传中文但绘图字体如果没有中文字体支持图上就是一个个方块或者问号。原因不在ggradar本身而在ggplot2的字体渲染机制。我推荐用showtext包解决它对中文支持最省心library(showtext) showtext_auto() font_add(heiti, C:/Windows/Fonts/simhei.ttf)如果你的系统是macOS字体路径改成对应的字体文件比如PingFang.ttc。加载showtext并启用自动渲染后再跑一次绘图代码中文就能正常显示。这个坑比较隐蔽因为代码不会报错只会默默输出带方块子的图。4.3 数据列类型不对导致的报错如果你读入的是CSV某列被读成了字符型ggradar会报错说需要数值型。最典型的例子是三分命中率有些数据里直接带上百分号比如35.5%那这一列就变成了字符串。处理方法也很标准先转成数值再去掉百分号playoff - playoff %% mutate(three_pt_pct as.numeric(gsub(%, , three_pt_pct)) / 100)这里有几个细节要注意gsub去掉百分号之后as.numeric转换不除以100的话数据范围是35.5而不是0.355所以换算要写全。如果数据里本身是小数点格式直接as.numeric()就行。4.4 图例位置和大小调整的各种尝试ggradar的图例参数经常被忽略但实际影响很大。默认图例在右侧标签文字小位置也比较尴尬。我调试时踩过几个坑想把图例放到底部但改完位置之后文字还是很小需要额外设置legend.text.size或者图例标题占了一块空间整体图形又变小了一圈。我的建议是单组数据时直接legend.position none去掉图例干净利落多组对比时放在底部并且用legend.text.size 12这类参数把字号调大。因为雷达图的网格和标签已经承载了大量信息图例的存在感不应该太强。4.5 常见问题速查表现象原因解决方案图像被压扁、方向不平衡数据未归一化用min-max归一化到0-1中文标签显示为方块字体不支持中文用showtext加载中文字体报错“must be numeric”数据列是字符型as.numeric()转换图形缺失或断线数据含NAdrop_na()清理图例挤占画布图例位置和大小不合理调整legend.position和字号数据点超出网格数据范围大于grid.max对齐grid.min和grid.max我在实际项目中已经把这些排查步骤固化成了SOP拿到数据先str()看类型再summary()看范围然后归一化最后才进ggradar。这套流程基本能避开九成以上的坑。最后再分享一个我自己的使用习惯。刚开始画雷达图我总是先纠结配色、字体、透明度这些视觉参数后来发现最核心的还是“指标选得对不对”。雷达图好看的前提是每个维度本身有意义、彼此又能区分出差异。如果六个维度全是强相关的数据画出来就是一块畸形的多边形颜色再高级也救不回来。所以现在每画一张雷达图之前我都会先问自己一句这些维度放在一起到底想表达什么差异想清楚了再动手图自然就有说服力。希望这篇内容能帮你少走点弯路快速画出第一张能看的雷达图。
返回列表