ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

R语言雷达图绘制全攻略:fmsb/ggradar/plotly实战与避坑指南

R语言雷达图绘制全攻略:fmsb/ggradar/plotly实战与避坑指南 做数据分析和可视化这么多年雷达图算是被问到最多的“小众图”之一。尤其当需求方一句“给我画个雷达图”甩过来时往往意味着他要对比的东西并不少——可能是几组样本的α多样性指数可能是模型在多个指标上的性能表现也可能是一组基因在不同处理下的表达模式。说它小众是因为雷达图在大多数场景下并不是第一选择但说它高频是因为只要涉及“多维指标 综合评估 直观对比”这三要素雷达图几乎是无可替代的表达方式。R语言在这个领域有着先天优势生态里有专门为雷达图深度定制的包数据管道整合能力强从数据清洗到出图能在同一环境里闭环完成而且统计模型的结果格式天然适合转换到雷达图的数据结构。这篇文章就围绕“雷达图R语言”这个组合把从选型、数据准备、绘图参数调整到实际项目中的应用技巧完整捋一遍涵盖像我这类从业者真实踩过的坑和长期验证过的方案直接照着做就能出图。1. 雷达图的核心定位什么时候该用它什么时候别硬凑1.1 雷达图的适用场景和真实优势雷达图也叫蛛网图或星图它的核心机制是把多个维度的数值映射到从同一个圆心向外辐射的坐标轴上。每个轴代表一个变量轴之间的夹角相等数据点连接成一个闭合多边形。这种设计有一个非常独特的视觉优势它把一个多维数据点变成了一个“形状”。人脑对形状的感知比对数字的感知快得多通过几个多边形的面积大小、边界是否凸起、哪个方向凹陷能一眼看出不同对象在多维空间中的分布差异。实际工作中我大量使用雷达图的几个典型场景是样本组间的多指标特征对比比如不同处理组在多个α多样性指数Shannon、Simpson、Chao1、ACE、Pielou上的整体差异功能富集分析结果的横向比较比如几个比较组各自富集到的GO条目在不同功能类别的分布机器学习模型的多维度性能比较精度、召回率、F1、AUC、特异度这些指标放一起看哪个模型更均衡综合评分型任务比如学生对多个能力维度打分、产品在可用性多个维度上的对比。这些场景的共同特点是指标数量不多通常5到8个、指标间没有强相关、需要看的是“整体构型”而不是精确数值。1.2 雷达图的局限性与避坑前提雷达图最大的问题是它不适合精确读数。雷达图中人的视觉会对轴向距离产生偏差靠近水平方向和垂直方向的坐标轴更容易被准确感知而斜向轴的数值容易被高估或低估。此外当指标个数超过8个时多边形会趋于圆形各对象之间的差异肉眼几乎无法分辨。指标相关性太强时相邻轴之间的区域会被重复强调产生视觉误导。所以在实际项目中我会先做一个判断如果指标超过8个或者需要观众读取具体数值我就放弃雷达图改用平行坐标图或分组条形图。雷达图适合“展示”而不是“度量”适合“对比构型”而不是“精确比较”。这个定位想清楚了后面选包、调参、写注释都会顺手很多。2. R语言里的雷达图方案选型四个路线到底怎么选2.1 四种主流实现方式横向对比R语言生态中能做雷达图的工具不少但真正能拿到生产中用的归纳下来主要就是四个方向基础绘图系统自带的stars()函数、专用包fmsb、基于ggplot2的ggradar以及coord_radar、交互式的plotly。每个方案的使用场景和优缺点差异很大选错了会多走弯路。我整理了一个对比表方便新手直接做选型方案图像风格数据格式要求交互性上手难度适用场景stars()经典星图/雷达图宽格式数据框行是观测无低快速探索、简单展示fmsb经典雷达图可定制性强特定格式首行最大、次行最小低中静态出版级图、复杂定制ggradar现代ggplot风格默认美化好长格式行是观测列是指标第一列是分组无中多组对比、需要ggplot风格统一plotly交互式Web图类似fmsb需要max/min行高中网页嵌入、动态切换2.2 选型原则和我的个人倾向在真实项目里我的选择逻辑非常简单如果要嵌入PDF报告且需要精细控制每个细节我会用fmsb如果整个报告都是用ggplot2体系生成为了风格统一我选择ggradar如果要交付HTML网页或者需要动态展示多个分组直接上plotly。至于stars()说实话它在现代工作流中出场率不高主要是我偶尔用它快速偷瞄一眼数据形态——一条命令出一个图适合探索阶段用但不太适合作为最终交付物。还要提一点fmsb和ggradar在处理多组数据时的策略不同。fmsb对多组数据的叠加展示很直接每个组一个多边形颜色区分即可ggradar则默认以分组列做分组输出更干净网格线和背景的定制也更符合现代审美。如果读者的数据包含大量分组超过5组fmsb的多边形叠加会变得混乱此时ggradar的分面展示或者plotly的交互切换是更好的出路。3. fmsb包实战从数据准备到出版级出图3.1 数据格式这是新手最容易忽略的硬性要求fmsb包对数据的格式要求是固定的数据框的第一行必须是指标的最大值第二行必须是最小值从第三行开始才是实际要绘制的样本或分组。为什么这样设计因为fmsb在绘制坐标轴时需要知道每个变量的量纲范围这个范围就来自前两行。如果数据框里没有连续的最大值和最小值函数会直接报错或者绘制出来的坐标轴范围错乱。比如我们有四个多样性指数Shannon、Simpson、Chao1、ACE两个处理组Treatment、Control数据准备流程如下library(fmsb) # 原始数据 data - data.frame( Shannon c(3.2, 2.8), Simpson c(0.92, 0.85), Chao1 c(320, 280), ACE c(335, 295) ) rownames(data) - c(Treatment, Control) # 添加最大值和最小值行关键步骤 data - rbind( rep(1, ncol(data)), # 第一行最大值这里 Simpsons 最大为1其他指标取实际最大值的上限 rep(0, ncol(data)), # 第二行最小值 data )这里有一个细节如果是比例型指标比如Simpson指数在0到1之间最大值行填1、最小值行填0是合理的但如果是指数型指标比如Chao1可以到几百甚至上千直接填1和0会压缩所有数值到超小区域导致图形几乎贴在中心点上看不出差异。正确的做法是填该指标的理论最大值或观测值的上限值比如Chao1 400。3.2 核心绘图函数与参数详解fmsb核心绘图函数是radarchart()。它的主要参数可以分成几类坐标轴控制类、多边形样式类、文字标签类。下面给一个比较完整的配置示例radarchart( data, axistype 1, # 坐标轴刻度类型1显示内圈数值 seg 5, # 坐标轴等分数网格线数量 pcol c(#E64B35, #4DBBD5), # 多边形边框颜色 pfcol c(rgb(230, 75, 53, maxColorValue 255, alpha 80), rgb(77, 187, 213, maxColorValue 255, alpha 80)), # 填充色半透明 plwd 2, # 多边形线宽 plty 1, # 线条类型 cglcol grey, # 网格线颜色 cglty 2, # 网格线类型虚线 cglwd 0.8, # 网格线宽度 axislabcol grey30, # 刻度标签颜色 vlcex 1.0, # 变量标签字体大小 calcex 0.8, # 刻度数值字体大小 title Alpha Diversity Comparison )参数很多但真正需要记住的核心逻辑只有三条pcol和pfcol控制多边形颜色seg控制图形精细程度axistype控制刻度显示方式。其他参数基本都是照葫芦画瓢用多了自然记得。关于半透明填充我强烈建议在有多组对比时使用。如果不加透明度多个多边形叠加时后面的多边形会把前面的完全遮住图面会变成一团色块。使用rgb()构造带Alpha通道的颜色是让多边形叠加显示的关键技巧。3.3 完整实战流程快速出图并保存下面这个完整脚本可以直接复制运行用内置数据集mtcars的前四列做一个雷达图示例library(fmsb) # 取mtcars前4个指标选3种车型 plot_data - mtcars[1:3, c(mpg, disp, hp, drat)] # 先做归一化因为各指标量纲差异太大不归一化小数值完全被压扁 normalize - function(x) { (x - min(x)) / (max(x) - min(x)) } plot_data_std - as.data.frame(t(apply(plot_data, 1, normalize))) colnames(plot_data_std) - colnames(plot_data) row.names(plot_data_std) - row.names(plot_data) # 加上最大最小行 data_for_radar - rbind( max rep(1, ncol(plot_data_std)), min rep(0, ncol(plot_data_std)), plot_data_std ) # 绘图并保存 png(radar_plot.png, width 1200, height 1200, res 300) radarchart(data_for_radar, axistype 1, seg 4, pcol c(#D7191C, #2C7BB6, #FDAE61), pfcol c(rgb(215, 25, 28, maxColorValue 255, alpha 60), rgb(44, 123, 182, maxColorValue 255, alpha 60), rgb(253, 174, 97, maxColorValue 255, alpha 60)), plwd 2, cglcol grey60, cglty 1, axislabcol black, title MTCars Radar Example) dev.off()我在实际工作中遇到过一个问题如果指标的量纲相差极大比如一个指标范围是0到1另一个是0到1000直接放进雷达图会导致小指标的所有数值看起来都是一个点。解决办法就是我上面脚本里的归一化把每个指标缩放到0到1之间。这一步骤在fmsb中尤其重要因为它没有内置的标准化选项。4. ggradar和plotly的进阶路线4.1 ggradarggplot2用户的现代选择ggradar是基于ggplot2的扩展包它不直接提供geom_*图层而是提供了一个封装好的ggradar()函数。与fmsb相比它的最大优势是默认配置好看剩余的定制规则完全遵循ggplot2体系方便与报告中的其他图保持风格一致。ggradar对数据格式的要求与fmsb不同它要求一个长格式数据框第一列是分组变量后续列全部是指标数值每一行对应一个分组。换句话说数据框的列名就是雷达图的指标名。library(ggradar) library(dplyr) library(scales) # 准备数据第一列为分组后面是指标 df_radar - data.frame( group c(ModelA, ModelB, ModelC), Accuracy c(0.92, 0.88, 0.95), Precision c(0.85, 0.90, 0.88), Recall c(0.78, 0.86, 0.92), F1 c(0.81, 0.88, 0.90), AUC c(0.94, 0.91, 0.97), Specificity c(0.90, 0.87, 0.89) ) # 画图 ggradar(df_radar, values.radar c(0.5, 0.7, 1.0), # 控制网格线代表的数值 grid.min 0.5, grid.mid 0.7, grid.max 1.0, group.line.width 1.2, group.point.size 3, background.circle.colour white, gridline.min.colour grey90, gridline.mid.colour grey80, gridline.max.colour grey70, legend.position c(1, 1))ggradar有一个坑它内部依赖tidyverse的管道和scales包的rescale函数如果包的版本冲突严重容易报错。我踩过几次坑之后总结出一个稳妥方案尽量在全新的R session中安装依赖按顺序装齐了再跑。另外ggradar的values.radar参数控制的是网格线数值网格默认有三圈代表最小值、中间值和最大值。如果数据范围超出了你设置的grid.min和grid.max图形会超出外圈看起来像多边形被截断。4.2 plotly交互式雷达图的偷懒方案如果你需要交付网页形式的结果推荐直接用plotly。它的plot_ly()函数里有专门针对雷达图设计的type scatterpolar模式底层是极坐标系统对多组数据的交互支持很友好。library(plotly) plot_ly( type scatterpolar, mode linesmarkers, fill toself, theta colnames(df_radar)[-1], # 指标名 r as.numeric(df_radar[1, -1]), # 第一个组的数据 name df_radar$group[1], opacity 0.5 ) %% add_trace( theta colnames(df_radar)[-1], r as.numeric(df_radar[2, -1]), name df_radar$group[2], opacity 0.5 )这个方案有一个天然优势交互式悬浮提示hover会直接显示每个指标的具体数值弥补雷达图“不适合精确读数”的缺陷。如果读者对象是不熟悉雷达图的外行交互式的悬浮提示能大幅降低理解成本。我还试过用plotly的subplot函数把多个雷达图排成网格适合展示分面式对比。5. 生物信息学场景实战α多样性和GO富集分析的雷达图展示5.1 α多样性指数综合对比在研究微生物群落时α多样性通常包含多个指数不同指数反映的生态学意义不同Shannon和Simpson反映物种丰富度和均匀度综合情况Chao1和ACE主要估算物种总数Pielou反映均匀度。常规做法是把这些指数分别做成箱线图或柱状图但缺点是无法直观看出“哪个处理组在整体多样性上更强”。把多个指数压缩到一张雷达图上组的整体多样性构型就出来了如果你看到一个处理组的多边形在多数轴上向外凸出它的整体生态多样性水平就明显更高。用vegan包计算多样性指数之后数据结构往往是每个样本一行、每个指数一列如下所示library(vegan) # 假设 otu_table 是 OTU 丰度表行是样本列是OTU # 计算多样性指数 div_index - data.frame( Shannon diversity(otu_table, index shannon), Simpson diversity(otu_table, index simpson), Chao1 estimateR(otu_table)[2, ], ACE estimateR(otu_table)[4, ] ) # 加上分组信息后按组求均值 group_mean - aggregate(div_index, by list(group metadata$group), FUN mean)然后把group_mean按fmsb格式整理好就能输出各组的多边形对比了。这个流程我建议用一个函数封装起来以后只要输入otu_table和分组信息就能直接出图省去重复的数据整理工作。5.2 GO富集分析结果的雷达图呈现GO富集分析的结果通常是一个长表格每行是一个GO条目列包含富集因子、p值、基因数、所属功能类别BP、CC、MF等信息。默认的可视化方案是气泡图或柱状图但当你需要比较多个比较组之间的功能富集差异时雷达图就有用武之地了。做法是每个比较组算出一组“功能类别得分”比如把富集到的GO条目按三个类别生物学过程BP、细胞组分CC、分子功能MF分别聚合取显著条目的数量或平均富集因子的负log10转换值然后作为雷达图的一个维度。# 假设 go_result 包含比较组、类别、pvalue library(dplyr) go_summary - go_result %% filter(pvalue 0.05) %% group_by(group, category) %% summarise(score -log10(median(pvalue)), .groups drop) %% tidyr::pivot_wider(names_from category, values_from score) # 转为 fmsb 格式并绘图这种图特别适合放在多组比较的总体概览部分直观展示各组在BP、CC、MF三个类别上的富集强度差异。比如A组在BP上明显凸出说明该组差异基因主要集中在生物学过程B组在MF上凸出则提示分子功能层面的富集更显著。6. 常见问题与排查技巧实录6.1 高频报错与排查速查表在实际使用过程中雷达图的报错说多不多说少不少但集中在几个点上。下面这个表是我长期使用下来整理的速查表覆盖了绝大多数场景症状可能原因处理方式fmsb报错“非数值列”数据框包含字符列或因子列确保数据框全部为数值型仅可用行名区分分组图形缩成一团多边形贴中心量纲差异过大未归一化先做min-max标准化或调整最大最小值行ggradar报错数据框不是长格式或第一列非字符确保第一列是字符型分组变量其余列是数值指标plotly雷达图没有填充色未设置fill toself在plot_ly()中显式声明fill toself多组多边形互相遮挡严重未使用半透明填充色用rgb()加alpha通道或用带透明度色值图例覆盖图形主体未合理设置图例位置legend.position或layout()里微调图例坐标这六个坑是我在项目中被反复磨过的尤其第一个新手最常犯。fmsb要求数据框内不能混入字符列如果分组信息是用列而不是行名表示的记得先转置并用rownames()保存分组名。6.2 两个提高效率的实战技巧第一个技巧是写一个通用封装函数。雷达图的绘制流程高度固定数据输入、格式调整、标准化、加最大最小行、调参绘图。把这五步包进一个函数里用参数控制颜色、标题、输出路径后续所有项目直接调用即可。我在实践中就把这个函数保存成了R/radar_plot.R项目里source()进来就能用比自己每次重写脚本效率高得多。第二个技巧是批量出图。如果要做多个分组组合的对比比如“处理组1对处理组2”“处理组3对处理组4”这种成对比较不要一个个手动画。用purrr的map()函数遍历分组组合自动生成批量PDF或PNG然后拼在一起做版面。代码大概是library(purrr) combinations - list(c(Treatment, Control), c(Treatment, Mock)) walk(combinations, function(pair) { sub_data - data[c(max, min, pair), ] radarchart(sub_data, ...) })批量处理时要注意输出文件的命名规则用paste0()拼上分组名避免覆盖。这一步看着不起眼但能在交付周报或论文图表时省下大量重复劳动。最后一个个人经验无论用哪个包雷达图的图例位置和坐标轴标签永远比图形本身更花时间。图例放得不对整个图面的重心会偏移坐标轴标签太长相互之间会重叠。解决办法是标签用简称并在脚注里注释或者利用par(mar c(..., ...))给图形留出额外的边距。多花几分钟调边距比出图后回来返工要划算得多。
返回列表