如何在Python中使用Matplotlib/Seaborn绘制双变量箱线图
用Matplotlib实现双向箱线图(带可变宽度水平箱线)
要实现你需要的同时包含水平/垂直须、且X轴方向箱线组件大小随数据调整的图形,本质是创建双向箱线图。Seaborn没有直接对应的函数,但可以用Matplotlib手动绘制元素,灵活控制每个组件的位置和尺寸。
以下是完整实现代码,用鸢尾花数据集做示例:
import matplotlib.pyplot as plt import seaborn as sns import numpy as np from matplotlib.patches import Rectangle # 加载示例数据,按分组准备数据 df = sns.load_dataset("iris") groups = df["species"].unique() x_var = "sepal_length" # X轴变量 y_var = "sepal_width" # Y轴变量 # 预计算每个分组的箱线统计量(四分位数、极值)及X方向箱线宽度 group_stats = {} for group in groups: subset = df[df["species"] == group] # Y方向(垂直箱线)统计:[最小值, Q1, 中位数, Q3, 最大值] y_percentiles = np.percentile(subset[y_var], [0, 25, 50, 75, 100]) # X方向(水平箱线)统计 x_percentiles = np.percentile(subset[x_var], [0, 25, 50, 75, 100]) # 用X变量的四分位距(IQR)计算箱线宽度,缩放系数调整视觉效果 x_iqr = x_percentiles[3] - x_percentiles[1] group_stats[group] = { "y_stats": y_percentiles, "x_stats": x_percentiles, "x_box_width": x_iqr * 0.3 # 可根据需求调整缩放比例 } # 创建画布 fig, ax = plt.subplots(figsize=(9, 6)) pos = np.arange(len(groups)) # 每个分组在Y轴上的位置 # 绘制垂直箱线(对应Y变量分布) for idx, group in enumerate(groups): stats = group_stats[group] y_min, y_q1, y_med, y_q3, y_max = stats["y_stats"] # 绘制箱体 ax.add_patch(Rectangle( (pos[idx] - 0.2, y_q1), 0.4, y_q3 - y_q1, fill=False, edgecolor="#2c3e50", linewidth=1.2 )) # 中位数线 ax.plot([pos[idx]-0.2, pos[idx]+0.2], [y_med, y_med], color="#2c3e50", linewidth=1.5) # 须和端点 ax.plot([pos[idx], pos[idx]], [y_min, y_q1], color="#2c3e50") ax.plot([pos[idx], pos[idx]], [y_q3, y_max], color="#2c3e50") ax.plot([pos[idx]-0.1, pos[idx]+0.1], [y_min, y_min], color="#2c3e50") ax.plot([pos[idx]-0.1, pos[idx]+0.1], [y_max, y_max], color="#2c3e50") # 绘制水平箱线(对应X变量分布,宽度随数据调整) for idx, group in enumerate(groups): stats = group_stats[group] x_min, x_q1, x_med, x_q3, x_max = stats["x_stats"] box_height = stats["x_box_width"] # 绘制箱体 ax.add_patch(Rectangle( (x_q1, pos[idx] - box_height), x_q3 - x_q1, box_height * 2, fill=False, edgecolor="#e74c3c", linewidth=1.2 )) # 中位数线 ax.plot([x_med, x_med], [pos[idx]-box_height, pos[idx]+box_height], color="#e74c3c", linewidth=1.5) # 须和端点 ax.plot([x_min, x_q1], [pos[idx], pos[idx]], color="#e74c3c") ax.plot([x_q3, x_max], [pos[idx], pos[idx]], color="#e74c3c") ax.plot([x_min, x_min], [pos[idx]-0.1, pos[idx]+0.1], color="#e74c3c") ax.plot([x_max, x_max], [pos[idx]-0.1, pos[idx]+0.1], color="#e74c3c") # 添加原始数据散点(带微小偏移避免重叠) for idx, group in enumerate(groups): subset = df[df["species"] == group] # X方向散点 ax.scatter( subset[x_var], pos[idx] + np.random.normal(0, 0.05, len(subset)), alpha=0.5, color="#95a5a6", s=20 ) # Y方向散点 ax.scatter( pos[idx] + np.random.normal(0, 0.05, len(subset)), subset[y_var], alpha=0.5, color="#95a5a6", s=20 ) # 调整坐标轴样式 ax.set_xlabel(x_var, fontsize=12) ax.set_ylabel("Species", fontsize=12) ax.set_yticks(pos) ax.set_yticklabels(groups, fontsize=10) ax.set_xlim(df[x_var].min() - 0.5, df[x_var].max() + 0.5) ax.set_ylim(-0.5, len(groups) - 0.5) ax.grid(axis="y", linestyle="-", alpha=0.3) plt.tight_layout() plt.show()
关键实现细节:
- 手动绘制箱体:用
matplotlib.patches.Rectangle分别绘制垂直(Y变量)和水平(X变量)的箱体,避开Seaborn单方向箱线图的限制。 - 可变宽度控制:通过计算每个分组X变量的四分位距(IQR)来设置水平箱线的高度(对应Y轴方向的尺寸),实现“根据数据调整大小”的需求。
- 元素对齐:将每个分组的垂直/水平箱线对齐到同一Y轴位置,保证视觉上的对应关系。
- 散点偏移:给散点添加微小的随机偏移,避免数据点完全重叠,提升可读性。
内容的提问来源于stack exchange,提问作者Liam Wilson
相关产品推荐
相关产品推荐

