箱线图须状线未显示至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
相关产品推荐
相关产品推荐

