箱形图常用于表示一组给定数据的分布特征,或者用于发现异常值。异常值是由于某种原因造成的数据中出现的统计上过大或过小的值,将它们纳入数据分析会影响分析结果。箱形图有简单箱形图、多色简单箱形图、渐变多色简单箱形图、复合箱形图、散点箱形图等多种类型,本节将逐一实现。[大谦Excel,dqexcel点com]
箱形图简介
常见箱形图如图5-4所示,由中间的矩形箱体、箱体中的横线、箱体上下的触须和表示异常值的点标记组合而成。矩形箱体的上边缘对应给定数据的75%分位数,下边缘对应给定数据的25%分位数,中间横线表示中值,即50%分位数。图中上触须和下触须的位置分别表示75%分位数+1.5×IQR和25%分位数-1.5×IQR,其中IQR表示75%分位数和25%分位数的差,也称为内四分极差。触须以外的数据点被判断为异常值,用点标记表示。图5-4中发现了2组数据中共3个异常值。
注意,上触须和下触须的位置不能高于最大值或低于最小值,所以,绘箱形图时,如果75%分位数+1.5×IQR的计算结果大于最大值,则上触须的位置取为最大值;如果25%分位数-1.5×IQR的计算结果小于最小值,则下触须的位置取为最小值。
图5-4 用箱形图查找异常值
根据箱形图可以探查数据的分布特征。如果箱体长度比较大,说明数据比较分散;长度小则说明数据紧凑。表示中值的横线位于箱体中间位置时,说明数据呈正态分布,反之则呈偏态分布。
简单箱形图
简单箱形图用一组箱形表示一组数据的分布特征,如图5-5所示。从图中可以查看单组数据的分布状态,以及各组数据之间的整体波动状态。
图5-6 用自定义函数绘制简单箱形图
使用Python xlwings编程,无法用Shapes对象的AddChart2函数直接创建箱形图。考虑自己绘制箱形图。完整代码见:Samples->ch08 统计图表->06 简单箱形图-自定义->py.py。
root=os.getcwd() #获取当前工作路径
app=xw.App(visible=True,add_book=False) #创建Excel应用
wb=app.books.open(root+r'/data.xlsx',read_only=False) #打开数据文件返回工作簿对象
sht=wb.sheets('Sheet1') #获取指定工作表对象
shp=sht.api.Shapes.AddChart2() #创建空白图表
shp.Left=20
cht=shp.Chart #获取图表
cht.ChartType=xw.constants.ChartType.xlXYScatter #图表类型为散点图
ax1=cht.Axes(1) #获取横轴
ax2=cht.Axes(2) #获取纵轴
ax1.MinimumScale=0 #横轴最小值
ax1.MaximumScale=7
ax2.MinimumScale=0 #纵轴最小值.05
ax2.MaximumScale=0.35
ax1.CrossesAt=ax1.MinimumScale #坐标轴相交位置
ax2.CrossesAt=ax2.MinimumScale
set_style(cht) #设置样式
cht.SeriesCollection().NewSeries() #新建序列
data=sht.range('B2:C101').value #获取数据
count1=0
count2=0
count3=0
count4=0
count5=0
count6=0
d1=[]
d2=[]
d3=[]
d4=[]
d5=[]
d6=[]
#根据第2列的值对第1列数据进行筛选
for i in range(100):
if data[i][1]==1:
count1+=1
d1.append(data[i][0])
elif data[i][1]== 2:
count2+=1
d2.append(data[i][0])
elif data[i][1]== 3:
count3+=1
d3.append(data[i][0])
elif data[i][1]== 4:
count4+=1
d4.append(data[i][0])
elif data[i][1]== 5:
count5+=1
d5.append(data[i][0])
elif data[i][1]== 6:
count6+=1
d6.append(data[i][0])
#根据分组数据创建箱形图
draw_boxplot(app,cht,d1,count1,1,0,0,255,0.5,False)
draw_boxplot(app,cht,d2,count2,2,0,0,255,0.5,False)
draw_boxplot(app,cht,d3,count3,3,0,0,255,0.5,False)
draw_boxplot(app,cht,d4,count4,4,0,0,255,0.5,False)
draw_boxplot(app,cht,d5,count5,5,0,0,255,0.5,False)
draw_boxplot(app,cht,d6,count6,6,0,0,255,0.5,False)
draw_boxplot函数根据给定数据绘制箱形图。下面是该函数的完整代码。需要给该函数指定应用对象、所属Chart对象、绘图数据、数据个数、横坐标位置、颜色分量、宽度和是否渐变色填充等。
def draw_boxplot(app,cht,data,n,x,r,g,b,w,grad):
p25=app.api.WorksheetFunction.Percentile(data,0.25) #25%分位数
p50=app.api.WorksheetFunction.Percentile(data,0.5) #50%分位数
p75=app.api.WorksheetFunction.Percentile(data,0.75) #75%分位数
iqr=p75-p25 #内四分极值
pu=p75+1.5*iqr #触须上界
pl=p25-1.5*iqr #触须下界
minv=app.api.WorksheetFunction.Min(data) #最小值
maxv=app.api.WorksheetFunction.Max(data) #最大值
if minv>pl: pl=minv #如果下界比最小值小,则下界取最小值
if maxv<pu: pu=maxv #如果上界比最大值大,则上界取最大值
#画箱体
bx=shape_x(cht,x-w/2)
by=shape_y(cht,p75)
ex=cht.PlotArea.InsideWidth/(cht.Axes(1).MaximumScale-cht.Axes(1).MinimumScale)*w
ey=cht.PlotArea.InsideHeight/(cht.Axes(2).MaximumScale-cht.Axes(2).MinimumScale)*iqr
shp=cht.Shapes.AddShape(1,bx,by,ex,ey)
if grad:
shp.Fill.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Fill.OneColorGradient(1, 1, 1)
shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Line.Weight=1.5
else:
shp.Fill.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Fill.Transparency=0.5 #半透明
shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Line.Weight=1.5
#绘中值线
bx=shape_x(cht,x-w/2)
ex=shape_x(cht,x+w/2)
by=shape_y(cht,p50)
ey=shape_y(cht,p50)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
#绘触须
bx=shape_x(cht,x)
ex=shape_x(cht,x)
by=shape_y(cht,p75)
ey=shape_y(cht,pu)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
bx=shape_x(cht,x)
ex=shape_x(cht,x)
by=shape_y(cht,p25)
ey=shape_y(cht,pl)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
bx=shape_x(cht,x-w/4)
ex=shape_x(cht,x+w/4)
by=shape_y(cht,pu)
ey=shape_y(cht,pu)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
bx=shape_x(cht,x-w/4)
ex=shape_x(cht,x+w/4)
by=shape_y(cht,pl)
ey=shape_y(cht,pl)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
#绘异常点
for i in range(n):
xx=shape_x(cht,x-w/20)
yy=shape_y(cht,data[i])
ww=cht.PlotArea.InsideWidth/(cht.Axes(1).MaximumScale-\
cht.Axes(1).MinimumScale)*w/10
hh=ww
if data[i]>pu or data[i]<pl:
shp2=cht.Shapes.AddShape(92,xx,yy,ww,hh)
运行代码生成类似图5-6的简单箱形图。
多色简单箱形图
多色简单箱形图中每个箱形的颜色都不一样,如图5-7所示。这种箱形图在各种学术期刊中也很常见。
图5-7 多色简单箱形图
5.2.2小节最后介绍了自定义箱形图的方法,通过自定义,可以对箱形图有更多的控制。如下面代码所示,调用draw_boxplot函数绘制自定义箱形图时,为每个箱形指定不同的颜色。完整代码见:Samples->ch08 统计图表->07 多色简单箱形图->py.py。
#…
draw_boxplot(app,cht,d1,count1,1,102,188,152,0.5,False)
draw_boxplot(app,cht,d2,count2,2,170,208,157,0.5,False)
draw_boxplot(app,cht,d3,count3,3,227,234,150,0.5,False)
draw_boxplot(app,cht,d4,count4,4,252,220,137,0.5,False)
draw_boxplot(app,cht,d5,count5,5,224,106,68,0.5,False)
draw_boxplot(app,cht,d6,count6,6,138,35,63,0.5,False)
#…
运行代码,生成类似图5-7的多色简单箱形图。
水平方向多色简单箱形图
常需要绘制水平方向的箱形图,如图5-8所示。
图5-8 水平多色简单箱形图
编写draw_boxplot_h函数绘制水平多色简单箱形图。完整代码见:Samples->ch08 统计图表->08 多色简单箱形图-水平->py.py。
def draw_boxplot_h(app,cht,data,n,x,r,g,b,w,grad):
'''
绘箱形图
data为一维数组,r,g,b为颜色,[255 0 0]
x为中心横坐标,w为宽度,grad为是否渐变色填充,n为数据个数
'''
p25=app.api.WorksheetFunction.Percentile(data,0.25)
p50=app.api.WorksheetFunction.Percentile(data,0.5)
p75=app.api.WorksheetFunction.Percentile(data,0.75)
iqr=p75-p25
pu=p75+1.5*iqr
pl=p25-1.5*iqr
minv=app.api.WorksheetFunction.Min(data)
maxv=app.api.WorksheetFunction.Max(data)
if minv>pl: pl=minv
if maxv<pu: pu=maxv
#绘中值线
by=shape_y(cht,x-w/2)
ey=shape_y(cht,x+w/2)
bx=shape_x(cht,p50)
ex=shape_x(cht,p50)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
#画箱体
bx=shape_x(cht,p25)
by=shape_y(cht,x+w/2)
ex=cht.PlotArea.InsideWidth/(cht.Axes(1).MaximumScale-cht.Axes(1).MinimumScale)*iqr
ey=cht.PlotArea.InsideHeight/(cht.Axes(2).MaximumScale-cht.Axes(2).MinimumScale)*w
shp=cht.Shapes.AddShape(1,bx,by,ex,ey)
if grad:
shp.Fill.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Fill.OneColorGradient(1, 1, 1)
shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Line.Weight=1.5
else:
shp.Fill.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Fill.Transparency=0.5 #半透明
shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Line.Weight=1.5
#绘触须
by=shape_y(cht,x)
ey=shape_y(cht,x)
bx=shape_x(cht,p75)
ex=shape_x(cht,pu)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
by=shape_y(cht,x)
ey=shape_y(cht,x)
bx=shape_x(cht,p25)
ex=shape_x(cht,pl)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
by=shape_y(cht,x-w/4)
ey=shape_y(cht,x+w/4)
bx=shape_x(cht,pu)
ex=shape_x(cht,pu)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
by=shape_y(cht,x-w/4)
ey=shape_y(cht,x+w/4)
bx=shape_x(cht,pl)
ex=shape_x(cht,pl)
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.Weight=1.5
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
#绘异常点
for i in range(n):
yy=shape_y(cht,x-w/20)
xx=shape_x(cht,data[i])
ww=cht.PlotArea.InsideWidth/(cht.Axes(1).MaximumScale-\
cht.Axes(1).MinimumScale)*w/10
hh=ww
if data[i]>pu or data[i]<pl:
shp2=cht.Shapes.AddShape(92,xx,yy,ww,hh)
#... 省略部分代码
#创建箱形图
draw_boxplot_h(app,cht,d1,count1,1,102,188,152,0.5,False)
draw_boxplot_h(app,cht,d2,count2,2,170,208,157,0.5,False)
draw_boxplot_h(app,cht,d3,count3,3,227,234,150,0.5,False)
draw_boxplot_h(app,cht,d4,count4,4,252,220,137,0.5,False)
draw_boxplot_h(app,cht,d5,count5,5,224,106,68,0.5,False)
draw_boxplot_h(app,cht,d6,count6,6,138,35,63,0.5,False)
#... 省略部分代码
单渐变色简单箱形图
多色简单箱形图中,虽然每个箱形的颜色不同,但每个箱形是单色的。本小节开始介绍如何绘制用渐变色填充箱体的箱形图。首先介绍比较简单的情况,即用相同的渐变色填充各箱体的情况。
图5-10 自定义单渐变色简单箱形图
draw_boxplot函数中对箱体进行渐变色填充的代码如下所示,使用了FillFormat对象的TwoColorGradient方法。用grad参数指定是否使用渐变色进行填充。完整代码见:Samples->ch08 统计图表->09 单渐变色简单箱形图->py.py。
def draw_boxplot(app,cht,data,n,x,r,g,b,w,grad):
#... 省略部分代码
#画箱体
bx=shape_x(cht,x-w/2)
by=shape_y(cht,p75)
ex=cht.PlotArea.InsideWidth/(cht.Axes(1).MaximumScale-cht.Axes(1).MinimumScale)*w
ey=cht.PlotArea.InsideHeight/(cht.Axes(2).MaximumScale-cht.Axes(2).MinimumScale)*iqr
shp=cht.Shapes.AddShape(1,bx,by,ex,ey)
if grad:
shp.Fill.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Fill.OneColorGradient(1, 1, 1)
shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
shp.Line.Weight=1.5
else:
#... 省略部分代码
调用该函数时给每个箱形指定相同的颜色,设置最后一个参数的值为True,表示用渐变色进行填充。
#... 省略部分代码
#创建箱形图
draw_boxplot(app,cht,d1,count1,1,0,0,255,0.5,True)
draw_boxplot(app,cht,d2,count2,2,0,0,255,0.5,True)
draw_boxplot(app,cht,d3,count3,3,0,0,255,0.5,True)
draw_boxplot(app,cht,d4,count4,4,0,0,255,0.5,True)
draw_boxplot(app,cht,d5,count5,5,0,0,255,0.5,True)
draw_boxplot(app,cht,d6,count6,6,0,0,255,0.5,True)
运行代码,生成类似图5-10的单渐变色简单箱形图。
多渐变色简单箱形图
多渐变色简单箱形图用不同的渐变色填充各箱形的箱体,如图5-11所示。
图5-11 多渐变色简单箱形图
用Python xlwings绘制多渐变色简单箱形图时,用draw_boxplot函数绘制渐变色填充的箱形,并给每个箱形指定不同的颜色即可。完整代码见:Samples->ch08 统计图表->10 多渐变色简单箱形图->py.py。
#... 省略部分代码
#创建箱形图
draw_boxplot(app,cht,d1,count1,1,102,188,152,0.5,True)
draw_boxplot(app,cht,d2,count2,2,170,208,157,0.5,True)
draw_boxplot(app,cht,d3,count3,3,227,234,150,0.5,True)
draw_boxplot(app,cht,d4,count4,4,252,220,137,0.5,True)
draw_boxplot(app,cht,d5,count5,5,224,106,68,0.5,True)
draw_boxplot(app,cht,d6,count6,6,138,35,63,0.5,True)
简单箱形图叠加均值连线
箱形图上没有均值信息,所以,常常在一组箱形图上叠加连接均值得到的折线。
图5-13 箱形图叠加均值连线
使用Python xlwings实现时先计算各组数据的均值,然后利用这些均值绘制折线。完整代码见:Samples->ch08 统计图表->11 简单箱形图叠加均值连线->py.py。
#绘制均值连线
meanv=[0 for _ in range(6)]
meanv[0]=app.api.WorksheetFunction.Average(d1) #计算均值
meanv[1]=app.api.WorksheetFunction.Average(d2)
meanv[2]=app.api.WorksheetFunction.Average(d3)
meanv[3]=app.api.WorksheetFunction.Average(d4)
meanv[4]=app.api.WorksheetFunction.Average(d5)
meanv[5]=app.api.WorksheetFunction.Average(d6)
#均值连线
for i in range(5):
bx=shape_x(cht,i+1)
by=shape_y(cht,meanv[i])
ex=shape_x(cht,i+2)
ey=shape_y(cht,meanv[i+1])
shp2=cht.Shapes.AddLine(bx,by,ex,ey)
shp2.Line.ForeColor.RGB=xw.utils.rgb_to_int((100,100,100))
shp2.Line.Weight=1.5
运行代码,生成的效果类似图5-13所示。
复合箱形图
复合箱形图用多组箱形序列表示多个分组的多组数据。如图5-14所示,颜色相同的箱形构成一个序列,相邻的不同颜色的箱形构成一个分组,图中有2个序列,6个分组。
图5-14 复合箱形图
用Python xlwings无法直接绘制复合箱形图,需要按照5.2.2小节介绍的方法自己创建。按照该方法自己绘制两组简单箱形图即可,注意计算好它们的显示位置。