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

箱线图须状线未显示至100上限的原因排查

百分比数据箱线图须状线未延伸至100的原因及解决方法

问题描述

我想要为DataFrame中含百分比数据的列绘制箱线图,设置下限为0、上限为100以直观识别异常值,但调用自定义函数后,须状线并未延伸到设定的100。

生成带异常值的数据集代码

import random
from random import randint

import matplotlib.pyplot as plt
import pandas as pd

random.seed(42)
lst=[]
for x in range(140):
    x=randint(1,100)
    lst.append(x)
lst.append(-1)
lst.append(300)
lst.append(140)
print(lst)

df = pd.DataFrame({0:lst})

自定义箱线图函数

def boxplot(df,var,lower_limit=None,upper_limit=None):
    
    q1=df[var].quantile(0.25)
    q3=df[var].quantile(0.75)
    iqr=q3-q1
    w1=w2=1.5
    
    if (q1!=q3) and (lower_limit!=None):
        w1=(q1-lower_limit)/iqr
    
    if (q1!=q3) and (upper_limit!=None):
        w2=(upper_limit-q3)/iqr
    
    plt.figure(figsize=(5,5))
    df.boxplot(column=var,whis=(w1,w2))
    plt.show()
    
    print(f'The minimum of {var} is',df[var].min(),'and its maximum is ',df[var].max(),"\n")
    print(f'The first quantile of {var} is ',q1,'its median is ',df[var].median(),'and its third quantile is ',q3,"\n")

调用boxplot(df,0,lower_limit=0,upper_limit=100)后,箱线图上须未到达100。


原因分析

核心问题出在pandas.boxplot的whis参数逻辑上:

  • 你计算的w2是为了让理论阈值等于100(q3 + w2*iqr = 100),但whis只是用来确定须状线的阈值范围,须状线最终只会绘制到数据集中落在该范围内的实际极端值,而不是直接跳到你设定的上限。
  • 你的数据里,140个随机数是1-100的整数,假设其中没有刚好等于100的点(或者该范围内的最大值小于100),那须状线就会停在这个实际最大值,而不是100。

举个例子:如果数据中≤100的最大值是98,哪怕你把阈值设为100,上须也只会画到98,因为没有数据点在98到100之间。


解决方法

方法1:强制须状线延伸到指定边界

如果必须让须状线画到0和100,可以改用手动绘制箱线图的方式,绕过pandas默认的须状线逻辑:

def boxplot(df,var,lower_limit=None,upper_limit=None):
    
    q1=df[var].quantile(0.25)
    q3=df[var].quantile(0.75)
    median=df[var].median()
    iqr=q3-q1
    
    plt.figure(figsize=(5,5))
    # 先绘制无胡须的箱线图,保留异常值
    bp = df.boxplot(column=var, whis=[0,0], showfliers=True, return_type='dict')
    
    # 调整箱型的位置和高度
    box = bp['boxes'][0]
    box.set_xdata([1,1])
    box.set_ydata([q1, q3])
    
    # 调整中位数线
    med = bp['medians'][0]
    med.set_ydata([median, median])
    
    # 手动绘制上下须到指定边界
    if lower_limit is not None:
        # 下须线
        plt.plot([1,1], [lower_limit, q1], color='black', linestyle='-')
        # 下须端点横线
        plt.plot([0.95,1.05], [lower_limit, lower_limit], color='black', linestyle='-')
    if upper_limit is not None:
        # 上须线
        plt.plot([1,1], [q3, upper_limit], color='black', linestyle='-')
        # 上须端点横线
        plt.plot([0.95,1.05], [upper_limit, upper_limit], color='black', linestyle='-')
    
    # 设置y轴范围,确保0和100显示完整
    plt.ylim(lower_limit-5 if lower_limit else None, upper_limit+5 if upper_limit else None)
    plt.show()
    
    print(f'The minimum of {var} is',df[var].min(),'and its maximum is ',df[var].max(),"\n")
    print(f'The first quantile of {var} is ',q1,'its median is ',median,'and its third quantile is ',q3,"\n")

方法2:添加参考线标记边界(更简单)

如果不需要强制须状线到边界,只是想清晰展示0-100的范围,可以在原有箱线图上添加红色参考线,并调整y轴范围:

def boxplot(df,var,lower_limit=None,upper_limit=None):
    
    q1=df[var].quantile(0.25)
    q3=df[var].quantile(0.75)
    iqr=q3-q1
    w1=w2=1.5
    
    if (q1!=q3) and (lower_limit!=None):
        w1=(q1-lower_limit)/iqr
    
    if (q1!=q3) and (upper_limit!=None):
        w2=(upper_limit-q3)/iqr
    
    plt.figure(figsize=(5,5))
    df.boxplot(column=var,whis=(w1,w2))
    
    # 添加0和100的红色虚线参考线
    if lower_limit is not None:
        plt.axhline(y=lower_limit, color='r', linestyle='--', label='Lower Limit')
    if upper_limit is not None:
        plt.axhline(y=upper_limit, color='r', linestyle='--', label='Upper Limit')
    
    # 设置y轴范围包含0和100
    plt.ylim(lower_limit-5 if lower_limit else None, upper_limit+5 if upper_limit else None)
    plt.legend()
    plt.show()
    
    print(f'The minimum of {var} is',df[var].min(),'and its maximum is ',df[var].max(),"\n")
    print(f'The first quantile of {var} is ',q1,'its median is ',df[var].median(),'and its third quantile is ',q3,"\n")

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:48:07