You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ggplot2:对数10与线性刻度下有序箱线图中位数显示异常

log10刻度下箱线图中位数显示异常的原因与解决

需求与数据

需要按B_4digits分组绘制箱线图,X轴按IntactPlusOne的原始值中位数升序排列,数据如下:

data <- tibble(
  ID = c("Participant1","Participant2","Participant3","Participant4","Participant5","Participant6","Participant7","Participant8","Participant9","Participant10","Participant11","Participant12","Participant13","Participant14","Participant15","Participant16","Participant17","Participant18","Participant19","Participant20"),
  B_4digits = c("B*27:05","B*27:05","B*27:05","B*27:05","B*27:05","B*27:05","B*27:05","B*58:01","B*58:01","B*27:05","B*27:05","B*27:05","B*58:01","B*27:05","B*35:09","B*27:05","B*58:01","B*35:09","B*27:05","B*27:05"),
  IntactPlusOne = c(45.607021,94.813992,7.508987,7.36921,294.145913,52.788633,150.445023,8.345686,45.29045,10.969521,22.970114,14.75898,116.86,8.852951,8.696451,108.828553,8.648521,38.946391,17.718311,15.569026))

线性Y轴下的正常表现

使用线性刻度时,X轴排序和箱线图中位数显示均符合预期:

data |>
  ggplot(aes(x = fct_reorder(factor(B_4digits), IntactPlusOne, .fun = median, na.rm = TRUE), y = IntactPlusOne))+
    geom_boxplot(outlier.shape = NA, fill = NA)+
    geom_dotplot(binaxis = "y", stackdir = "center", alpha = 0.6, colour = NA,  stackratio = 0.55, position = "dodge")

log10 Y轴下的异常现象

切换为log10刻度后,B*35:09组的箱线图中位数线显示位置低于B*27:05组,与手动计算的原始值中位数顺序矛盾:

data |>
  ggplot(aes(x = fct_reorder(factor(B_4digits), IntactPlusOne, .fun = median, na.rm = TRUE), y = IntactPlusOne))+
    geom_boxplot(outlier.shape = NA, fill = NA)+
    geom_dotplot(binaxis = "y", stackdir = "center", alpha = 0.6, colour = NA,  stackratio = 0.55, position = "dodge") +
    scale_y_log10()

手动验证的中位数顺序

分组计算原始值中位数,确认正确顺序应为B*27:05(20.3) < B*35:09(23.8) < B*58:01(27.0):

data |>
  group_by(B_4digits) |>
  summarise(median_intact = median(IntactPlusOne)) |>
  arrange(median_intact)

结果:

# A tibble: 3 × 2
  B_4digits median_intact
  <chr>             <dbl>
1 B*27:05            20.3
2 B*35:09            23.8
3 B*58:01            27.0

问题原因分析

核心原因是ggplot在log刻度下计算箱线图统计量时,默认对转换后的值进行计算,而非原始值:

  • 对于B*35:09组,原始值是8.696和38.946,原始中位数为两者的平均值23.821;
  • 但log10转换后的值为log10(8.696)≈0.939和log10(38.946)≈1.590,ggplot计算的是转换后值的中位数(同样是平均值)≈1.264,对应原始值为10^1.264≈18.4;
  • 而B*27:05组的原始中位数20.3,转换后为log10(20.3)≈1.307,对应log轴上的位置比1.264更高,因此视觉上B*35:09的中位数线反而更低。
  • 该现象确实和B*35:09仅含2个观测值有关:当样本数为偶数时,中位数是中间两个数的平均,对数转换后再平均的结果≠原始值平均的对数(对数函数的非线性导致),而样本数为奇数时,中位数是单个值,转换后的值就是原始中位数的对数,不会出现这个偏差。

解决方法

若要保持X轴按原始值中位数排序,同时在log轴上正确显示原始中位数的位置,可以手动指定统计量使用原始值的中位数:

方法1:提前计算中位数,手动绘制

# 计算各组原始中位数
median_df <- data |>
  group_by(B_4digits) |>
  summarise(med = median(IntactPlusOne)) |>
  mutate(B_4digits = fct_reorder(B_4digits, med))

# 绘图时用提前计算的中位数
data |>
  mutate(B_4digits = fct_reorder(B_4digits, IntactPlusOne, median)) |>
  ggplot(aes(x = B_4digits, y = IntactPlusOne))+
    geom_boxplot(outlier.shape = NA, fill = NA, aes(ymedian = med), data = median_df)+
    geom_dotplot(binaxis = "y", stackdir = "center", alpha = 0.6, colour = NA,  stackratio = 0.55, position = "dodge") +
    scale_y_log10()

方法2:用stat_summary指定原始值中位数

data |>
  mutate(B_4digits = fct_reorder(B_4digits, IntactPlusOne, median)) |>
  ggplot(aes(x = B_4digits, y = IntactPlusOne))+
    geom_boxplot(outlier.shape = NA, fill = NA, stat = "summary", 
                 fun.data = function(x) {
                   data.frame(
                     ymin = quantile(x, 0.25),
                     lower = quantile(x, 0.25),
                     middle = median(x),
                     upper = quantile(x, 0.75),
                     ymax = quantile(x, 0.75)
                   )
                 })+
    geom_dotplot(binaxis = "y", stackdir = "center", alpha = 0.6, colour = NA,  stackratio = 0.55, position = "dodge") +
    scale_y_log10()

内容的提问来源于stack exchange,提问作者viral

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 07:57:39