4.ggplot2——群组几何对象
4 群组几何对象
几何对象可以简单分为个体几何对象和总体几何对象。个体几何对象对数据框的每一行数据绘制一个可以区别于其他个体的图形对象。例如,散点图中每个点代表一个观测。群组几何对象则用来表示多个观测。这可能是统计摘要的结果,如箱线图,也可能是几何图形的基础展示,如多边形图。折线图和路径图同时具备两种特点:每条线由一系列线段组成,但每条线段代表两个点。我们如何分配并控制观察到图形元素?这是group
图形属性的工作。
默认情况下,group
图形属性被映射到图中所有离散变量中。这通常会正确地对数据进行分区,但是当它没有正确分区时,或者当图中没有使用离散变量时,您需要定义分组结构,通过将group映射到每个组具有不同值的变量来明确。
默认有三种常见的情况分组是不能解决的,我们将在下面逐一考虑。在以下示例中,我们将使用来自 nlme 包的简单纵向数据集Oxboys
。它记录了26 名男孩 (Subject
)在9 个不同时期 ( Occasion
)的身高 ( height
) 和中心化后的年龄(age
)。Subject
和Occassion
以有序因子形式存储。
data(Oxboys, package = "nlme")
head(Oxboys)
#> Subject age height Occasion
#> 1 1 -1.0000 140 1
#> 2 1 -0.7479 143 2
#> 3 1 -0.4630 145 3
#> 4 1 -0.1643 147 4
#> 5 1 -0.0027 148 5
#> 6 1 0.2466 150 6
4.1 多个分组与单一图形属性
在许多情况下,我们希望将数据分组,以相同的方式对每个组进行处理。换句话说,您希望能够区分单个个体,但不能识别它们。这在许多个体的纵向研究中很常见,其中的图通常被描述为意大利面条图。例如,下图显示了每个男孩(每个Subject
)的成长轨迹:
ggplot(Oxboys, aes(age, height, group = Subject)) +
geom_point() +
geom_line()

如果您错误地指定了分组变量,您将获得一张无用的锯齿图形:
ggplot(Oxboys, aes(age, height)) +
geom_point() +
geom_line()

如果分组不是由单个变量定义的,而是由多个变量的组合定义的,则使用interaction()来组合它们,例如 aes(group = interaction(school_id, student_id))
.
4.2 不同图层上的不同分组
有时我们将不同水平下的数据整合,然后使用统计汇总信息绘制图形:一层可能显示单一属性,而另一层可能显示整体汇总信息。在前面的示例的基础上,假设我们要添加一条平滑线,显示所有男孩的整体变化趋势。如果我们在两层中使用相同的分组,我们会得到每个男孩的平滑分组结果:
ggplot(Oxboys, aes(age, height, group = Subject)) +
geom_line() +
geom_smooth(method = "lm", se = FALSE)
#> `geom_smooth()` using formula 'y ~ x'

这不是我们想要的;我们无意中为每个男孩添加了一条平滑的线条。分组控制着几何图形的显示和统计数据的操作:每组运行一个统计转换。
我们没有在ggplot()中设置分组图形属性,它将应用于所有图层,而是将其设置为geom_line()仅适用于线条。图中没有离散变量,因此默认分组变量将是一个常数,我们得到一个平滑:
ggplot(Oxboys, aes(age, height)) +
geom_line(aes(group = Subject)) +
geom_smooth(method = "lm", size = 2, se = FALSE)
#> `geom_smooth()` using formula 'y ~ x'

4.3 修改默认分组
某些图在 x轴上有离散型变量,但您仍想绘制连接各个组的线。这是交互图、轮廓图和平行坐标图等中使用的策略。例如,假设我们在每个测量场合都绘制了高度的箱线图:
ggplot(Oxboys, aes(Occasion, height)) +
geom_boxplot()

该图中有一个离散变量Occasion
,因此我们为每个唯一的 x 值得到一个箱线图。现在我们要叠加连接每个男孩的线条。简单地添加geom_line()是行不通的:线条是在每个场景中绘制的,而不是跨越每个对象:
ggplot(Oxboys, aes(Occasion, height)) +
geom_boxplot() +
geom_line(colour = "#3366FF", alpha = 0.5)

为了得到我们想要的图形,我们需要覆盖分组,我们要为每个男孩绘制一条线:
ggplot(Oxboys, aes(Occasion, height)) +
geom_boxplot() +
geom_line(aes(group = Subject), colour = "#3366FF", alpha = 0.5)

4.4 图形属性与图形对象的匹配
群组几何对象的最后一个重要问题是如何将个体观察的图形属性映射到整体的图形属性。当不同的图形属性映射到单个几何元素时会发生什么?
在 ggplot2 中,这对于不同的群组几何对象的处理方式不同。折线图和路径图按照“第一个值”原则运行:每个段由两个观察定义,ggplot2在绘制线段时应与第一个观察值相关的图形属性(例如,颜色)。也就是说,绘制第一个线段时使用第一个观察值的图形属性,绘制第二个线段时使用第二个观察值的图形属性,依此类推。最后一次观察的图形属性值不会被使用:
df <- data.frame(x = 1:3, y = 1:3, colour = c(1,3,5))
ggplot(df, aes(x, y, colour = factor(colour))) +
geom_line(aes(group = 1), size = 2) +
geom_point(size = 5)
ggplot(df, aes(x, y, colour = colour)) +
geom_line(aes(group = 1), size = 2) +
geom_point(size = 5)


在第一个图中颜色是离散的,第一个点和第一条线段是红色的,第二个点和第二条线段是绿色的,最后一个点(没有对应的线段)是蓝色的。在第二个图中颜色是连续的,同样的原则适用于三种不同深浅的蓝色。请注意,即使颜色变量是连续的,ggplot2 也不会从一种图形属性值平滑地混合到另一种图形属性值。如果您想要这样的结果,您可以自己执行线性插值:
xgrid <- with(df, seq(min(x), max(x), length = 50))
interp <- data.frame(
x = xgrid,
y = approx(df$x, df$y, xout = xgrid)$y,
colour = approx(df$x, df$colour, xout = xgrid)$y
)
ggplot(interp, aes(x, y, colour = colour)) +
geom_line(size = 2) +
geom_point(data = df, size = 5)

值得注意的是对路径图和折线图的额外限制:线类型必须在每个个体的线段上保持不变。在 R 中,无法绘制具有不同线型的线。
其他群体几何对象呢,比如多边形?大多数群体几何对象比折线图和路径图更复杂,单个几何对象可以映射到许多观测值。例如,您将如何为边界上每个点具有不同填充颜色的多边形着色?由于这种歧义,ggplot2 采用了一个简单的规则:仅当各个组件都相同时才使用它们的图形属性。如果每个组件的图形属性不同,ggplot2 将使用默认值。
在图形属性映射到连续变量时,这些问题最为相关。对于离散变量,ggplot2 的默认行为是将变量视为组美学的一部分,如上所述。这具有将群体几何对象 分成更小的部分的效果。这对于条形图和面积图特别有效,因为堆叠各个部分会产生与原始未分组数据相同的形状:
ggplot(mpg, aes(class)) +
geom_bar()
ggplot(mpg, aes(class, fill = drv)) +
geom_bar()


如果您尝试以相同的方式将填充属性映射到连续变量(例如hwy
),则它不起作用。默认分组仅基于class
,因此每个条现在都与多种颜色相关联(取决于hwy
每个类中观察的值)。因为一个条形只能显示一种颜色,所以 ggplot2 在这种情况下会恢复为默认的灰色。要显示多种颜色,我们需要为每个class
设定多个条形块,我们可以通过覆盖分组来获得:
ggplot(mpg, aes(class, fill = hwy)) +
geom_bar()
ggplot(mpg, aes(class, fill = hwy, group = hwy)) +
geom_bar()


在右侧的图中,每个class
的“阴影条”是通过将许多不同的条堆叠在彼此之上构建的,每个条都根据hwy
的值填充了不同的阴影。请注意,执行此操作时,条形将按照分组变量(在本例中hwy
)定义的顺序堆叠。如果您需要对这种行为进行精细控制,则需要根据需要创建一个具有排序级别的因子。
4.5 练习
-
为
cyl
的每个值绘制hwy
的箱线图,而不将其cyl
转化为因子。你需要设置什么额外的图形属性? -
修改下图,使
displ
的每个整数值的箱线图。ggplot(mpg, aes(displ, cty)) + geom_boxplot()
-
在说明将连续颜色和离散颜色映射到一条线之间的区别时,离散示例需要设置
aes(group = 1)
. 为什么?如果省略会发生什么?aes(group = 1)
和aes(group = 2)
之间有什么区别?为什么? -
以下每个图中有多少个条形?
ggplot(mpg, aes(drv)) + geom_bar() ggplot(mpg, aes(drv, fill = hwy, group = hwy)) + geom_bar() library(dplyr) mpg2 <- mpg %>% arrange(hwy) %>% mutate(id = seq_along(hwy)) ggplot(mpg2, aes(drv, fill = hwy, group = id)) + geom_bar()
(提示:尝试在每个条形周围添加轮廓
colour = "white"
) -
安装 babynames 包。它包含有关婴儿名字在美国受欢迎程度的数据。运行以下代码并修复生成的图形。这张图形有什么问题呢?
library(babynames) hadley <- dplyr::filter(babynames, name == "Hadley") ggplot(hadley, aes(year, n)) + geom_line()