前言
昨天在浏览www.hltv.org网站的csgo选手数据时看到了这样一幅图,图上包含有选手的六项数据,分别为KPR(平均每回合击杀),DPR(平均每回合死亡),KAST(助攻综合百分比),IMPACT(影响力),ADR(平均每回合输出),Rating 2.0(综合得分),从这图上可以看出KPR,DPR,IMPACT和RATING2.0为同一类型的输出(保留两位小数),KAST以百分比输出,ADR的范围在0到100多之间,它们是不同量纲的数据,但是图上却有一条灰色的平均线,同时,数据的大小影响到了该数据条的颜色,这样的图是怎么画出来的呢?能不能用python的Seaborn画出这样的图呢?
流程
作图之前,我们需要先弄清楚,默认的作图和我们想要的作图之间到底存在什么样的不同之处?
看官网示例的作图:
第一,Y轴的额外标签(total_bill)和X轴的额外标签(day)应该去掉。
第二,Y轴的刻度标签应该去掉(0,5,10,15,20)。
第三,每个条形图上方应该存在数据标签而不是黑色竖直误差条。
第四,背景的刻度线应该淡化,Y轴该去掉刻度。
第五,应该存在一条共同的平均线,同时添加上文字标签放置在灰色线条的左侧。
第六,根据数值大小进行颜色变换。
关于第五点,我们可以看出这条共同的平均线其实可以用标准化后的各项数据表示,什么意思呢?进行正态变换,正态化后平均数=0,不就有共同的标准线了吗?同时第六点也有了解决思路,那就是比如高于一个标准差,高于两个标准差,高于三个标准差分别用不同的颜色。
seaborn.barplot(*, x=None, y=None, hue=None, data=None, order=None, hue_order=None,
estimator=<function mean at 0x7fecadf1cee0>,
ci=95, n_boot=1000, units=None, seed=None, orient=None,
color=None, palette=None, saturation=0.75, errcolor='.26', errwidth=None,
capsize=None, dodge=True, ax=None, **kwargs)
官网中对于seaborn这个画图的函数有许多参数可以定义,在这里我们只关注x,y,hue,data这三个参数,这里我们先把数据准备成官网范例中输入的DateFrame样式。
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.DataFrame()
df['data']=[2.67129444, 1.24857579, 2.11691103, 2.92484313, 2.80181327,3.1691944]
df['skill']=['KPR','DPR','KAST','IMPACT','ADR','Rating 2.0']
df['Category']=[1,0,1,1,1,2]
df
运行后得到以下输出:
第一列为已经标准化后的数据y,第二列为我们要的标签名x,第三列是我们定义好的类别轴hue,这里方便演示把高于一个标准差小于两个设为0,高于两个设为1,高于三个设为2。我们先按照官网示例的方法输入以下代码:
sns.barplot(x='skill',y='data',hue='Category',data = df , palette='Blues',dodge=False)
这里传入参数的时候有个data = df,表示传入我们要用的数据框(DataFrame),x,y,hue则以取列名的方式传入参数,其实等同于x=df[‘skill’],传入了一个Series。在这种情况下作图得出的结果:
会带有额外标签data和skill,显然不是我们想要的结果。如何去掉?这里我们只需要传入单纯的数组(array)就可以了。
sns.barplot(x=df['skill'].values,y=df['data'].values,hue=df['Category'].values,palette='Blues',dodge=False)
当一个Series加上.values属性,取出来的数值就是单纯的array,如下:
X轴和Y轴的额外标签没有了!
在此,我们已经完成了第六点(通过hue这个类别轴属性)和第一点(画图时传入数组而非Series),但是也多出了一点,那就是一个莫名其妙跟随类别轴出现的图例。
不过接下来,我们先看第四点,背景的刻度线淡化以及Y轴去掉刻度。
等等,这图上也没有刻度线啊??‘
别急,首先需要对它的整体布局进行一个调整,
sns.set(style="whitegrid")
这个style可以有白色带网格(我们要的),黑色带网格,白色不带网格,黑色不带网格四种,具体英文名搜一下都有这里不赘述了。效果如下:
有了刻度线,就可以进行淡化和Y轴去刻度问题了,我们用matplotlib库的一个功能即可解决:
plt.tick_params(labelleft=False,grid_alpha=0.2)
tick_params包含很多可选参数,这里我们传入labelleft=False,表示左侧的刻度关闭,如果想更改其他方向同理把left换成right,bottom,top就行。grid_aplha表示刻度线透明度,这里我们设定为0.2,效果如下:
怎么样,很接近了对吗,但是这个边框它没有消失啊,没关系,用Seaborn的despine函数即可解决:
sns.despine(top=True, right=True, left=True, bottom=True)
在这个参数中我们把上边,右边,左边,下边四个方向的轴全部设置为True,就去掉了它。
接下来去除图中目前最碍眼的部分即图例:
ax = plt.legend() #获取图中的图例为ax
ax.remove() #调用remove方法去除
现在只剩下第三点和第五点了,添加数据标签和一条带标签的灰色横线,它们都共同用到了plt.text方法添加文字标签,先添加带标签的灰色横线:
df['data']=df['data']+1
plt.axhline(y=1,ls="-", lw=2, c="darkgrey")
plt.text(-1,1,"Avg")
在说明之前需要明白,我们这里的平均线实际上是0,但我们不能把它画在X轴,看不到,那么一个好的方法就是把我们的df的data数据先全加上1,再把平均值设为1。
plt.axhline方法画了一条横向的直线,这里在y=1处画,设置线条属性为“-”形状,宽度为2,颜色为深灰的直线。
plt.text方法生成了一个在x=-1,y=1处的文字标签,我们把它命名为"Avg"。
效果如下:
接下来规范化输出数据标签,前面也提过量纲不同,所以需要输出标准化之前不同的原始数据值。
df1 = pd.Series([1.942354,0.370536,0.997880,1.400000,180.000000,1.437307]) #原始数据
for x,y in zip((0,1,3,5),(0,1,3,5)):
plt.text(x-0.2,df['data'][y]+0.08,'{:.2f}'.format(df1[y]),color = 'black') #保留两位小数
plt.text(1.7,df['data'][2]+0.08,'{:.2%}'.format(df1[2]),color = 'black') #保留两位小数同时百分号输出
plt.text(3.85,df['data'][4]+0.08,int(df1[4]),color = 'black') #整数输出
plt.text函数前两个参数是坐标值,这里我们需要一个合适的美观的坐标值,x轴就根据1,2,3,4,5的特殊位置进行调整,y轴就根据每列的Y值再往上一点点就可以,color属性设置文字颜色,这里用黑色。由于KPR(0),DPR(1),IMPACT(3),Rating2.0(5)的轴是同一种输出方式,所以就用循环进行处理,再单独处理KAST(2)和ADR(4)就可以了。
结果如下:
最后一步,也就是看个人喜好的一步,调整颜色板:
sns.barplot(x=df['skill'].values,y=df['data'].values,hue=df['Category'].values,palette='Blues',dodge=False)
也就是其中的palette参数,这里我用的是蓝色’Blues’,如果想反转颜色变化方向就用’Blues_r’,此外还有sns.color_palette(),sns.cubehelix_palette()等多种色板可以调整。
比如:
palette1 = sns.color_palette("pastel") #建立一个调色板
sns.barplot(x=df['skill'].values,y=df['data'].values,hue=df['Category'].values,palette=palette1,dodge=False)
只需要先建立一个调色板,再把它传入palette参数就可以了,效果如下:
总结
临时起意作图,经过了上百次调试之后不仅对pandas的数据结构有了更深刻的理解,也学会了很多作图的新技巧,本文所用的function总结如下:
sns.barplot(x,y,hue,palette,dodge)
sns.set(style)
plt.tick_params(labelleft,grid_alpha)
sns.despine(top,right,left,bottom)
plt.legend().remove()
plt.axhline(y,ls,lw,c)
plt.text(x,y,label,color)
你学会了吗?