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

如何在Plotly go.Box中分组数据并指定10/90分位数

问题:Plotly分组箱线图自定义须线分位数失败

我想用Plotly制作分组箱线图,将默认展示最小值/最大值的须线改为展示第10和第90分位数,但尝试后未成功实现。以下是我的代码及统计数据,目前箱线图未正确应用指定分位数,求协助解决。

import plotly.graph_objects as go
from itertools import cycle
import numpy as np
import pandas as pd

# 生成数据框
num_rows = 100
ids = np.random.randint(1, 1000, size=num_rows)
categories = np.random.choice(['A', 'B', 'C', 'D'], size=num_rows)
phases, durations = [], []
for id in ids:
    phases.extend([1, 2, 3])
    durations.extend(np.random.randint(100, 1001, size=3))
data = {
    'id': np.repeat(ids, 3),
    'category': np.repeat(categories, 3),
    'phase': phases,
    'duration': durations}
df = pd.DataFrame(data)
df = df.sample(frac=1).reset_index(drop=True)

# 计算统计量
p10 = lambda x: x.quantile(0.10)
p25 = lambda x: x.quantile(0.25)
p50 = lambda x: x.quantile(0.50)
p75 = lambda x: x.quantile(0.75)
p90 = lambda x: x.quantile(0.90)

to_display = df.groupby(['phase', 'category'], as_index=False).agg(
    p_10=('duration', p10),
    p_25=('duration', p25),
    p_75=('duration', p75),
    median=('duration', p50),
    p_90=('duration', p90),
    avg=('duration', 'mean')
)

# 创建图表
palette = cycle(['black', 'grey', 'red', 'blue'])
fig_grouped = go.Figure()
for i, cat in enumerate(df['category'].unique()):
    df_plot = df[df['category']==cat]
    fig_grouped.add_trace(go.Box(y=df_plot['duration'],
                          x=df_plot['phase'],
                          name=cat, boxpoints=False,
                          marker_color=next(palette)))
    fig_grouped.update_traces(boxmean=True)
    fig_grouped.update_layout(boxmode='group')
    
    temp = to_display[to_display['category']==cat]
    q1 = list(temp['p_25'].values)
    median = list(temp['median'].values)
    q3 = list(temp['p_75'].values)
    
    lowerfence = list(temp['p_10'].values)
    upperfence = list(temp['p_90'].values)
    
    avg = list(temp['avg'].values)
    
    print(cat)
    print('q1', q1)
    print('median', median)
    print('q3', q3)
    print('lowerfence', lowerfence)
    print('upperfence', upperfence)
    print('avg', avg)
    
    fig.update_traces(q1=q1, 
                      median=median, 
                      q3=q3, 
                      lowerfence=lowerfence,
                      upperfence=upperfence
                      )
fig_grouped.show()

预计算统计数据(to_display数据框)

phase category   p_10    p_25    p_75   p_90         avg
0       1        A  231.8  376.25  803.75  920.6  591.964286
1       1        B  124.0  200.50  669.50  886.4  468.913043
2       1        C  203.2  318.50  784.50  901.6  535.739130
3       1        D  175.0  294.75  821.00  882.5  528.961538
4       2        A  326.5  448.25  764.25  842.8  586.928571
5       2        B  169.0  321.50  825.50  933.6  599.304348
6       2        C  138.8  352.50  808.50  936.2  556.260870
7       2        D  166.5  376.50  783.50  872.0  590.961538
8       3        A  260.2  419.00  707.00  828.2  564.928571
9       3        B  190.0  528.00  836.50  962.0  614.043478
10      3        C  343.4  450.00  812.00  894.6  630.652174
11      3        D  204.5  364.00  833.25  929.0  604.384615

修复后的代码
import plotly.graph_objects as go
from itertools import cycle
import numpy as np
import pandas as pd

# 生成数据框
num_rows = 100
ids = np.random.randint(1, 1000, size=num_rows)
categories = np.random.choice(['A', 'B', 'C', 'D'], size=num_rows)
phases, durations = [], []
for id in ids:
    phases.extend([1, 2, 3])
    durations.extend(np.random.randint(100, 1001, size=3))
data = {
    'id': np.repeat(ids, 3),
    'category': np.repeat(categories, 3),
    'phase': phases,
    'duration': durations}
df = pd.DataFrame(data)
df = df.sample(frac=1).reset_index(drop=True)

# 计算统计量
p10 = lambda x: x.quantile(0.10)
p25 = lambda x: x.quantile(0.25)
p50 = lambda x: x.quantile(0.50)
p75 = lambda x: x.quantile(0.75)
p90 = lambda x: x.quantile(0.90)

to_display = df.groupby(['phase', 'category'], as_index=False).agg(
    p_10=('duration', p10),
    p_25=('duration', p25),
    p_75=('duration', p75),
    median=('duration', p50),
    p_90=('duration', p90),
    avg=('duration', 'mean')
)

# 创建图表
palette = cycle(['black', 'grey', 'red', 'blue'])
fig_grouped = go.Figure()

# 按分类添加自定义统计量的箱线图
for cat in df['category'].unique():
    temp = to_display[to_display['category'] == cat].sort_values('phase')
    fig_grouped.add_trace(go.Box(
        x=temp['phase'],
        name=cat,
        boxpoints=False,
        marker_color=next(palette),
        # 直接传入预计算的统计值
        q1=temp['p_25'].tolist(),
        median=temp['median'].tolist(),
        q3=temp['p_75'].tolist(),
        lowerfence=temp['p_10'].tolist(),
        upperfence=temp['p_90'].tolist(),
        mean=temp['avg'].tolist(),
        boxmean=True
    ))

fig_grouped.update_layout(
    boxmode='group',
    title='分组箱线图(须线为10/90分位数)',
    xaxis_title='阶段',
    yaxis_title='时长'
)

fig_grouped.show()

修复说明

  1. 修正变量名错误:原代码误用fig.update_traces,实际图表变量为fig_grouped,修复后直接在添加trace时传入统计量,避免批量修改出错
  2. 精准绑定统计值:不再传入原始数据,直接使用预计算的分位数、中位数和均值,确保箱线图完全遵循自定义规则
  3. 保证顺序一致性:对每个分类的统计数据按phase排序,确保x轴阶段顺序正确
  4. 简化逻辑:将boxmean=True直接嵌入trace参数,减少冗余操作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 17:42:04