如何基于log10尺度计算并在线性尺度展示Seaborn箱线图?
问题描述
现有如下结构的DataFrame,记录了不同区域每平方千米的物品数量:
| n_items | region |
|---|---|
| 28741 | region_1 |
| 192 | region_1 |
| 3856 | region_2 |
| 12345 | region_2 |
| 0 | region_3 |
| 297 | region_3 |
需求是绘制箱线图展示各区域数据的分布与统计特征,具体要求:
- 对
n_items列做log10变换; - 基于
log10(n_items)计算中位数、均值、四分位数等统计量生成Seaborn箱线图; - 最终箱线图以原始尺度(即
10^(log10(n_items)))展示。
目前使用常规箱线图代码:
sns.boxplot(data=data, x='n_items', y='region', hue='region', ax=ax, palette=palette, legend=False, whis=5, fliersize=0.8, showmeans=True, meanprops={'marker': 'o', 'markerfacecolor': 'white', 'markeredgecolor': 'black', 'markersize': '8'} )
曾尝试用np.log10(data)作为数据参数后手动设置ax.set_xscale("linear"),但未成功,求可行实现方法。
解决方案
核心思路是先基于log10变换后的数据计算统计量,再将结果转换回原始尺度展示。以下提供两种可靠实现方式:
方法一:手动计算统计量绘制
- 处理原始数据中的0值(log10(0)无意义),添加极小偏移避免报错;
- 按区域分组计算log尺度下的统计量,再转换回原始尺度;
- 用Matplotlib手动绘制箱线图并添加均值标记、离群点。
示例代码:
import seaborn as sns import matplotlib.pyplot as plt import numpy as np import pandas as pd # 构建原始数据 data = pd.DataFrame({ 'n_items': [28741, 192, 3856, 12345, 0, 297], 'region': ['region_1', 'region_1', 'region_2', 'region_2', 'region_3', 'region_3'] }) # 处理0值,添加极小偏移 data['log_n_items'] = np.log10(data['n_items'] + 1e-6) # 按区域计算log尺度统计量,再转换回原始尺度 stats = data.groupby('region')['log_n_items'].agg([ 'median', 'mean', lambda x: np.percentile(x, 25), lambda x: np.percentile(x, 75), lambda x: np.percentile(x, 100 - (5/2)), lambda x: np.percentile(x, 5/2) ]).rename(columns={ '<lambda_0>': 'q25', '<lambda_1>': 'q75', '<lambda_2>': 'whis_high', '<lambda_3>': 'whis_low' }) stats = 10 ** stats # 提取离群点(基于log尺度范围转换回原始尺度) outliers = [] for region in stats.index: region_data = data[data['region'] == region] log_low = np.log10(stats.loc[region, 'whis_low']) log_high = np.log10(stats.loc[region, 'whis_high']) region_outliers = region_data[(region_data['log_n_items'] < log_low) | (region_data['log_n_items'] > log_high)]['n_items'] outliers.append(region_outliers.values) # 绘制箱线图 fig, ax = plt.subplots() box = ax.boxplot( [stats.loc[r, ['whis_low', 'q25', 'median', 'q75', 'whis_high']].values for r in stats.index], vert=False, patch_artist=True, whis=[0,100] ) # 设置箱线颜色 palette = sns.color_palette() for patch, color in zip(box['boxes'], palette): patch.set_facecolor(color) # 添加均值标记 for i, region in enumerate(stats.index): ax.scatter(stats.loc[region, 'mean'], i+1, marker='o', facecolor='white', edgecolor='black', s=64) # 添加离群点 for i, outlier_group in enumerate(outliers): ax.scatter(outlier_group, [i+1]*len(outlier_group), s=0.8) # 设置坐标轴 ax.set_yticklabels(stats.index) ax.set_xlabel('n_items (原始尺度)') ax.set_ylabel('region') plt.show()
方法二:Seaborn绘制后修改坐标轴刻度
这种方式更简洁:先基于log尺度绘制箱线图,再将x轴刻度从log值转换为原始尺度数值,视觉上直接呈现目标效果。
示例代码:
import seaborn as sns import matplotlib.pyplot as plt import numpy as np import pandas as pd # 构建原始数据 data = pd.DataFrame({ 'n_items': [28741, 192, 3856, 12345, 0, 297], 'region': ['region_1', 'region_1', 'region_2', 'region_2', 'region_3', 'region_3'] }) # 处理0值,添加极小偏移 data['log_n_items'] = np.log10(data['n_items'] + 1e-6) fig, ax = plt.subplots() # 基于log尺度绘制箱线图 sns.boxplot(data=data, x='log_n_items', y='region', hue='region', ax=ax, palette=sns.color_palette(), legend=False, whis=5, fliersize=0.8, showmeans=True, meanprops={'marker': 'o', 'markerfacecolor': 'white', 'markeredgecolor': 'black', 'markersize': '8'} ) # 将log刻度转换为原始尺度标签 log_ticks = ax.get_xticks() original_ticks = 10 ** log_ticks ax.set_xticks(log_ticks) ax.set_xticklabels([f'{int(tick):,}' if tick.is_integer() else f'{tick:.1f}' for tick in original_ticks]) # 设置x轴标签 ax.set_xlabel('n_items (原始尺度)') plt.show()
注意事项
- 必须处理原始数据中的0值,通常添加极小值(如1e-6)避免log10计算报错;
- 方法二更适合快速实现,方法一适合需要自定义统计量或展示细节的场景。
内容的提问来源于stack exchange,提问作者shrympe
相关产品推荐
相关产品推荐

