Menu

R 语言箱线图:用 boxplot() 分组绘图(附示例)

如何在 R 中用 boxplot() 画箱线图:读懂箱体和须线,用公式接口比较各组,调整样式,以及找出图背后的数字。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

如何读懂箱线图

箱线图把整个分布压缩成五个数,再把它们画成一个紧凑的图形。学会看它只需要三十秒,所以我们先来做这件事。每个部分都有精确的含义:

  • 箱体内的粗线是中位数:一半数据在它下面,一半在它上面。
  • 箱体本身覆盖四分位距(IQR),从第一四分位数(25% 的数据在它下面)到第三四分位数(75% 的数据在它下面)。数据中间的那一半都在箱体里。
  • 须线是从箱体向外延伸的线。每条须线延伸到距箱体边缘 1.5 × IQR 以内最极端的数据点,所以它覆盖的是数据的“常规范围”,而不是完整范围。
  • 须线之外的单个点是距箱体超过 1.5 × IQR 的值:被标记为疑似离群值,逐个画出来,方便你数清楚。

箱体很高说明数据分散;中位线在箱体里偏向一侧说明数据有偏斜;一条须线外拖着一串点,说明那一侧有长尾。直方图能更详细地展示同一个分布,但箱线图的超能力在于,十几个箱线图可以并排放在一起,这让它成为比较各组时的首选图形。

单个变量的箱线图

函数是 boxplot(),对单个数值向量只需一次调用:

scores <- c(52, 55, 58, 60, 61, 63, 64, 66, 68, 70, 72, 95)

boxplot(scores,
        main = "Test scores",
        ylab = "Score")

画出来的图是这样的:箱体从 59 到 69,中位线在 63.5,须线向下延伸到 52、向上延伸到 72,还有一个孤零零的点浮在 95 处。如果用均值和标准差来概括,这个离群值会被悄悄地吸收掉。一眼就看出“有个值和其他的不一样”,这正是箱线图的用途。

(和本章的每一页一样,绘图调用都是静态代码片段:下面的运行器只显示文本输出。在本地运行它们才能看到图。)

比较各组:公式接口

日常最常见的用法是每组一个箱体,为此 boxplot() 接受一个公式:y ~ group,读作“按 group 拆分 y”。R 内置的 ToothGrowth 数据集是经典示例:60 只豚鼠的牙齿长度,它们分别通过橙汁(OJ)或抗坏血酸(VC)摄入维生素 C:

boxplot(len ~ supp,
        data = ToothGrowth,
        main = "Tooth growth by supplement",
        xlab = "Supplement",
        ylab = "Tooth length")

两个箱体出现在同一个纵向刻度上:OJ 的箱体明显高于 VC,中位数大约是 22,而后者约为 19。因为两个箱体共用一条坐标轴,这种比较天生就是公平的:不会出现两张刻度不同的图、让某一组看起来更好的情况。分组列应该是因子(或字符向量);每个水平对应一个箱体。

公式还可以嵌套:boxplot(len ~ supp * dose, data = ToothGrowth) 会画出六个箱体,每种补充剂与剂量的组合一个。当箱线图中的组间差异看起来是真的时,自然要问下一个问题:这不只是噪声吧?这个问题由 t 检验来回答。

标签、颜色和横向箱线图

样式参数沿用基础绘图的惯例。names 设置箱体的标签,col 设置填充色,传入一个向量就能给每组不同的颜色:

boxplot(len ~ supp,
        data  = ToothGrowth,
        names = c("Orange juice", "Ascorbic acid"),
        col   = c("orange", "lightblue"),
        main  = "Tooth growth by supplement",
        ylab  = "Tooth length")

另外两个开关也很实用:

  • horizontal = TRUE 把整张图旋转过来,让箱体沿水平方向排列。只要组名很长就这样做:横向的标签能保持清晰,而竖向的标签会挤在一起。
  • notch = TRUE 在每个中位数周围刻出一个凹口;如果两组的凹口不重叠,它们的中位数很可能不同。把它当作视觉上的提示,而不是检验。

箱体背后的数字

图上画的所有东西都来自可以打印出来的数字。quantile() 给出五数概括,boxplot.stats() 给出图实际使用的数字,包括离群值。这段代码可以直接在这里运行:

注意这两者是近亲,而不是双胞胎:quantile() 报告的是真实的最小值和最大值,而 s$stats 止于须线,也就是 1.5 × IQR 边界以内最极端的点。两者之间的差距正好是 s$out,这里就是那个孤零零的 95。当有人问“那些离群点是哪几行?”时,x[x %in% boxplot.stats(x)$out](或者按边界值筛选)就能回答。

用 ggplot2 画箱线图

在 ggplot2 中,分组写在美学映射里,而不是公式里:

library(ggplot2)

ggplot(ToothGrowth, aes(x = supp, y = len, fill = supp)) +
    geom_boxplot() +
    labs(title = "Tooth growth by supplement",
         x = "Supplement", y = "Tooth length")

同样是两个箱体,还免费附带了图例和主题。图变复杂时,ggplot2 版本的扩展性更好:按 dose 分面,用 geom_jitter(width = 0.1) 叠加原始数据点,代码依然是声明式的。如果只是分析过程中做一次性比较,boxplot(y ~ g, data = df) 仍然是敲起来最快的写法。

要点回顾

  • 箱线图的构成:中位线,箱体 = IQR,须线 = 1.5 × IQR 以内最极端的点,更外面的点 = 疑似离群值。
  • 单个变量用 boxplot(x);各组并排比较用 boxplot(y ~ group, data = df),共用一个公平的刻度。
  • 用 names、每组一个颜色的 col 向量调整样式,标签很长时用 horizontal = TRUE。
  • boxplot.stats(x)$stats 和 $out 就是用数字表示的图,可以用它们提取图中标出的离群值。
  • ggplot2:使用 geom_boxplot(),把分组映射到 x 或 fill。

下一篇:散点图,从单个变量的分布,到两个变量之间的关系。

常见问题

在 R 中如何画箱线图?

对一个数值向量调用 boxplot(x),得到一个箱体;或者用公式接口 boxplot(y ~ group, data = df),让每组一个箱体并排显示。两者都是基础 R 自带的,不需要安装任何包。

箱线图的各个部分代表什么?

箱体内的粗线是中位数。箱体覆盖四分位距,从第一四分位数到第三四分位数。须线向外延伸到距箱体 1.5 倍 IQR 以内最极端的数据点,须线之外的值都会单独画成点,也就是疑似离群值。

在 R 中如何按组画箱线图?

用公式:boxplot(len ~ supp, data = ToothGrowth) 会为 supp 的每个水平画一个箱体。分组变量应该是因子或字符列;每个水平在同一刻度上对应一个箱体。

如何找出箱线图中显示的离群值?

boxplot.stats(x)$out 返回的正是图中画在须线之外的那些点的值,boxplot.stats(x)$stats 则给出箱体背后的五个数:两端须线的端点、上下铰链和中位数。

Coddy 编程语言插图

和 Coddy 一起学编程

开始