如何将Matplotlib箱线图+直方图子图从2列改为4列?
解决Matplotlib/Seaborn 4列子图布局空白问题
问题根源
你遇到的右侧子图空白,核心是两个问题:
- 子图创建时的行列数计算错误,导致实际生成的子图结构和预期不匹配
- 循环中特征与子图索引的对应逻辑出错,只往左侧子图写入了数据,右侧的没被用到
修正后的完整代码示例
假设你用pandas处理数据,特征列表为features,每个特征对应一组箱线图+直方图,以下是4列布局的正确实现:
import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import numpy as np # 示例数据(替换成你的实际数据) df = pd.DataFrame({ 'col1': np.random.normal(0, 1, 100), 'col2': np.random.normal(5, 2, 100), 'col3': np.random.uniform(0, 10, 100), 'col4': np.random.exponential(1, 100), 'col5': np.random.normal(-2, 3, 100), 'col6': np.random.uniform(-5, 5, 100) }) features = list(df.columns) # 设置4列布局 ncols = 4 n_features = len(features) # 计算所需行数:每个特征占2个子图,总子图数是n_features*2,行数=总子图数//列数,有余数则加1 total_subplots = n_features * 2 nrows = (total_subplots + ncols - 1) // ncols # 创建子图网格,转成一维数组方便索引 fig, axs = plt.subplots(nrows, ncols, figsize=(18, 5*nrows)) axs = axs.flatten() # 循环绘制:每行放2组(箱线+直方),共4个子图 for row_idx in range(nrows): # 当前行处理的特征:每行2个特征 feature_start = row_idx * 2 current_features = features[feature_start : feature_start + 2] # 第一个特征的子图位置 if len(current_features) >= 1: box_ax = axs[row_idx * ncols] hist_ax = axs[row_idx * ncols + 1] sns.boxplot(data=df, x=current_features[0], ax=box_ax) box_ax.set_title(f'箱线图:{current_features[0]}') hist_ax.hist(df[current_features[0]].dropna(), bins=20) hist_ax.set_title(f'直方图:{current_features[0]}') # 第二个特征的子图位置 if len(current_features) >= 2: box_ax2 = axs[row_idx * ncols + 2] hist_ax2 = axs[row_idx * ncols + 3] sns.boxplot(data=df, x=current_features[1], ax=box_ax2) box_ax2.set_title(f'箱线图:{current_features[1]}') hist_ax2.hist(df[current_features[1]].dropna(), bins=20) hist_ax2.set_title(f'直方图:{current_features[1]}') plt.tight_layout() plt.show()
关键注意事项
- 子图数组扁平化:一定要用
axs.flatten()把二维的子图数组转成一维,不然直接用axs[row_idx * ncols]会索引错误(二维数组需要用axs[row, col]格式) - 行列数计算准确:总子图数是特征数×2,行数要根据总子图数和4列来计算,避免生成多余或不足的子图
- 指定绘图的ax对象:所有绘图函数(
sns.boxplot、plt.hist)必须通过ax参数指定子图,否则会默认画到最后一个子图里
内容的提问来源于stack exchange,提问作者Happyg
相关产品推荐
相关产品推荐

