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
相关产品推荐
相关产品推荐

