ggplot2:定位

ggplot2:定位

本文的主题是图形的定位,主要讲解了布局分面和坐标系如何工作两个问题。

分面

ggplot2提供两种分面类型:网格型(facet_grid)和封装型(facet_wrap)。网格分面生成的是一个2维的面板网格,面板的行与列通过变量来定义;封装分面这先生成一个1维的面板条块,然后再封装到2维中。

分面系统有两个基本参数,一个是分面变量的设置,另一个是指定分面的位置标度是全局还是局部。两套分面系统在参数设定上略有不同。

在qplot()中可以选择分面系统,2维分面(比如x~y)使用facet_grid,1维分面(比如 ~x)使用facet_wrap。

网格分面

1
2
3
4
5
library(ggplot2)
library(ggthemes)
theme_set(theme_igray(base_size = 20, base_family = "FiraSans-Regular"))
mpg2 <- subset(mpg, cyl != 5 & drv %in% c("4", "f"))
qplot(cty, hwy, data = mpg2) + facet_grid(.~cyl)


1
qplot(cty, hwy, data = mpg2) + facet_grid(cyl~.)


1
qplot(cty, hwy, data = mpg2) + facet_grid(drv ~ cyl)

边际图

1
2
p <- qplot(displ, hwy, data= mpg2) + geom_smooth(method = "lm", se = F)
p + facet_grid(cyl ~ drv)


1
p + facet_grid(cyl ~ drv, margins = T)


1
p + aes(colour = drv) + facet_grid(cyl ~ drv, margins = T)

封装分面

facet_wrap并不是用两个或更多个变量来生成一个二维网格,而是先生成一个长的面板条块(由任意数目的变量生成,然后将它封装在二维中)。在处理单个多水平变量时,这种处理方式是非常有用的,它可以有效利用空间来安放图形。lattice中的栅栏(trellis)图形也是采取了这个方法。

下图展示了每10年电影平均评分的分布情况:

1
2
3
4
5
6
library(ggplot2movies)
library(plyr)
movies$decode <- round_any(movies$year, 10, floor)
qplot(rating, ..density.., data = subset(movies, decode > 1890),
geom = "histogram", binwidth = 0.5) +
facet_wrap(~ decode, ncol = 4) + ylab("Density")

标度控制

对于两种分面,你可以通过调整参数scales来控制面板的位置标度是相同的还是自由的:

  • scales = “fixed”:x和y的标度在所有面板中都相同;
  • scales = “free”:x和y的标度在每个面板中都可以变化;
  • scales = “free_x”:x的标度可变,y的标度固定;
  • scales = “free_y”:y的标度可变,x的标度固定;
    1
    2
    p <- qplot(cty, hwy, data = mpg)
    p + facet_wrap(~ cyl)


1
p + facet_wrap(~cyl, scales = "free")


1
2
3
4
5
library(reshape2)
data(economics)
em <- melt(economics, id = "date")
qplot(date, value, data = em, geom = "line", group = variable) +
facet_grid(variable ~ ., scale = "free_y")

使用网格分面时facet_grid有一个额外的限制:同列的面板必须有相同的x标度,同行的面板必须有相同的y标度。这是因为网格分面中,每列必须都共用一个x轴,每行都必须共用一个y轴。

facet_grid还有个额外的参数space,值可以为“free”或“fixed”。但space设定为free的时候,每列(行)的宽度(高度)与该列(行)的标度范围成比例。这将使所有面板的标度比例相同:每个面板中的1cm都映射为相同的数据范围,面板b有4个单位的范围,那么1/3的空间将分配给a,其余的分配给b。这对分类标度非常有用:

1
2
3
4
5
6
mpg3 <- within(mpg2, {
model <- reorder(model, cty)
manufacturer <- reorder(manufacturer, -cty)
})
models <- qplot(cty, model, data = mpg3)
models


1
2
3
4
# 使用生产商进行分面
models + facet_grid(manufacturer ~ ., scales = "free",
space = "free") +
theme(strip.text.y = element_text(angle = 0))

分组与分面

与通过调整图形属性来分组不同,分面提供了另一种分组的途径。依据子集相对位置的不同,这两种绘图技巧都有相应的优缺点。

1
2
3
4
5
6
7
8
9
10
11
xmaj <- c(0.3, 0.5, 1.3, 5)
xmin <- as.vector(outer(1:10, 10^c(-1, 0)))
ymaj <- c(500, 1000, 5000, 10000)
ymin <- as.vector(outer(1:10, 10^c(2,3,4)))
dplot <- ggplot(subset(diamonds, color %in% c("D", "E", "G", "J")),
aes(carat, price, colour)) +
scale_x_log10(breaks = xmaj, labels = xmaj, minor = xmin) +
scale_y_log10(breaks = ymaj, labels = ymaj, minor = ymin) +
scale_colour_hue(limits = levels(diamonds$color)) +
theme(legend.position = "none")
dplot + geom_point()


1
dplot + geom_point() + facet_grid(.~color)


1
dplot + geom_smooth(method = "lm", se = F, fullrange = F)


1
dplot + geom_smooth(method = "lm", se = F, fullrange = T) + facet_grid(.~color)

并列与分面

分面可以绘制出与图形并列类似的图形效果。它们之间的主要区别在于标注方式:分面图形在上方有颜色的标注,同时下方有切工的标注,而并列图形中的标签需要做一些调整,以增强可读性:

1
ggplot(diamonds, aes(x = color, fill = cut)) + geom_bar(position = "dodge")


1
2
ggplot(diamonds, aes(x = color, fill = cut)) + geom_bar() + facet_grid(. ~ color) +
theme(axis.text.x = element_text(angle = 90, hjust = 1, size = 8, colour = "grey50"))

除了标注之外,当两个变量的因子水平几乎完全交叉,而部分变量组合缺失时,两种绘图方式也就会有所不同。此时,并列图形的用处不大,因为它只是对条形图做局部分割,没有任何标签。而分面就实用得多,它能控制分割方式是局部的(scales = “freee_x”, space = “free”)还是全局的(scales = “fixed”)。下图对两者的效果进行了比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mpg4 <-subset(mpg, manufacturer %in% c("audi", "volkswagen", "jeep"))
mpg4$manufacturer <- as.character(mpg4$manufacturer)
mpg4$model <- as.character(mpg4$model)

base <- ggplot(mpg4, aes(fill = model)) +
geom_bar(position = "dodge") +
theme(legend.position = "none")
p1 <- base + aes(x = model) + facet_grid(. ~ manufacturer)
p2 <- last_plot() + facet_grid(. ~ manufacturer, scales = "free_x", space = "free")
p3 <- base + aes(x = manufacturer)
library(grid)
png("20181013c19.png", width = 24, height = 6)
grid.newpage()
pushViewport(viewport(layout = grid.layout(1, 3)))
vp <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(p1, vp = vp(1, 1))
print(p2, vp = vp(1, 2))
print(p3, vp = vp(1, 3))
dev.off()

总之,图形是选择分面还是并列,要视两变量的关系而定:

  1. 水平完全交叉:基本等同;
  2. 水平几乎完全几乎交叉:有相同标度的分面保证了所有的水平组合可见,即使有些是空的;
  3. 水平无交叉(嵌套):标度只有的分面会对每个有较高水平的组别分配充足的作图空间,并对每个条目进行标注。

连续型变量

对连续型变量的分面,首先需要将其变换为离散型。有三种转化方法:

  1. 将数据分为n个长度相同的部分:用cut_interval(x, n = 10)控制划分的数目,或用cut_interval(x, length = 1)控制每个部分的长度。控制划分数目会容易些,但划分区间的端点值可能不太美观。
  2. 将数据划分为n个相同数点的部分:cut_number(x, n = 10)。这使得分面间进行对比会更容易一些(分面有相同数目的点,但需要注意每个部分的标度范围是不同的)。

下面的代码展示了三种转化方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mpg2$disp_ww <- cut_interval(mpg2$displ, length = 1)
mpg2$disp_wn <- cut_interval(mpg2$displ, n = 6)
mpg2$disp_nn <- cut_number(mpg2$displ, n = 6)
plot <- qplot(cty, hwy, data = mpg2) + labs(x = NULL, y = NULL)
(p1 <- plot + facet_wrap(~ disp_ww, nrow = 1))
(p2 <- plot + facet_wrap(~ disp_wn, nrow = 1))
(p3 <- plot + facet_wrap(~ disp_nn, nrow = 1))
png("20181014a1.png", width = 1800, height = 1000)
grid.newpage()
pushViewport(viewport(layout = grid.layout(3, 1)))
vp <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(p1, vp = vp(1, 1))
print(p2, vp = vp(2, 1))
print(p3, vp = vp(3, 1))
dev.off()

注意,分面表达式支队包含在数据集中的变量有效果,不是变量的函数,因此需在数据集里创建一个包含了离散化后的数据的新变量。

坐标系

ggplot2中可用的坐标系:

名字 描述
cartesian 笛卡尔坐标系
equal 同尺度笛卡尔坐标系
flip 翻转的笛卡尔坐标系
trans 变换的笛卡尔坐标系
map 地图射影
polar 极坐标系

笛卡尔坐标系

有四种基于笛卡尔的坐标系:coord_cartesian,coord_equal,coord_flip和coord_trans,由于x和y的位置都是正交映射到图形的位置上,因此四种坐标系本质上仍是笛卡尔型的,它们之间有许多共同之处。

设置范围

coord_cartesian有两个参数xlim和ylim,和标度中的范围参数不同的是,这里的范围设置不会删除任何数据,只是选择展示范围。

1
2
3
4
5
6
7
8
9
10
11
(p <- qplot(disp, wt, data = mtcars) + geom_smooth())
(p1 <- p + scale_x_continuous(limits = c(325, 500)))
(p2 <- p + coord_cartesian(xlim = c(325, 500)))
png("20181014a2.png", width = 1800, height = 600)
grid.newpage()
pushViewport(viewport(layout = grid.layout(1, 3)))
vp <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(p, vp = vp(1, 1))
print(p1, vp = vp(1, 2))
print(p2, vp = vp(1, 3))
dev.off()

可以看出,第二幅图重新拟合了子数据集。

坐标轴翻转

1
2
3
4
5
6
7
8
9
10
11
(p <- qplot(displ, cty, data = mpg) + geom_smooth())
(p1 <- qplot(cty, displ, data = mpg) + geom_smooth())
(p2 <- p1 + coord_flip())
png("20181014a3.png", width = 1800, height = 600)
grid.newpage()
pushViewport(viewport(layout = grid.layout(1, 3)))
vp <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(p, vp = vp(1, 1))
print(p1, vp = vp(1, 2))
print(p2, vp = vp(1, 3))
dev.off()

变换

1
2
3
4
5
6
7
8
9
10
p1 <- qplot(carat, price, data = diamonds, log = "xy") + geom_smooth(method = "lm")
library(scales)
p2 <- last_plot() + coord_trans(x = exp_trans(10), y = exp_trans(10))
png("20181014a4.png", width = 1800, height = 600)
grid.newpage()
pushViewport(viewport(layout = grid.layout(1, 2)))
vp <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(p1, vp = vp(1, 1))
print(p2, vp = vp(1, 2))
dev.off()

相同标度

coord_equal保证了x轴和y轴有相同的标度:x轴上和y轴上的1cm代表相同的数据波动范围。默认设定为1:1,可以通过参数ratio设定两者的尺度比例,横款比的设定保证了不管图形输出设备的形状如何变化,映射都保持不变。

非笛卡尔坐标系

1
2
3
4
5
6
7
8
9
10
11
12
# 堆叠条形图
(pie <- ggplot(mtcars, aes(x = factor(1), fill = factor(cyl))) + geom_bar(width = 1))

# 饼图
(pie2 <- pie + coord_polar())
png("20181014a5.png", width = 1800, height = 900)
grid.newpage()
pushViewport(viewport(layout = grid.layout(1, 2)))
vp <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(pie, vp = vp(1, 1))
print(pie2, vp = vp(1, 2))
dev.off()

# R

评论

程振兴

程振兴 @czxa.top
截止今天,我已经在本博客上写了607.9k个字了!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×