You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python(Pandas/Matplotlib)复现带SEM误差棒的Excel分组柱状图?

解决方案:带SEM误差棒的分组柱状图实现

核心思路

先按Plant、Concentration、Stress分组计算均值和标准误(SEM),再用Matplotlib手动控制柱状图布局,解决Pandas默认绘图分组混乱、样式不佳的问题。

步骤1:导入依赖库

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

步骤2:数据预处理(可替换为你的真实数据)

# 模拟符合需求的数据集(替换成你的真实数据即可)
np.random.seed(42)  # 固定随机种子保证结果可复现
plants = ['Plant A', 'Plant B', 'Plant C']
concentrations = [0, 10, 50, 100]
stresses = ['No Stress', 'Stress']

data = pd.DataFrame({
    'Plant': np.random.choice(plants, 240),
    'Concentration': np.random.choice(concentrations, 240),
    'Stress': np.random.choice(stresses, 240),
    'Measurement': np.random.normal(loc=50, scale=10, size=240) + 
                   np.where(np.random.choice(stresses, 240) == 'Stress', -10, 0) +
                   np.where(np.random.choice(concentrations, 240) > 0, 5*np.random.choice(concentrations, 240)/100, 0)
})

步骤3:计算均值与SEM

SEM公式:SEM = 标准差 / sqrt(样本量)

# 分组计算关键统计量
grouped_stats = data.groupby(['Plant', 'Concentration', 'Stress']).agg(
    mean=('Measurement', 'mean'),
    std=('Measurement', 'std'),
    n=('Measurement', 'count')
).reset_index()

# 计算标准误
grouped_stats['sem'] = grouped_stats['std'] / np.sqrt(grouped_stats['n'])

步骤4:绘制美观的分组柱状图

提供两种布局方案,按需选择:

方案1:按植物分面展示(清晰易读)

每个植物单独子图,避免元素拥挤:

plt.figure(figsize=(12, 8))
bar_width = 0.35
unique_plants = grouped_stats['Plant'].unique()
unique_concs = grouped_stats['Concentration'].unique()
n_concs = len(unique_concs)

for idx, plant in enumerate(unique_plants):
    ax = plt.subplot(1, len(unique_plants), idx+1)
    plant_data = grouped_stats[grouped_stats['Plant'] == plant]
    
    # 拆分胁迫状态数据
    no_stress = plant_data[plant_data['Stress'] == 'No Stress']
    stress = plant_data[plant_data['Stress'] == 'Stress']
    
    # 设置x轴基准位置
    x = np.arange(n_concs)
    
    # 绘制无胁迫柱子+误差棒
    ax.bar(x - bar_width/2, no_stress['mean'], width=bar_width, label='No Stress', color='#1f77b4')
    ax.errorbar(x - bar_width/2, no_stress['mean'], yerr=no_stress['sem'], fmt='none', c='black', capsize=5)
    
    # 绘制有胁迫柱子+误差棒
    ax.bar(x + bar_width/2, stress['mean'], width=bar_width, label='Stress', color='#ff7f0e')
    ax.errorbar(x + bar_width/2, stress['mean'], yerr=stress['sem'], fmt='none', c='black', capsize=5)
    
    # 美化子图
    ax.set_xticks(x)
    ax.set_xticklabels(unique_concs)
    ax.set_xlabel('Compound Concentration')
    ax.set_ylabel('Measurement Value')
    ax.set_title(f'{plant}')
    ax.legend()

plt.tight_layout()
plt.show()

方案2:所有植物同图展示(对比直观)

用颜色区分植物,同一浓度下展示两种胁迫状态:

plt.figure(figsize=(14, 8))
bar_width = 0.2
n_plants = len(unique_plants)
x = np.arange(n_concs)  # 每个浓度的基准位置

for idx, plant in enumerate(unique_plants):
    plant_data = grouped_stats[grouped_stats['Plant'] == plant]
    no_stress = plant_data[plant_data['Stress'] == 'No Stress']
    stress = plant_data[plant_data['Stress'] == 'Stress']
    
    # 计算柱子偏移量,避免重叠
    offset = idx * bar_width * 2 - (n_plants-1)*bar_width
    
    # 绘制无胁迫柱子+误差棒
    plt.bar(x + offset, no_stress['mean'], width=bar_width, label=f'{plant} - No Stress')
    plt.errorbar(x + offset, no_stress['mean'], yerr=no_stress['sem'], fmt='none', c='black', capsize=3)
    
    # 绘制有胁迫柱子+误差棒
    plt.bar(x + offset + bar_width, stress['mean'], width=bar_width, label=f'{plant} - Stress')
    plt.errorbar(x + offset + bar_width, stress['mean'], yerr=stress['sem'], fmt='none', c='black', capsize=3)

# 美化全局样式
plt.xticks(x + bar_width, unique_concs)
plt.xlabel('Compound Concentration')
plt.ylabel('Measurement Value')
plt.title('Plant Responses by Concentration & Stress Treatment')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')  # 图例放图外避免遮挡
plt.tight_layout()
plt.show()

旧方法效果差的原因

data.groupby(...).unstack().plot.bar()是Pandas快捷绘图方式,但会自动把所有分组层级直接映射到x轴,导致标签拥挤、分组逻辑混乱,且误差棒样式和位置难以自定义。手动用Matplotlib绘制可完全控制布局、颜色和误差棒样式,更符合科研展示的美观要求。

内容的提问来源于stack exchange,提问作者rbiem8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:16:23