统计图表中常常把不同类型的图表组合在一起组成一个新的图表,这个新图表兼具有组成它的每个图表的用途,可以为使用者提供绘图数据的更多的信息。比如5.3.2小节介绍的小提琴图样式2就组合了简单小提琴图和箱形图,从而让使用者对数据有更多的了解。本节介绍更多组合图,包括云雨图、误差柱状图、散点柱状图和散点箱形图等。[大谦Excel,dqexcel点com]
云雨图
云雨图是核密度估计曲线图、箱形图和抖动散点图等图表类型的组合,根据参与组合的图表类型的不同,可以有不同的样式。下面介绍3种常见样式。
云雨图样式1由核密度估计曲线图和抖动散点图组合而成,如图5-17所示。上有云,下有雨,很形象。抖动散点图直接显示了绘图数据的分布特征。
图5-17 云雨图样式1
用draw_kde函数绘制核密度估计曲线图,用draw_rnd_scatter_h函数绘制水平放置的抖动散点图。调用这两个函数绘制云雨图样式1。完整代码见:Samples->ch08 统计图表->15 云雨图->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') #获取指定工作表对象
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函数的实现代码为:
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函数的实现代码为:
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一样。
图5-18 云雨图样式2
用draw_kde函数绘制核密度估计曲线图,用draw_boxplot_h函数绘制水平放置的箱形图。调用这两个函数绘制云雨图样式2。完整代码见:Samples->ch08 统计图表->16 云雨图2->py.py。
#... 省略部分代码
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所示。该样式的云雨图兼具了各种图表的优点,可以直接探查原始数据的分布,也可以用统计量和概率密度了解数据的总体特征。
图5-19 云雨图样式3
用draw_kde函数绘制核密度估计曲线图,用draw_boxplot_h函数绘制水平放置的箱形图,用draw_rnd_scatter_h函数绘制水平放置的抖动散点图。调用这三个函数绘制云雨图样式3。完整代码见:Samples->ch08 统计图表->17 云雨图3->py.py。
#... 省略部分代码
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所示。
图5-20 误差柱状图
用Python xlwings编程绘制误差柱状图时,笔者试图先创建柱状图,然后利用序列对象的Hasbars属性、ErrorBars属性和ErrorBar属性等添加误差条图,如下面代码所示。但操作没有成功,误差条始终加不上。
所以,最后考虑自己绘制柱状图和误差条图,如下面代码所示。完整代码见:Samples->ch08 统计图表->18 误差柱状图->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 #横轴最小值.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函数用多边形绘制柱形面。也可以用矩形表示柱形面。
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条线段。
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所示。该图给出了各组数据的均值统计量和数据点本身。
图5-21 散点柱状图
用draw_bar函数绘制柱状图,用draw_rnd_scatter函数绘制抖动散点图。调用这两个函数绘制散点柱状图。完整代码见:Samples->ch08 统计图表->19 散点柱状图->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=-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所示。散点箱形图给出了各组数据的分位数统计量和数据点本身。
图5-23 用自定义函数绘制散点箱形图
用draw_boxplot函数绘制箱形图,用draw_rnd_scatter函数绘制抖动散点图。调用这两个函数绘制散点箱形图。完整代码见:Samples->ch08 统计图表->21 散点箱形图-自定义->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=[]
#筛选数据
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函数绘制抖动散点图。
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的散点箱形图。