绘制R语言中带有两行的图例和填充指南的子图(ggplot legend in two rows with guides fill)

R语言ggplot2创建两行图例填充子图教程
30 篇文章 ¥59.90 ¥99.00
使用R语言ggplot2包,通过调整图例行数和填充指南,创建一个带有两行图例的子图。教程以mtcars数据集为例,展示了如何根据汽车气缸数和品牌进行颜色和填充编码,实现图例在右侧的布局。

绘制R语言中带有两行的图例和填充指南的子图(ggplot legend in two rows with guides fill)

在R语言中使用ggplot2包可以创建美观且灵活的数据可视化图形。其中,图例是帮助读者理解图形中各种元素的关键组成部分之一。有时,我们需要在图例中显示大量的标识符,但默认情况下,ggplot2图例只显示一行。本文将展示如何使用R语言中的ggplot2包创建一个具有两行图例和填充指南的子图。

首先,我们需要准备一些数据来绘制图形。在本示例中,我们将使用mtcars数据集,该数据集包含有关不同汽车型号的信息。我们将根据汽车的气缸数和汽车品牌来绘制图形,并在图例中显示这些信息。

# 导入所需的包
library(ggplot2)

# 加载mtcars数据集
data(mtcars)

# 创建一个新的列用于存储汽车品牌和气缸数的组合
mtcars$brand_cyl <- paste(mtcars$am, mtcars$cyl)

# 创建ggplot对象并绘制图形
p <- ggplot(mtcars, aes(x = mpg, y = disp, color = brand_cyl, fill = brand_cyl)) +
  geom_point(shape = 21, size = 4) +
  theme_classic()

# 显示图形
p

运行上述代码后,我们将得到一个散点图,其中每个点代表一个汽车型号。点的颜色和填充将根据汽车的气缸数和品牌进行编码。

接下来,我们将在现有的图形基础上添加一个包含两行的图例,并将其放置在图形的右侧。为此,我们需要使用

<think> 我们有一个需求:在Python中使用Matplotlib创建两行一列的(即两个垂直排列),具体要求如下: 1. 标题放在1(上方)的上方。 2. 图例放在12(即两个)的中间,并且以一行排列(水平排列)。 根据引用内容,我们知道Matplotlib提供了几种布局调整方法,如约束布局(constrained_layout)、紧密布局(tight_layout)自定义布局。同时,引用中提到了使用subplots函数创建,以及使用tight_layout来调整间距。 然而,为了满足特定的图例位置要求(在两个中间且水平排列),我们可能需要结合使用一些额外的技巧,比如: - 使用fig.legend()来创建整个形的图例,而不是单个图例。 - 调整形区域(包括区域图例区域)的布局。 具体步骤: 1. 创建:使用plt.subplots(nrows=2, ncols=1)创建两行一列的。 2. 设置整个形的标题:由于要求标题在1的上方,我们可以使用fig.suptitle()设置总标题,或者使用ax1.set_title()设置第一个的标题(但这样标题会紧贴第一个)。根据需求,标题要放在1上方,可能更接近整个形的顶部,因此使用fig.suptitle()并调整其位置可能更合适,但注意引用[1]提到多个紧凑时标题可能重叠,所以我们也要调整标题的位置。 3. 绘制两个的内容,并分别设置标签(label)以便图例使用。 4. 创建图例:由于图例要放在两个中间,我们可以创建一个属于整个形的图例(使用fig.legend()),并指定图例的位置(loc)为自定义位置,或者使用预定义的位置字符串,如'center',然后调整其具体位置。 另一种方法是手动调整图例的位置,通过指定图例形中的坐标(使用bbox_to_anchor参数)。 但是,注意引用[3]提到约束布局仅适用于调整中的刻度标签、轴标签、标题图例(指内部的图例)的位置,而不调整其他元素。因此,如果我们使用fig.legend()创建整个形的图例,约束布局可能不会自动调整它。 因此,我们可以考虑以下方法: - 不使用约束布局,而使用tight_layout,然后手动调整图例位置。 - 或者,使用约束布局,然后手动调整图例位置(因为图例在两个中间的需求可能无法自动满足)。 由于要求图例在两个中间,我们可以: a. 在第一个(ax1)中绘制所有需要图例的线条,并收集标签(但这样图例会出现在ax1内部)。 b. 在第二个(ax2)中绘制,同样收集标签。 c. 然后,我们创建一个共同的图例,放在两个之间。 我们可以通过以下步骤实现: 1. 在ax1ax2中绘制形时,设置label。 2. 通过ax1.get_legend_handles_labels()ax2.get_legend_handles_labels()获取两个中的图例句柄标签。 3. 合并句柄标签。 4. 使用fig.legend()创建图例,并指定图例的位置为形中间偏下的位置。 然后,我们需要调整的布局,为图例留出空间。我们可以使用constrained_layout=True,然后调整图例的位置参数,或者使用tight_layout并指定rect参数(调整区域的位置)来为图例留出空间。 但是,引用[2]中使用了tight_layout调整之间的距离,但我们的图例是在形中间的,所以我们需要在之间留出空间给图例。 具体实现计划: 1. 创建,设置constrained_layout=True(或者使用tight_layout,但引用[2]提到tight_layout可以调整间距,而引用[1]提到约束布局也能自动调整,这里我们尝试使用constrained_layout,因为引用[3]提到了约束布局)。 2. 设置整个形的标题(fig.suptitle)放在形顶部(注意:如果放在1上方,可能需要调整suptitle的y坐标,使其位于1的上方,但又不至于太靠上)。 3. 在两个绘制形,并收集所有图例的句柄标签。 4. 使用fig.legend()创建图例,并设置位置参数(loc)为两个中间的位置。我们可以通过指定loc='lower center',然后调整其垂直位置(比如向上移动一些)来达到两个中间的效果。或者使用bbox_to_anchor参数(在形坐标中指定位置)来精确定位。 由于两个垂直排列,整个形可以看作三个部分:顶部标题区域、1区域、2区域、图例区域(位于12之间)底部空白。因此,我们可以: - 将图例放在整个形高度的中间位置(0.5),但实际上因为有两个,所以中间位置应该是1底部2顶部之间的中间位置。 但constrained_layout可能不会自动为整个形的图例留出空间(引用[3]说约束布局仅适用于中的元素),所以我们需要手动调整。 因此,另一种方法是: - 不使用constrained_layout,而使用手动调整位置大小的方法,通过设置GridSpec来实现。 考虑到我们要精确控制图例的位置,我们可以: - 使用GridSpec来创建,并留出图例的空间。 步骤: 1. 创建一个GridSpec布局,将整个形分成三部分:顶部(标题区域),中间上(1),中间下(图例区域),底部(2)。具体行数比例可以调整。 2. 在GridSpec的顶部区域设置标题(使用fig.text或fig.suptitle)。 3. 在中间上的区域创建第一个ax1。 4. 在中间下的区域(图例区域)我们不创建,而是用来放置图例。注意,这个区域的高度应该很小,仅用于放置一行图例。 5. 在底部区域创建第二个ax2。 6. 然后,绘制形,并在ax1ax2中设置label。 7. 合并所有句柄标签,然后在中间下的区域(即我们预留的空间)创建图例。 然而,这种方法需要手动调整高度比例,可能较为复杂。 为了简便,我们可以尝试使用constrained_layout,并配合手动调整图例位置。 考虑以下步骤(使用constrained_layout): 1. 创建形:fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, constrained_layout=True) 2. 设置整个形的标题:fig.suptitle('总标题', y=1.05) # 调整y值使其高于1 3. 在ax1ax2中绘,并设置label。 4. 获取两个的句柄标签,合并。 5. 使用fig.legend(),并指定位置,例如: - loc='upper center', bbox_to_anchor=(0.5, 0.5), ncol=图例行数(一行的话ncol为标签个数) 但是,这里的bbox_to_anchor是相对于整个形(figure)的坐标,其中(0,0)是左下角,(1,1)是右上角。两个中间的位置大约是y=0.5左右。但由于constrained_layout会自动调整区域,实际中间位置可能不是0.5,我们可以尝试0.5,然后观察效果调整。 6. 但是,使用constrained_layout时,我们添加的图例可能会与重叠,因为constrained_layout不会自动考虑fig.legend()。因此,我们可以关闭constrained_layout,改为手动调整布局(使用plt.tight_layout)并设置rect参数来为图例留出空间。 因此,我们决定不使用constrained_layout,而是使用tight_layout,并通过调整区域(设置rect)来为图例留出空间。 具体步骤: 1. 创建两个(垂直排列):fig, axs = plt.subplots(2, 1, figsize=(6,8)) 2. 设置整个形的标题:fig.suptitle('总标题') 3. 绘制ax1ax2的形,设置label。 4. 收集所有句柄标签(从ax1ax2中)。 5. 使用tight_layout调整布局,但通过rect参数指定区域(不包括图例区域)所占的形矩形区域。例如,我们希望区域只占用形的一部分,中间留空给图例。 - rect参数是一个四元组(left, bottom, right, top),表示区域在形中的位置(归一化坐标)。通常,我们可以将整个形分为三个部分:标题(顶部一部分),1,图例(一小部分),2。所以,我们可以设置: rect=[0, 0, 1, 0.9] # 表示区域占用形高度的90%,顶部留10%给标题,底部留10%给图例?但这样图例会在底部,而我们需要在中间。 - 因此,我们需要将形分成四部分(从上到下): 标题:占5% 1:占45% 图例区域:占5% 2:占45% 这样,图例区域在中间,占5%的高度。 - 但是,tight_layout的rect参数只能指定整个区域(包括所有)的矩形,不能分别指定每个部分。所以,我们需要整体调整,然后手动设置的位置。 由于tight_layout无法如此细致地控制,我们可以改为使用GridSpec。 因此,我们采用GridSpec方案: 步骤: 1. 创建GridSpec,设置行数高度比例: - 行数:3行(标题行、1行、图例行、2行?)实际上应该是4行:标题(顶部)、1、图例2。 - 高度比例:我们设置四行的高度比例,例如[0.1, 0.4, 0.1, 0.4](标题10%,1占40%,图例10%,2占40%)。 2. 在GridSpec的相应位置创建。 代码结构: import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec fig = plt.figure(figsize=(6,8)) gs = GridSpec(nrows=4, ncols=1, height_ratios=[0.1, 0.4, 0.1, 0.4]) # 标题区域:我们通常不使用来放置标题,可以直接用fig.text或fig.suptitle,但为了布局,我们也可以用一个来占位(然后隐藏边框)。 # 但标题可以直接放在形顶部,所以我们用第一个区域放置标题(作为文本),第二个区域为ax1,第三个区域放置图例(不是,而是图例),第四个区域为ax2。 # 创建ax1ax2 ax1 = fig.add_subplot(gs[1,0]) # 第二行第一列 ax2 = fig.add_subplot(gs[3,0]) # 第四行第一列 # 然后,在ax1ax2中绘制形。 # 绘制示例:在ax1中画一条线 line1, = ax1.plot([0,1,2], [0,1,0], label='Line 1') # 在ax2中画一条线 line2, = ax2.plot([0,1,2], [0,0,1], label='Line 2') # 设置整个形的标题,放在第一行位置 fig.suptitle('总标题', fontsize=16) # 收集图例的句柄标签 handles = [line1, line2] labels = [h.get_label() for h in handles] # 在第三行(即图例区域)创建图例,注意第三行是一个GridSpec的位置,我们可以在这个位置创建一个没有边框的,然后在这个上创建图例,或者直接用fig.legend并将位置定位到第三行的中间。 # 方法:使用fig.legend,并指定位置为第三行的位置(归一化坐标) # 我们可以获取第三行的边界框(bbox)位置,然后在这个位置放置图例。 # 另一种更简单的方法:在第三行的位置添加一个(隐藏坐标轴),然后在这个上调用legend,但这样需要调整的边距等。 # 或者,直接计算第三行在形中的坐标: # 由于我们有GridSpec,我们可以获取第三行的bbox(在形坐标中) # 但更简单的是,我们使用fig.legend,然后用bbox_to_anchor参数指定中间位置,并设置bbox_transform为fig.transFigure(即使用形坐标)。 # 形坐标:第三行的中心位置,x=0.5(横向居中),y=第三行区域的中心(注意GridSpec从下到上,四行分别索引0,1,2,3,第三行索引为2,高度比例为0.1,位置在从下到上的0.5(2的高度0.4)0.1(图例0.4(1)之上)? # 实际上,高度比例是[0.1, 0.4, 0.1, 0.4],所以从下往上: 底部0-0.4:2(索引3) 0.4-0.5:图例区域的下半部分(索引2)?不对,GridSpec的排列是从上往下还是从下往上? # GridSpec默认是从上往下排列,第一行在最上面,第二行在下面,等等。所以索引0(第一行)在最上面,高度比例为0.1,然后是索引1(第二行)高度0.4,然后是索引2(第三行)高度0.1,最后索引3(第四行)高度0.4在底部。 # 所以,四个区域从顶部到底部的位置: 区域0(标题):从y=0.9到1.0(因为第一个区域占10%) 区域1(ax1):从y=0.5到0.9(占40%) 区域2(图例):从y=0.4到0.5(占10%) 区域3(ax2):从y=0.0到0.4(占40%) # 因此,图例区域(区域2)的中心坐标为(0.5, 0.45)(因为图例区域从0.4到0.5,中心为0.45)。 # 所以,我们可以这样创建图例: fig.legend(handles, labels, loc='center', bbox_to_anchor=(0.5, 0.45), ncol=2) # 一行排列,所以ncol等于标签个数(这里2个) # 最后,我们隐藏第三行(如果我们没有创建,就不需要隐藏)。注意,我们没有在第三行创建,所以不需要隐藏。 # 显示形 plt.show() 但是,这里有一个问题:GridSpec只用于创建ax1ax2,标题图例我们是用fig.suptitlefig.legend添加的,这样布局就是按照GridSpec划分的区域。 注意:我们并没有在区域0(标题区域)创建,所以标题是直接放在形的顶部,而图例放在区域2(即图例区域)的中心。 这样,我们就实现了: - 标题在1上方(实际上在整个形的顶部,而1在标题下方) - 图例在两个中间(区域2) 这种方法可以精确控制位置。 但如果我们不想使用GridSpec(因为它需要更多代码),我们可以尝试更简便的方法: 1. 创建两个(垂直排列),并设置整个形大小。 2. 使用fig.suptitle设置标题。 3. 绘制形。 4. 使用fig.tight_layout()调整,然后手动调整的位置(通过调整subplots_adjust)来为图例留出中间空间。 例如: plt.subplots_adjust(top=0.9, bottom=0.1, hspace=0.5) # 增加两个之间的间距(hspace) 5. 然后,将图例放在两个之间的空白区域:通过bbox_to_anchor=(0.5, 0.5)(注意这个0.5是形坐标,而两个中间的位置可能是0.5,但实际上因为调整了间距,所以具体位置需要计算)。 但是,由于hspace是之间的间距(以高度的比例为单位),而整个区域在形中的位置由subplots_adjust控制,所以图例的垂直位置可以通过以下方式计算: 整个区域(包括两个及其之间的空白)的底部顶部由subplots_adjust的bottomtop指定。 两个之间的空白区域位于整个区域的中间部分。 计算起来比较复杂,不如GridSpec直观。 因此,我们选择GridSpec方法。 下面给出完整代码示例: ```python import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec # 创建GridSpec布局 fig = plt.figure(figsize=(6,8)) # 我们将形分为4行:标题(0.1)、1(0.4)、图例(0.1)、2(0.4) height_ratios = [0.1, 0.4, 0.1, 0.4] gs = GridSpec(nrows=4, ncols=1, height_ratios=height_ratios, figure=fig) # 创建ax1(在第二行,索引1)ax2(在第四行,索引3) ax1 = fig.add_subplot(gs[1,0]) ax2 = fig.add_subplot(gs[3,0]) # 在ax1中绘制示例数据 line1, = ax1.plot([0, 1, 2], [0, 1, 0.5], 'b-', label='Line 1') scatter1 = ax1.scatter([0, 1, 2], [0.5, 0, 1], color='red', label='Scatter 1') # 在ax2中绘制示例数据 line2, = ax2.plot([0, 1, 2], [1, 0, 0.5], 'g--', label='Line 2') scatter2 = ax2.scatter([0, 1, 2], [0.5, 1, 0], color='purple', label='Scatter 2') # 设置整个形的标题(放在第一行区域,即gs[0]的位置,但我们不在该区域创建,所以用fig.suptitle) # 注意:fig.suptitle默认在形顶部,我们可以调整其垂直位置使其位于第一行区域内 # 第一行区域在形坐标中从top=1.0向下到1.0-height_ratios[0]=0.9,所以中心位置在0.95 fig.suptitle('Two Rows Subplot Example', fontsize=16, y=0.95) # y=0.95是为了放在第一行区域内 # 收集所有图例的句柄标签(从两个) handles = [line1, scatter1, line2, scatter2] labels = [h.get_label() for h in handles] # 在图例区域(第三行,索引2)的中心位置创建图例 # 图例区域在形坐标中的y范围:从底部开始,底部是0.0(形底部),总高度为1.0。 # 根据高度比例,图例区域在y轴的位置: # 第四行(2)高度占0.4,所以从0.0到0.4 # 第三行(图例)高度0.1,所以在0.4到0.5 # 第二行(1)高度0.4,所以在0.5到0.9 # 第一行(标题)高度0.1,所以在0.9到1.0 # 因此,图例区域的中心y坐标为:0.4 + 0.1/2 = 0.45 fig.legend(handles, labels, loc='center', bbox_to_anchor=(0.5, 0.45), ncol=4) # 一行排列,所以ncol=4(有4个图例项?这里我们每个有两个,共4个) # 注意:如果图例项太多,一行放不下,可以调整ncol的值,或者调整图例的字体大小。 # 如果需要,我们可以关闭图例区域中的(但我们没有创建,所以不需要) # 显示形 plt.show() ``` 注意:图例区域的高度比例(0.1)可能不足以显示图例,如果图例项太多,可能需要增加该比例(例如0.15)或调整图例的字体大小。 另外,图例的ncol参数应该设置为图例项的数量,这样它们就会排成一行。如果图例项很多,一行排不下,可以分成多行(调整ncol)或减小字体大小。 以上代码实现了需求。 如果用户希望使用更简单的方法(不使用GridSpec),也可以尝试使用plt.subplots配合subplots_adjust调整间距,然后用fig.legend指定位置,但位置需要根据调整后的间距计算,不够精确。 因此,我们推荐使用GridSpec方法。 </think> 根据您的需求,在Python中实现两行一列布局,并将标题置于1上方、图例放在两中间一行排列,可以通过Matplotlib的`GridSpec`结合自定义布局实现。以下是完整解决方案: ```python import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec # 创建GridSpec布局(3行1列) fig = plt.figure(figsize=(8, 10)) gs = GridSpec(nrows=3, ncols=1, height_ratios=[1, 0.1, 1]) # 中间行预留图例空间 # 1(顶部) ax1 = fig.add_subplot(gs[0]) ax1.plot([0, 1, 2], [0, 1, 0], 'b-', label='正弦波') ax1.scatter([0, 1, 2], [0.2, 0.8, 0.3], c='red', label='数据点') ax1.set_title('1数据展示', fontsize=14) # 2(底部) ax2 = fig.add_subplot(gs[2]) ax2.bar(['A', 'B', 'C'], [3, 7, 2], color='green', label='类别统计') ax2.plot([0, 1, 2], [2, 5, 3], 'm--', label='趋势线') # 设置整体形标题(位于1上方) fig.suptitle('双数据对比分析', fontsize=16, y=0.95) # 收集所有图例句柄标签 handles1, labels1 = ax1.get_legend_handles_labels() handles2, labels2 = ax2.get_legend_handles_labels() all_handles = handles1 + handles2 all_labels = labels1 + labels2 # 在中间区域(gs[1])创建图例(一行排列) legend_ax = fig.add_subplot(gs[1]) legend_ax.axis('off') # 隐藏坐标轴 legend_ax.legend(all_handles, all_labels, loc='center', ncol=len(all_labels), # 自动适应项数 frameon=False, fontsize=12) # 优化布局 plt.tight_layout(pad=3.0) # 增加全局边距 plt.subplots_adjust(top=0.9) # 为顶部标题预留空间 plt.show() ``` ### 关键实现原理: 1. **网格布局**:使用`GridSpec(3,1)`创建三行布局: - 第0行:1区域 - 第1行:预留图例放置区(高度设为0.1) - 第2行:2区域 2. **标题定位**: - 通过`fig.suptitle()`设置全局标题 - `y=0.95`参数确保标题位于1上方 - `plt.subplots_adjust(top=0.9)`为标题留出空间[^1] 3. **图例处理**: - 合并两个图例句柄(`get_legend_handles_labels`) - 在中间区域创建隐藏坐标轴的专门放置图例 - `ncol=len(all_labels)`确保所有图例项水平排成一行[^2] 4. **布局优化**: - `tight_layout(pad=3.0)`防止元素重叠 - 高度比例参数`height_ratios`精确控制区域占比[^4] ### 效果说明: 1. 标题位于1正上方(非形顶部) 2. 图例水平居中显示在两个之间 3. 自动适应图例项数量(一行排列) 4. 保留间距防止元素重叠[^1] > 此方法避免了`constrained_layout`无法调整外部元素的限制[^3],通过显式空间分配实现精确布局控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值