如何在matplotlib中使小提琴图(violin plot)面积按样本计数缩放
解决方案
matplotlib 原生的 violinplot 接口没有直接提供按面积归一化的内置参数,你可以通过两种方式实现两个小提琴面积相等的需求:
方案1:手动调整violinplot返回的图形对象
plt.violinplot 会返回包含小提琴填充轮廓的对象,你可以计算两个轮廓的面积后,对水平方向的坐标做缩放调整,使二者面积一致:
import matplotlib.pyplot as plt import matplotlib.transforms as transforms import numpy as np year1 = [0.31686211, 0.39691523, 0.28396513, 0.35692174, 0.28013689, 0.40459902, 0.3020441, 0.34157149] year2 = [0.18479367, 0.30268128, 0.25940495, 0.39964962, 0.32782555, 0.28997294, 0.31437325, 0.36121367] # 绘制两个小提琴,放在同一个x坐标位置 vp1 = plt.violinplot(year1, positions=[1], showextrema=False) vp2 = plt.violinplot(year2, positions=[1], showextrema=False) # 计算多边形面积的工具函数(鞋带公式) def calc_path_area(path): x, y = path.vertices[:, 0], path.vertices[:, 1] return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) area1 = calc_path_area(vp1['bodies'][0].get_path()) area2 = calc_path_area(vp2['bodies'][0].get_path()) # 计算缩放系数,面积和水平缩放比例线性相关 if area1 > area2: scale = area2 / area1 # 沿x轴缩放第一个小提琴,缩放原点为小提琴的中心x坐标1 trans = transforms.Affine2D().scale(scale, 1, origin=(1, 0)) vp1['bodies'][0].set_transform(plt.gca().transData + trans) else: scale = area1 / area2 trans = transforms.Affine2D().scale(scale, 1, origin=(1, 0)) vp2['bodies'][0].set_transform(plt.gca().transData + trans) # 调整样式区分两组 vp1['bodies'][0].set_facecolor('#1f77b4') vp1['bodies'][0].set_alpha(0.6) vp2['bodies'][0].set_facecolor('#ff7f0e') vp2['bodies'][0].set_alpha(0.6) plt.show()
方案2:自行计算KDE后绘制(更稳定可控)
你也可以手动计算两组数据的核密度估计,归一化到面积相等后直接用fill_betweenx绘制,不需要依赖violinplot的内置逻辑:
import matplotlib.pyplot as plt import numpy as np from scipy.stats import gaussian_kde year1 = [0.31686211, 0.39691523, 0.28396513, 0.35692174, 0.28013689, 0.40459902, 0.3020441, 0.34157149] year2 = [0.18479367, 0.30268128, 0.25940495, 0.39964962, 0.32782555, 0.28997294, 0.31437325, 0.36121367] # 生成统一的y轴取值范围 y_range = np.linspace(min(*year1, *year2), max(*year1, *year2), 200) # 计算两组的核密度 kde1 = gaussian_kde(year1) kde2 = gaussian_kde(year2) dens1 = kde1(y_range) dens2 = kde2(y_range) # 归一化密度,使两组积分(面积)相等 area1 = np.trapz(dens1, y_range) area2 = np.trapz(dens2, y_range) dens1 /= area1 dens2 /= area2 # 叠加绘制,左右分布避免完全重叠,也可调整为同一侧加透明度 plt.fill_betweenx(y_range, 1 - dens1, 1, color='#1f77b4', alpha=0.6, label='第一年') plt.fill_betweenx(y_range, 1, 1 + dens2, color='#ff7f0e', alpha=0.6, label='第二年') plt.xlim(0.5, 1.5) plt.legend() plt.show()
注:如果需要两个小提琴完全重叠在同一侧,只需要把两个fill_betweenx的x范围都设置为[1, 1+ dens]即可,调整透明度区分两组
内容的提问来源于stack exchange,提问作者louise
相关产品推荐
相关产品推荐

