☰
R语言雷达图绘制全攻略:fmsb/ggradar/plotly实战与避坑指南
2026/10/8 3:41:34 网站建设 项目流程

做数据分析和可视化这么多年,雷达图算是被问到最多的“小众图”之一。尤其当需求方一句“给我画个雷达图”甩过来时,往往意味着他要对比的东西并不少——可能是几组样本的α多样性指数,可能是模型在多个指标上的性能表现,也可能是一组基因在不同处理下的表达模式。说它小众,是因为雷达图在大多数场景下并不是第一选择;但说它高频,是因为只要涉及“多维指标 + 综合评估 + 直观对比”这三要素,雷达图几乎是无可替代的表达方式。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 ggradar:ggplot2用户的现代选择

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 = "lines+markers", 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(..., ...))给图形留出额外的边距。多花几分钟调边距,比出图后回来返工要划算得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询