组合图

统计图表中常常把不同类型的图表组合在一起组成一个新的图表,这个新图表兼具有组成它的每个图表的用途,可以为使用者提供绘图数据的更多的信息。比如5.3.2小节介绍的小提琴图样式2就组合了简单小提琴图和箱形图,从而让使用者对数据有更多的了解。本节介绍更多组合图,包括云雨图、误差柱状图、散点柱状图和散点箱形图等。[大谦Excel,dqexcel点com]

云雨图

云雨图是核密度估计曲线图、箱形图和抖动散点图等图表类型的组合,根据参与组合的图表类型的不同,可以有不同的样式。下面介绍3种常见样式。

云雨图样式1由核密度估计曲线图和抖动散点图组合而成,如图5-17所示。上有云,下有雨,很形象。抖动散点图直接显示了绘图数据的分布特征。

Document Image
\[\]

图5-17 云雨图样式1

用draw_kde函数绘制核密度估计曲线图,用draw_rnd_scatter_h函数绘制水平放置的抖动散点图。调用这两个函数绘制云雨图样式1。完整代码见:Samples->ch08 统计图表->15 云雨图->py.py。

code.python
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')    #获取指定工作表对象
data=sht.range('A1:B90').value    #获取数据
app.kill()    #退出应用
#从comtypes包中导入CreateObject函数
from comtypes.client import CreateObject
app2=CreateObject("Excel.Application")    #创建Excel应用
app2.Visible=True    #应用窗口可见
app2.ScreenUpdating=False
wb2=app2.Workbooks.Open(root+r'/data.xlsx')    #添加工作簿
sht2=wb2.Sheets('Sheet1')    #获取第1个工作表
shp=sht2.Shapes.AddChart2()    #创建空白图表
shp.Left=20    #图表位置和大小
shp.Top=20
shp.Width=250
shp.Height=300
cht=shp.Chart    #获取图表
cht.ChartType=-4169    #散点图
ax1=cht.Axes(1)    #获取横轴
ax2=cht.Axes(2)    #获取纵轴
ax1.MinimumScale=-4    #横轴最小值
ax1.MaximumScale=4
ax2.MinimumScale=0    #纵轴最小值
ax2.MaximumScale=6
ax1.CrossesAt=ax1.MinimumScale    #坐标轴交点位置
ax2.CrossesAt=ax2.MinimumScale
set_style(cht)    #设置样式
count1=0
count2=0
count3=0
d1=[]
d2=[]
d3=[]
#筛选数据
for i in range(90):
    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])
#绘图
draw_kde(cht,d1,1+0.2,0,0,255,-4,4)
draw_rnd_scatter_h(cht,d1,count1,1,0.5)
draw_kde(cht,d2,3+0.2,0,0,255,-4,4)
draw_rnd_scatter_h(cht,d2,count2,3,0.5)
draw_kde(cht,d3,5+0.2,0,0,255,-4,4)
draw_rnd_scatter_h(cht,d3,count3,5,0.5)
app2.ScreenUpdating=True

draw_rnd_scatter_h函数的实现代码为:

code.python
def draw_rnd_scatter_h(cht,x,n,y,w):
    #添加抖动散点图
    rd=[0 for _ in range(n)]
    for i in range(n):
        rd[i]=y-0.2-w*np.random.rand(1)[0]    #抖动数据
    ser=cht.SeriesCollection().NewSeries()    #新建序列
    ser.ChartType=-4169
    ser.XValues=x    #绑定数据
    ser.Values=rd
    ser.MarkerSize=4

draw_kde函数的实现代码为:

code.python
def draw_kde(cht,data,y,r,g,b,minx,maxx):
    kdex=[0 for _ in range(180)]
    kdef=[0 for _ in range(180)]
    step=(maxx-minx)/180
    for i in range(180):
        kdex[i]=minx+(i+1)*step
        kdef[i]=y+kde(data,kdex[i],0.5)
    cht.SeriesCollection().NewSeries()    #新建序列
    #单色填充多边形
    pt=[[0 for _ in range(2)] for _ in range(183)]
    for i in range(180):
        pt[i][0]=shape_x(cht,kdex[179-i])
        pt[i][1]=shape_y(cht,kdef[179-i])
    pt[180][0]=pt[179][0]
    pt[180][1]=shape_y(cht,y)
    pt[181][0]=pt[0][0]
    pt[181][1]=shape_y(cht,y)
    pt[182][0]=pt[0][0]
    pt[182][1]=pt[0][1]
    shp=cht.Shapes.AddPolyline(pt)
    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

第2种样式的云雨图由核密度估计曲线图和箱形图组合而成,如图5-18所示。该样式的作用跟上面介绍的小提琴图样式2一样。

Document Image
\[\]

图5-18 云雨图样式2

用draw_kde函数绘制核密度估计曲线图,用draw_boxplot_h函数绘制水平放置的箱形图。调用这两个函数绘制云雨图样式2。完整代码见:Samples->ch08 统计图表->16 云雨图2->py.py。

code.python
#... 省略部分代码
draw_kde(cht,d1,1+0.2,0,0,255,-4,4)
draw_boxplot_h(app2,cht,d1,count1,1,0,0,255,0.1,False)
draw_kde(cht,d2,3+0.2,0,0,255,-4,4)
draw_boxplot_h(app2,cht,d2,count2,3,0,0,255,0.1,False)
draw_kde(cht,d3,5+0.2,0,0,255,-4,4)
draw_boxplot_h(app2,cht,d3,count3,5,0,0,255,0.1,False)
#... 省略部分代码

3种样式的云雨图由核密度估计曲线图、箱形图和抖动散点图组合而成,如图5-19所示。该样式的云雨图兼具了各种图表的优点,可以直接探查原始数据的分布,也可以用统计量和概率密度了解数据的总体特征。

Document Image
\[\]

图5-19 云雨图样式3

用draw_kde函数绘制核密度估计曲线图,用draw_boxplot_h函数绘制水平放置的箱形图,用draw_rnd_scatter_h函数绘制水平放置的抖动散点图。调用这三个函数绘制云雨图样式3。完整代码见:Samples->ch08 统计图表->17 云雨图3->py.py。

code.python
#... 省略部分代码
draw_kde(cht,d1,1+0.2,0,0,255,-4,4)
draw_rnd_scatter_h(cht,d1,count1,1-0.2,0.5)
draw_boxplot_h(app2,cht,d1,count1,1,0,0,255,0.1,False)
draw_kde(cht,d2,3+0.2,0,0,255,-4,4)
draw_rnd_scatter_h(cht,d2,count2,3-0.2,0.5)
draw_boxplot_h(app2,cht,d2,count2,3,0,0,255,0.1,False)
draw_kde(cht,d3,5+0.2,0,0,255,-4,4)
draw_rnd_scatter_h(cht,d3,count3,5-0.2,0.5)
draw_boxplot_h(app2,cht,d3,count3,5,0,0,255,0.1,False)
#... 省略部分代码

误差柱状图

误差柱状图由柱状图和误差条图组合而成。柱状图一般用来表示各组数据的均值的大小,误差条图表示置信区间或标准差等。误差柱状图如图5-20所示。

Document Image
\[\]

图5-20 误差柱状图

用Python xlwings编程绘制误差柱状图时,笔者试图先创建柱状图,然后利用序列对象的Hasbars属性、ErrorBars属性和ErrorBar属性等添加误差条图,如下面代码所示。但操作没有成功,误差条始终加不上。

所以,最后考虑自己绘制柱状图和误差条图,如下面代码所示。完整代码见:Samples->ch08 统计图表->18 误差柱状图->py.py。

code.python
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    #横轴最小值.3
ax1.MaximumScale=6.7
ax2.MinimumScale=0    #纵轴最小值
ax2.MaximumScale=0.25
set_style(cht)    #设置样式
cht.SeriesCollection().NewSeries()    #新建序列
#自己绘制柱状图
data=sht.range('B2:B7').value
up=sht.range('C2:C7').value
dn=sht.range('D2:D7').value
for i in range(6):
    draw_bar(cht,data[i],i+1,0,0,255,0.5,False)
    draw_error(cht,data[i],up[i],dn[i],i+1,0,0,255)

上面代码中,draw_bar函数用多边形绘制柱形面。也可以用矩形表示柱形面。

code.python
def draw_bar(cht,y,x,r,g,b,w,grad):
    #画柱面
    bx=shape_x(cht,x-w/2)
    by=shape_y(cht,y)
    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)*y
    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

draw_error函数绘制误差条。误差条包括上触须、下触须和触须末端的短横线,共4条线段。

code.python
def draw_error(cht,y,eu,el,x,r,g,b):
#自己绘制误差条
    bx=shape_x(cht,x)
    ex=shape_x(cht,x)
    by=shape_y(cht,y)
    ey=shape_y(cht,y+eu)
    shp=cht.Shapes.AddLine(bx,by,ex,ey)
    shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
    shp.Line.Weight=1.5
    bx=shape_x(cht,x)
    ex=shape_x(cht,x)
    by=shape_y(cht,y)
    ey=shape_y(cht,y-el)
    shp=cht.Shapes.AddLine(bx,by,ex,ey)
    shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
    shp.Line.Weight=1.5
    bx=shape_x(cht,x-0.5/4)
    ex=shape_x(cht,x+0.5/4)
    by=shape_y(cht,y+eu)
    ey=shape_y(cht,y+eu)
    shp=cht.Shapes.AddLine(bx,by,ex,ey)
    shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
    shp.Line.Weight=1.5
    bx=shape_x(cht,x-0.5/4)
    ex=shape_x(cht,x+0.5/4)
    by=shape_y(cht,y-el)
    ey=shape_y(cht,y-el)
    shp=cht.Shapes.AddLine(bx,by,ex,ey)
    shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
    shp.Line.Weight=1.5

运行代码后生成图5-20所示的误差柱状图。

散点柱状图

散点柱状图在柱状图基础上叠加绘制抖动散点图,如图5-21所示。该图给出了各组数据的均值统计量和数据点本身。

Document Image
\[\]

图5-21 散点柱状图

用draw_bar函数绘制柱状图,用draw_rnd_scatter函数绘制抖动散点图。调用这两个函数绘制散点柱状图。完整代码见:Samples->ch08 统计图表->19 散点柱状图->py.py。

code.python
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=-4169    #散点图
ax1=cht.Axes(1)    #获取横轴
ax2=cht.Axes(2)    #获取纵轴
ax1.MinimumScale=0    #横轴最小值.5
ax1.MaximumScale=4.5
ax2.MinimumScale=0    #纵轴最小值
ax2.MaximumScale=0.35
set_style(cht)    #设置样式
cht.SeriesCollection().NewSeries()    #新建序列
data=sht.range('B2:E21').value
dt1=[0 for _ in range(20)]
dt2=[0 for _ in range(20)]
dt3=[0 for _ in range(20)]
dt4=[0 for _ in range(20)]
for i in range(20):
    dt1[i]=data[i][0]
    dt2[i]=data[i][1]
    dt3[i]=data[i][2]
    dt4[i]=data[i][3]
mean1=app.api.WorksheetFunction.Average(dt1)    #求均值
mean2=app.api.WorksheetFunction.Average(dt2)
mean3=app.api.WorksheetFunction.Average(dt3)
mean4=app.api.WorksheetFunction.Average(dt4)
#绘柱状图
draw_bar(cht,mean1,1,76,200,132,0.5,False)
draw_bar(cht,mean2,2,76,200,132,0.5,False)
draw_bar(cht,mean3,3,76,200,132,0.5,False)
draw_bar(cht,mean4,4,76,200,132,0.5,False)
#绘抖动散点图
draw_rnd_scatter(cht,1,dt1,20,0.5,192,0,0)
draw_rnd_scatter(cht,2,dt2,20,0.5,255,192,0)
draw_rnd_scatter(cht,3,dt3,20,0.5,146,208,80)
draw_rnd_scatter(cht,4,dt4,20,0.5,0,176,80)

运行代码生成类似图9-21所示的散点柱状图。

散点箱形图

散点箱形图在箱形图基础上叠加绘制抖动散点图,如图5-22和图5-23所示。散点箱形图给出了各组数据的分位数统计量和数据点本身。

Document Image
\[\]

图5-23 用自定义函数绘制散点箱形图

用draw_boxplot函数绘制箱形图,用draw_rnd_scatter函数绘制抖动散点图。调用这两个函数绘制散点箱形图。完整代码见:Samples->ch08 统计图表->21 散点箱形图-自定义->py.py。

code.python
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=[]
#筛选数据
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,76,200,132,0.5,False)
draw_boxplot(app,cht,d2,count2,2,76,200,132,0.5,False)
draw_boxplot(app,cht,d3,count3,3,76,200,132,0.5,False)
draw_boxplot(app,cht,d4,count4,4,76,200,132,0.5,False)
draw_boxplot(app,cht,d5,count5,5,76,200,132,0.5,False)
draw_boxplot(app,cht,d6,count6,6,76,200,132,0.5,False)
#绘制抖动散点图
draw_rnd_scatter(cht,1,d1,count1,0.5,192,0,0)
draw_rnd_scatter(cht,2,d2,count2,0.5,255,192,0)
draw_rnd_scatter(cht,3,d3,count3,0.5,146,208,80)
draw_rnd_scatter(cht,4,d4,count4,0.5,0,176,80)
draw_rnd_scatter(cht,5,d5,count5,0.5, 0,176,240)
draw_rnd_scatter(cht,6,d6,count6,0.5,0,112,192)

代码中draw_rnd_scatter函数绘制抖动散点图。

code.python
def draw_rnd_scatter(cht,x,y,n,w,r,g,b):
    #绘制抖动散点图
    rd=[]
    for i in range(n):
        rd.append(x-w/2+w*np.random.rand(1)[0])
    for i in range(n):
        bx=shape_x(cht,rd[i])
        by=shape_y(cht,y[i])
        ex=cht.PlotArea.InsideWidth/(cht.Axes(1).MaximumScale- \
        cht.Axes(1).MinimumScale)*0.09
        ey=ex
        shp=cht.Shapes.AddShape(9,bx,by,ex,ey)
        shp.Fill.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))
        shp.Line.Weight=1
        shp.Line.ForeColor.RGB=xw.utils.rgb_to_int((r,g,b))

运行完整代码生成类似图5-23的散点箱形图。