如何用ggplot和geom_text_repel按象限随机且比例化标注?
解决方案:mtcars四象限图的比例化随机标注
核心思路
先按mpg和drat的中位数划分四象限,再根据各象限数据点数量占比分配标注名额,随机抽取样本后用ggrepel的标注功能避免拥挤。
完整代码实现
library(ggplot2) library(ggrepel) library(dplyr) # 1. 预处理数据:划分象限 + 计算象限占比 mtcars_quad <- mtcars %>% mutate( # 添加行名作为标注文本 car = rownames(.), # 计算中位数作为象限分割线 mpg_med = median(mpg), drat_med = median(drat), # 标记象限:Q1(右上), Q2(左上), Q3(左下), Q4(右下) quadrant = case_when( mpg > mpg_med & drat > drat_med ~ "Q1", mpg < mpg_med & drat > drat_med ~ "Q2", mpg < mpg_med & drat < drat_med ~ "Q3", mpg > mpg_med & drat < drat_med ~ "Q4", TRUE ~ "On Line" # 处理刚好在中位数线上的点 ) ) %>% filter(quadrant != "On Line") # 去掉线上点,也可根据需求保留 # 2. 按比例确定各象限标注数量 total_labels <- 10 # 设定总标注数,可自行调整 quad_counts <- mtcars_quad %>% count(quadrant) %>% mutate(prop = n / sum(n)) quad_labels <- quad_counts %>% mutate(num_label = round(prop * total_labels)) # 处理四舍五入后总数偏差的情况(比如总和不等于total_labels) label_diff <- total_labels - sum(quad_labels$num_label) if(label_diff != 0){ # 给占比最大的象限调整数量 quad_labels <- quad_labels %>% mutate(num_label = ifelse(prop == max(prop), num_label + label_diff, num_label)) } # 3. 按象限随机抽取标注样本 mtcars_labeled <- mtcars_quad %>% left_join(quad_labels, by = "quadrant") %>% group_by(quadrant) %>% sample_n(size = num_label) %>% ungroup() # 4. 绘制四象限图 + 比例化标注 ggplot(mtcars_quad, aes(x = mpg, y = drat)) + # 绘制象限分割线 geom_vline(xintercept = median(mtcars$mpg), linetype = "dashed", color = "gray50") + geom_hline(yintercept = median(mtcars$drat), linetype = "dashed", color = "gray50") + # 绘制所有数据点 geom_point(color = "steelblue", alpha = 0.7) + # 只标注抽样的点,用repel避免拥挤 geom_text_repel( data = mtcars_labeled, aes(label = car), size = 3.5, box.padding = 0.5, # 增加文本与点的距离 point.padding = 0.3, max.overlaps = Inf # 关闭重叠限制,因为已经抽样控制数量了 ) + # 标注象限名称 annotate("text", x = Inf, y = Inf, label = "Q1", hjust = 1.2, vjust = 1.2, size = 4) + annotate("text", x = -Inf, y = Inf, label = "Q2", hjust = -0.2, vjust = 1.2, size = 4) + annotate("text", x = -Inf, y = -Inf, label = "Q3", hjust = -0.2, vjust = -0.2, size = 4) + annotate("text", x = Inf, y = -Inf, label = "Q4", hjust = 1.2, vjust = -0.2, size = 4) + theme_minimal()
关键细节说明
- 象限划分:用中位数作为分割线,保证四个象限的划分更贴合数据分布,也可以换成均值或自定义阈值。
- 比例抽样:通过计算各象限数据点占比分配标注名额,解决了“点数越多标注越多”的需求;同时处理了四舍五入后的总数偏差,保证总标注数符合设定值。
- 避免拥挤:先通过抽样控制标注数量,再用
geom_text_repel的box.padding和point.padding参数调整文本与点、文本之间的距离,比单纯调max.overlaps更可控。 - 灵活性:可以通过修改
total_labels调整总标注数,也可以在抽样时加入weight参数(比如按点的离散程度加权),进一步优化标注分布。
内容的提问来源于stack exchange,提问作者Maul Seil
相关产品推荐
相关产品推荐

