ggplot2:如何强制情节太少的方面的数量?
ggplot2: How to force the number of facets with too few plots?
为了绘制每个 ggplot 图的 9 个直方图,我使用了以下数据:
id variable value
1 Segment III | RIM BlackBerry Pearl | 5.600000
2 Display size | RIM BlackBerry Pearl | 6.500000
3 Voice/call quality | RIM BlackBerry Pearl | 5.600000
4 Instant messaging availability | RIM BlackBerry Pearl | 7.200000
5 Media quality | RIM BlackBerry Pearl | 6.100000
6 Ease of use for typing | RIM BlackBerry Pearl | 5.700000
7 Speed in accessing email | RIM BlackBerry Pearl | 6.400000
8 Segment II | RIM BlackBerry Pearl | 5.545455
9 Value for money | RIM BlackBerry Pearl | 6.000000
10 Segment III | Palm Treo 700p | 4.320000
11 Display size | Palm Treo 700p | 6.500000
12 Voice/call quality | Palm Treo 700p | 8.000000
13 Instant messaging availability | Palm Treo 700p | 5.100000
14 Media quality | Palm Treo 700p | 7.000000
15 Ease of use for typing | Palm Treo 700p | 6.200000
16 Speed in accessing email | Palm Treo 700p | 6.500000
17 Segment II | Palm Treo 700p | 4.454545
18 Value for money | Palm Treo 700p | 5.400000
19 Segment III | Motorola Q | 4.680000
20 Display size | Motorola Q | 7.400000
21 Voice/call quality | Motorola Q | 4.800000
22 Instant messaging availability | Motorola Q | 5.300000
23 Media quality | Motorola Q | 6.900000
24 Ease of use for typing | Motorola Q | 7.400000
25 Speed in accessing email | Motorola Q | 8.000000
26 Segment II | Motorola Q | 3.121212
27 Value for money | Motorola Q | 4.200000
28 Segment III | Nokia 9300 | 4.360000
29 Display size | Nokia 9300 | 6.400000
30 Voice/call quality | Nokia 9300 | 7.800000
31 Instant messaging availability | Nokia 9300 | 6.700000
32 Media quality | Nokia 9300 | 5.900000
33 Ease of use for typing | Nokia 9300 | 4.500000
34 Speed in accessing email | Nokia 9300 | 6.300000
35 Segment II | Nokia 9300 | 7.181818
36 Value for money | Nokia 9300 | 4.600000
37 Segment III | Sony Ericsson M600i | 4.360000
38 Display size | Sony Ericsson M600i | 7.300000
39 Voice/call quality | Sony Ericsson M600i | 8.000000
40 Instant messaging availability | Sony Ericsson M600i | 1.500000
41 Media quality | Sony Ericsson M600i | 7.800000
42 Ease of use for typing | Sony Ericsson M600i | 5.000000
43 Speed in accessing email | Sony Ericsson M600i | 8.100000
44 Segment II | Sony Ericsson M600i | 3.606061
45 Value for money | Sony Ericsson M600i | 4.000000
46 Segment III | Sidekick3 | 7.040000
47 Display size | Sidekick3 | 7.200000
48 Voice/call quality | Sidekick3 | 6.300000
49 Instant messaging availability | Sidekick3 | 7.200000
50 Media quality | Sidekick3 | 6.400000
51 Ease of use for typing | Sidekick3 | 6.800000
52 Speed in accessing email | Sidekick3 | 6.200000
53 Segment II | Sidekick3 | 3.424242
54 Value for money | Sidekick3 | 5.300000
然后我使用了下面的代码:
ggplot(data = data_sub, aes(x = variable, y = value)) +
geom_bar(stat = "identity") +
facet_wrap(~id, ncol = 3) +
coord_flip() +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid = element_blank(),
legend.position = "none")
并得到:
我的问题:
当我的图表较少时,例如只有一个,我想保持这种格式。但是,我只得到如下大图(不要介意图例)。
我怎样才能得到像下面这样的东西?
试试这个
d <- subset(mtcars, cyl==4)
d$cyl <- factor(d$cyl, levels=unique(mtcars$cyl))
ggplot(d, aes(x=am, y=wt) ) +
geom_point() +
facet_grid(.~cyl, drop = FALSE)
一种方法是为每个非空因子水平创建一个图,并为每个空因子水平创建一个空白占位符:
首先,使用内置的 mtcars
数据框,我们将分面变量设置为具有 9 个级别的因子,但对于任何数据只有 5 个级别:
library(ggplot2)
library(grid)
library(gridExtra)
d = mtcars
set.seed(4193)
d$cyl = sample(1:9, nrow(d), replace=TRUE)
d$cyl <- factor(d$cyl, levels=sort(unique(d$cyl)))
d <- subset(d, cyl %in% c(1,5,7:9))
# Identify factor levels without any data
blanks = which(table(d$cyl)==0)
# Initialize a list
pl = list()
下面的 for 循环遍历分面变量的每个级别,并创建具有数据级别的图或 nullGrob
(即,如果有数据,则图所在的空占位符该因素水平)并将其添加到列表 pl
。
for (i in 1:length(levels(d$cyl))) {
if(i %in% blanks) {
pl[[i]] = nullGrob()
} else {
pl[[i]] = ggplot(d[d$cyl %in% levels(d$cyl)[i], ], aes(x=am, y=wt) ) +
geom_point() +
facet_grid(.~ cyl)
}
}
现在,布置图并在它们周围添加边框:
do.call(grid.arrange, c(pl, ncol=3))
grid.rect(.5, .5, gp=gpar(lwd=2, fill=NA, col="black"))
更新: 我想添加到我的答案中的一个功能是删除不在最左列或底行的图的轴标签(待更像是 OP 中的格式)。以下是我不太成功的尝试。
当您从一些图中删除轴刻度 and/or 标签时出现的问题是,不同图中的绘图区域最终大小不同。这样做的原因是所有的图都占据了相同的物理区域,但是带有轴标签的图使用了一些区域作为轴标签,使得它们的绘图区域相对于没有轴标签的图更小。
我曾希望我可以使用 cowplot
包(由@ClausWilke 编写)中的 plot_grid
解决这个问题,但是 plot_grid
不适用于 nullGrob
秒。然后@baptiste 为这个问题添加了另一个答案,他已经删除了这个答案,但对于声誉至少为 10,000 的 SO 用户来说仍然可见。这个答案让我知道了他的 egg
包和 set_panel_size
函数,用于在不同的 ggplots 之间设置一个通用的面板大小。
下面是我尝试使用set_panel_size
来解决地块问题。它不是很成功,我将在展示代码和情节后更详细地讨论。
# devtools::install_github("baptiste/egg")
library(egg)
# Fake data for making a barplot. Once again we have 9 facet levels,
# but with data for only 5 of the levels.
set.seed(4193)
d = data.frame(facet=rep(LETTERS[1:9],each=100),
group=sample(paste("Group",1:5),900,replace=TRUE))
d <- subset(d, facet %in% LETTERS[c(1,5,7:9)])
# Identify factor levels without any data
blanks = which(table(d$facet)==0)
# Initialize a list
pl = list()
for (i in 1:length(levels(d$facet))) {
if(i %in% blanks) {
pl[[i]] = nullGrob()
} else {
# Create the plot, including a common y-range across all plots
# (though this becomes the x-range due to coord_flip)
pl[[i]] = ggplot(d[d$facet %in% levels(d$facet)[i], ], aes(x=group) ) +
geom_bar() +
facet_grid(. ~ facet) +
coord_flip() +
labs(x="", y="") +
scale_y_continuous(limits=c(0, max(table(d$group, d$facet))))
# If the panel isn't on the left edge, remove y-axis labels
if(!(i %in% seq(1,9,3))) {
pl[[i]] = pl[[i]] + theme(axis.text.y=element_blank(),
axis.ticks.y=element_blank())
}
# If the panel isn't on the bottom, remove x-axis labels
if(i %in% 1:6) {
pl[[i]] = pl[[i]] + theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank())
}
}
# If the panel is a plot (rather than a nullGrob),
# remove margins and set to common panel size
if(any(class(pl[[i]]) %in% c("ggplot","gtable"))) {
pl[[i]] = pl[[i]] + theme(plot.margin=unit(rep(-1,4), "lines"))
pl[[i]] = set_panel_size(pl[[i]], width=unit(4,"cm"), height=unit(3,"cm"))
}
}
现在布置地块:
do.call(grid.arrange, c(pl, ncol=3))
grid.rect(.5, .5, gp=gpar(lwd=2, fill=NA, col="black"))
正如您在下图中所看到的,即使这些图都具有相同的面板大小,它们之间的边距也不是恒定的,这可能是由于 grid.arrange
处理 null grobs 间距的方式,具体取决于在哪些职位上有实际情节。另外,因为 set_panel_size
设置了绝对大小,所以我必须手动调整最终图的大小,以使面板尽可能靠近在一起,同时仍然避免重叠。我希望 SO 的一位常驻 grid
专家会顺便过来并提出更有效的方法。
(另请注意,使用这种方法,您最终可能会在给定的行或列中没有带标签的图。在下面的示例中,图 "E" 没有 y 轴标签,而图 "D" 丢失,所以你必须在不同的行中查看标签是什么。如果只有图 "B"、"C"、"E" 和 "F" 存在,布局中不会有 任何 标记图。我不知道 OP 想如何处理这种情况(一种选择是添加逻辑以将标签保留在 "interior" 如果给定的行或列不存在 "outer" 图),但我认为值得指出。)
一个可能的解决方案是生成一个 ggplot 列表,并用虚拟占位符替换其中的一些。
d <- data.frame(x=rnorm(90), y=rnorm(90),
f1 = gl(3, 30) , f2 = rep(gl(3, 10), 3) )
p <- ggplot(d, aes(x, y)) +
geom_point()
# if you want to keep the facet labels
# p <- p + facet_grid(f1~f2)
library(plyr)
pl <- dlply(d, .(f1, f2), "%+%", e1=p, .drop = FALSE)
.dummy_plot <- ggplot() + theme_void()
pl[c(3,4,7)] <- rep(list(.dummy_plot), 3, simplify=FALSE)
# devtools::install_github("baptiste/egg)
library(grid)
grid.newpage()
grid.draw(egg::ggarrange(plots=pl))
# alternatively
# library(cowplot)
# plot_grid(plotlist = pl)
为了绘制每个 ggplot 图的 9 个直方图,我使用了以下数据:
id variable value
1 Segment III | RIM BlackBerry Pearl | 5.600000
2 Display size | RIM BlackBerry Pearl | 6.500000
3 Voice/call quality | RIM BlackBerry Pearl | 5.600000
4 Instant messaging availability | RIM BlackBerry Pearl | 7.200000
5 Media quality | RIM BlackBerry Pearl | 6.100000
6 Ease of use for typing | RIM BlackBerry Pearl | 5.700000
7 Speed in accessing email | RIM BlackBerry Pearl | 6.400000
8 Segment II | RIM BlackBerry Pearl | 5.545455
9 Value for money | RIM BlackBerry Pearl | 6.000000
10 Segment III | Palm Treo 700p | 4.320000
11 Display size | Palm Treo 700p | 6.500000
12 Voice/call quality | Palm Treo 700p | 8.000000
13 Instant messaging availability | Palm Treo 700p | 5.100000
14 Media quality | Palm Treo 700p | 7.000000
15 Ease of use for typing | Palm Treo 700p | 6.200000
16 Speed in accessing email | Palm Treo 700p | 6.500000
17 Segment II | Palm Treo 700p | 4.454545
18 Value for money | Palm Treo 700p | 5.400000
19 Segment III | Motorola Q | 4.680000
20 Display size | Motorola Q | 7.400000
21 Voice/call quality | Motorola Q | 4.800000
22 Instant messaging availability | Motorola Q | 5.300000
23 Media quality | Motorola Q | 6.900000
24 Ease of use for typing | Motorola Q | 7.400000
25 Speed in accessing email | Motorola Q | 8.000000
26 Segment II | Motorola Q | 3.121212
27 Value for money | Motorola Q | 4.200000
28 Segment III | Nokia 9300 | 4.360000
29 Display size | Nokia 9300 | 6.400000
30 Voice/call quality | Nokia 9300 | 7.800000
31 Instant messaging availability | Nokia 9300 | 6.700000
32 Media quality | Nokia 9300 | 5.900000
33 Ease of use for typing | Nokia 9300 | 4.500000
34 Speed in accessing email | Nokia 9300 | 6.300000
35 Segment II | Nokia 9300 | 7.181818
36 Value for money | Nokia 9300 | 4.600000
37 Segment III | Sony Ericsson M600i | 4.360000
38 Display size | Sony Ericsson M600i | 7.300000
39 Voice/call quality | Sony Ericsson M600i | 8.000000
40 Instant messaging availability | Sony Ericsson M600i | 1.500000
41 Media quality | Sony Ericsson M600i | 7.800000
42 Ease of use for typing | Sony Ericsson M600i | 5.000000
43 Speed in accessing email | Sony Ericsson M600i | 8.100000
44 Segment II | Sony Ericsson M600i | 3.606061
45 Value for money | Sony Ericsson M600i | 4.000000
46 Segment III | Sidekick3 | 7.040000
47 Display size | Sidekick3 | 7.200000
48 Voice/call quality | Sidekick3 | 6.300000
49 Instant messaging availability | Sidekick3 | 7.200000
50 Media quality | Sidekick3 | 6.400000
51 Ease of use for typing | Sidekick3 | 6.800000
52 Speed in accessing email | Sidekick3 | 6.200000
53 Segment II | Sidekick3 | 3.424242
54 Value for money | Sidekick3 | 5.300000
然后我使用了下面的代码:
ggplot(data = data_sub, aes(x = variable, y = value)) +
geom_bar(stat = "identity") +
facet_wrap(~id, ncol = 3) +
coord_flip() +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid = element_blank(),
legend.position = "none")
并得到:
我的问题:
当我的图表较少时,例如只有一个,我想保持这种格式。但是,我只得到如下大图(不要介意图例)。
我怎样才能得到像下面这样的东西?
试试这个
d <- subset(mtcars, cyl==4)
d$cyl <- factor(d$cyl, levels=unique(mtcars$cyl))
ggplot(d, aes(x=am, y=wt) ) +
geom_point() +
facet_grid(.~cyl, drop = FALSE)
一种方法是为每个非空因子水平创建一个图,并为每个空因子水平创建一个空白占位符:
首先,使用内置的 mtcars
数据框,我们将分面变量设置为具有 9 个级别的因子,但对于任何数据只有 5 个级别:
library(ggplot2)
library(grid)
library(gridExtra)
d = mtcars
set.seed(4193)
d$cyl = sample(1:9, nrow(d), replace=TRUE)
d$cyl <- factor(d$cyl, levels=sort(unique(d$cyl)))
d <- subset(d, cyl %in% c(1,5,7:9))
# Identify factor levels without any data
blanks = which(table(d$cyl)==0)
# Initialize a list
pl = list()
下面的 for 循环遍历分面变量的每个级别,并创建具有数据级别的图或 nullGrob
(即,如果有数据,则图所在的空占位符该因素水平)并将其添加到列表 pl
。
for (i in 1:length(levels(d$cyl))) {
if(i %in% blanks) {
pl[[i]] = nullGrob()
} else {
pl[[i]] = ggplot(d[d$cyl %in% levels(d$cyl)[i], ], aes(x=am, y=wt) ) +
geom_point() +
facet_grid(.~ cyl)
}
}
现在,布置图并在它们周围添加边框:
do.call(grid.arrange, c(pl, ncol=3))
grid.rect(.5, .5, gp=gpar(lwd=2, fill=NA, col="black"))
更新: 我想添加到我的答案中的一个功能是删除不在最左列或底行的图的轴标签(待更像是 OP 中的格式)。以下是我不太成功的尝试。
当您从一些图中删除轴刻度 and/or 标签时出现的问题是,不同图中的绘图区域最终大小不同。这样做的原因是所有的图都占据了相同的物理区域,但是带有轴标签的图使用了一些区域作为轴标签,使得它们的绘图区域相对于没有轴标签的图更小。
我曾希望我可以使用 cowplot
包(由@ClausWilke 编写)中的 plot_grid
解决这个问题,但是 plot_grid
不适用于 nullGrob
秒。然后@baptiste 为这个问题添加了另一个答案,他已经删除了这个答案,但对于声誉至少为 10,000 的 SO 用户来说仍然可见。这个答案让我知道了他的 egg
包和 set_panel_size
函数,用于在不同的 ggplots 之间设置一个通用的面板大小。
下面是我尝试使用set_panel_size
来解决地块问题。它不是很成功,我将在展示代码和情节后更详细地讨论。
# devtools::install_github("baptiste/egg")
library(egg)
# Fake data for making a barplot. Once again we have 9 facet levels,
# but with data for only 5 of the levels.
set.seed(4193)
d = data.frame(facet=rep(LETTERS[1:9],each=100),
group=sample(paste("Group",1:5),900,replace=TRUE))
d <- subset(d, facet %in% LETTERS[c(1,5,7:9)])
# Identify factor levels without any data
blanks = which(table(d$facet)==0)
# Initialize a list
pl = list()
for (i in 1:length(levels(d$facet))) {
if(i %in% blanks) {
pl[[i]] = nullGrob()
} else {
# Create the plot, including a common y-range across all plots
# (though this becomes the x-range due to coord_flip)
pl[[i]] = ggplot(d[d$facet %in% levels(d$facet)[i], ], aes(x=group) ) +
geom_bar() +
facet_grid(. ~ facet) +
coord_flip() +
labs(x="", y="") +
scale_y_continuous(limits=c(0, max(table(d$group, d$facet))))
# If the panel isn't on the left edge, remove y-axis labels
if(!(i %in% seq(1,9,3))) {
pl[[i]] = pl[[i]] + theme(axis.text.y=element_blank(),
axis.ticks.y=element_blank())
}
# If the panel isn't on the bottom, remove x-axis labels
if(i %in% 1:6) {
pl[[i]] = pl[[i]] + theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank())
}
}
# If the panel is a plot (rather than a nullGrob),
# remove margins and set to common panel size
if(any(class(pl[[i]]) %in% c("ggplot","gtable"))) {
pl[[i]] = pl[[i]] + theme(plot.margin=unit(rep(-1,4), "lines"))
pl[[i]] = set_panel_size(pl[[i]], width=unit(4,"cm"), height=unit(3,"cm"))
}
}
现在布置地块:
do.call(grid.arrange, c(pl, ncol=3))
grid.rect(.5, .5, gp=gpar(lwd=2, fill=NA, col="black"))
正如您在下图中所看到的,即使这些图都具有相同的面板大小,它们之间的边距也不是恒定的,这可能是由于 grid.arrange
处理 null grobs 间距的方式,具体取决于在哪些职位上有实际情节。另外,因为 set_panel_size
设置了绝对大小,所以我必须手动调整最终图的大小,以使面板尽可能靠近在一起,同时仍然避免重叠。我希望 SO 的一位常驻 grid
专家会顺便过来并提出更有效的方法。
(另请注意,使用这种方法,您最终可能会在给定的行或列中没有带标签的图。在下面的示例中,图 "E" 没有 y 轴标签,而图 "D" 丢失,所以你必须在不同的行中查看标签是什么。如果只有图 "B"、"C"、"E" 和 "F" 存在,布局中不会有 任何 标记图。我不知道 OP 想如何处理这种情况(一种选择是添加逻辑以将标签保留在 "interior" 如果给定的行或列不存在 "outer" 图),但我认为值得指出。)
一个可能的解决方案是生成一个 ggplot 列表,并用虚拟占位符替换其中的一些。
d <- data.frame(x=rnorm(90), y=rnorm(90),
f1 = gl(3, 30) , f2 = rep(gl(3, 10), 3) )
p <- ggplot(d, aes(x, y)) +
geom_point()
# if you want to keep the facet labels
# p <- p + facet_grid(f1~f2)
library(plyr)
pl <- dlply(d, .(f1, f2), "%+%", e1=p, .drop = FALSE)
.dummy_plot <- ggplot() + theme_void()
pl[c(3,4,7)] <- rep(list(.dummy_plot), 3, simplify=FALSE)
# devtools::install_github("baptiste/egg)
library(grid)
grid.newpage()
grid.draw(egg::ggarrange(plots=pl))
# alternatively
# library(cowplot)
# plot_grid(plotlist = pl)