多直方图对比:基于区间计数的箱线图可视化实现问询
实现直方图嵌套箱线图的可视化方案
我明白你想要的是在每个直方图区间(bin)的位置上,展示对应五组数据计数的箱线图,形成一种嵌套式的可视化效果。我们可以通过自动分箱统计替代手动计数,再将直方图和箱线图叠加在同一坐标轴上来实现这个需求,下面是完整的解决方案:
步骤1:自动分箱并统计各组计数
首先我们不需要手动统计每个区间的计数,用pd.cut给原始数据分箱,再通过分组聚合自动计算每个Photo在每个bin里的数量,这样更准确且高效:
import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt # 原始数据定义 u30_diameter_01 = pd.Series([6.76, 5.03, 6.57, 5.52, 8.81, 9.50, 4.96, 6.19, 5.61, 4.86, 4.41, 6.04, 6.39, 4.61, 6.90, 6.38, 6.20, 6.75, 4.76, 6.18, 6.13, 7.95, 4.22, 6.16, 8.40, 6.90, 6.77, 6.11, 6.15, 6.43]) u30_diameter_02 = pd.Series([4.98, 5.16, 6.43, 3.93, 5.41, 6.39, 6.35, 5.43, 6.69, 7.10, 7.55, 5.92, 6.50, 5.36, 7.27, 4.10, 6.89, 4.82, 4.34, 5.67, 8.95, 7.53, 4.91, 5.30, 6.62, 6.90, 5.98, 6.05, 4.37, 6.42, 5.03, 7.78, 6.28, 7.81, 5.81, 5.99, 6.22, 6.48, 4.43, 5.18]) u30_diameter_03 = pd.Series([5.80, 5.72, 6.19, 5.69, 4.19, 4.82, 6.32, 6.14, 6.59, 4.90, 6.39, 5.93, 5.17, 6.00, 6.64, 7.48, 5.61, 5.79, 4.79, 5.99, 5.11, 6.08, 6.68, 5.84, 6.88, 6.81, 6.07, 4.55, 6.20, 5.50]) u30_diameter_04 = pd.Series([5.21, 7.85, 6.48, 4.44, 6.91, 6.30, 8.51, 5.99, 7.74, 5.47, 6.19, 8.44, 8.03, 8.37, 6.80, 6.49, 5.65, 6.06, 7.04, 4.55, 6.76, 7.99, 5.64, 5.97, 6.21]) u30_diameter_05 = pd.Series([7.24, 7.42, 6.85, 8.14, 7.03, 6.52, 5.82, 6.92, 7.44, 7.91, 7.18, 7.99, 6.19, 7.44, 7.37, 6.54, 7.11, 7.77, 8.67, 8.35, 7.12, 5.10, 8.29, 6.36, 7.81]) # 合并数据,添加Photo标签 u30_diameter = pd.DataFrame({ 'Value': pd.concat([u30_diameter_01, u30_diameter_02, u30_diameter_03, u30_diameter_04, u30_diameter_05], ignore_index=True), 'Photo': np.repeat(['1','2','3','4','5'], [len(s) for s in [u30_diameter_01, u30_diameter_02, u30_diameter_03, u30_diameter_04, u30_diameter_05]]) }) # 定义分箱区间,和你之前的直方图一致 diameter_range = np.arange(3.75,9.75,0.5) # 给每个数据点分配对应的bin u30_diameter['Bin'] = pd.cut(u30_diameter['Value'], bins=diameter_range, include_lowest=True) # 统计每个Photo在每个Bin里的计数 bin_counts = u30_diameter.groupby(['Bin', 'Photo']).size().reset_index(name='Count') # 填充缺失的组合(比如某个Photo在某个Bin里没有数据,计数为0) bin_counts = bin_counts.pivot(index='Bin', columns='Photo', values='Count').fillna(0).stack().reset_index(name='Count')
步骤2:绘制嵌套式的直方图+箱线图
接下来我们把直方图和箱线图绘制在同一坐标轴上,调整箱线图的位置,让它刚好嵌入每个直方图的区间内:
fig, ax = plt.subplots(figsize=(10,6)) # 第一步:绘制重叠直方图,保持你原来的样式 sns.histplot( data=u30_diameter, x="Value", hue="Photo", element="step", bins=diameter_range, alpha=0.3, # 增加透明度,避免遮挡箱线图 ax=ax ) # 第二步:准备箱线图的x轴位置(每个bin的中间值) bin_midpoints = (diameter_range[:-1] + diameter_range[1:])/2 # 把Bin转换为对应的中间值,方便定位 bin_counts['Bin_Mid'] = bin_counts['Bin'].apply(lambda x: (x.left + x.right)/2) # 绘制箱线图,调整宽度让它嵌入bin内 sns.boxplot( data=bin_counts, x='Bin_Mid', y='Count', width=0.3, # 调整箱线图宽度,适配bin的宽度 color='white', boxprops={'edgecolor':'black'}, whiskerprops={'color':'black'}, capprops={'color':'black'}, medianprops={'color':'red'}, ax=ax ) # 调整坐标轴标签和样式 ax.set_xlabel('Diameter, μm') ax.set_ylabel('Count') # 设置x轴刻度为bin的区间,更清晰 ax.set_xticks(bin_midpoints) ax.set_xticklabels([f"{left:.1f}-{right:.1f}" for left, right in zip(diameter_range[:-1], diameter_range[1:])]) plt.xticks(rotation=45) # 调整图例位置,避免遮挡 plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') plt.tight_layout() plt.savefig('U30_d_nested.png', facecolor='white', edgecolor='none', dpi=300) plt.show()
关键说明
- 自动分箱统计:用
pd.cut和groupby替代手动计数,避免人为错误,同时能自动处理缺失的计数(填充为0) - 箱线图定位:使用每个bin的中间值作为箱线图的x坐标,确保它刚好落在对应的直方图区间内
- 样式调整:设置直方图的透明度,箱线图用白色填充+黑色边框,中位数用红色突出,让两层可视化清晰区分
- x轴刻度:显示每个bin的区间范围,提升可读性
这样绘制出来的图形就能实现你想要的嵌套分布可视化:每个直方图区间上叠加该区间内五组数据计数的箱线图,直观展示每组数据在不同直径区间的分布差异。
内容的提问来源于stack exchange,提问作者Oscar Espinosa
相关产品推荐
相关产品推荐

