有趣的图形:用Python绘制带饼图的散点图兼论marker的隐藏功能

浏览: 1989

作者:博观厚积

链接:https://www.jianshu.com/p/4f67dbf4e3f9

來源:简书

01 带饼图的散点图

有这样一个例子:假设有五个人,每个人的月均收入水平为a=[1,3,2,4,3],消费水平b=[2,1,3,3,5],数据单位均有千元。同时五个人消费水平中,按照每月衣食住行的消费比例为:

s1=[0.1,0.2,0.3,0.4]

s2=[0.35,0.35,0.2,0.1]

s3=[0.2,0.25,0.25,0.3]

s4=[0.5,0.1,0.15,0.25]

s5=[0.0,0.25,0.4,0.35]

依据上述数据(数据纯属虚构),我可以将这些数据画在一个图形里,如图:

(其中,蓝色:衣;黄色:食;红色:住;绿色:行)
这个图形就是带饼图的散点图,从图中不仅可以看出五个人的消费与收入的关系趋势,也可以看出每个人的衣食住行消费比例不同。
是不是很有趣的一个图形?它是怎么做出来的?

02 matplotlib中marker参数的一个隐藏功能

上图是用python中matplotlib包绘制的,而绘制成带饼图的散点图则是用了里边关键的marker参数,所以在介绍如何绘制此图之前,先说说marker参数的一个隐藏功能。
一般的我们绘制散点图基本的命令为:

import matplotlib.pyplot as plt
plt.scatter(x, y, s=20, c=None, marker='o')

其中,s是点的大小,c是颜色,marker就是指定点标记的形状,在这里用的就是小圆点o;我们还可以用“*”、“x”、“Δ”等等,甚至还有数字、字母所代表的形状。

事实上,不仅仅如此,在marker的help文档中还指出了一个我们不经常用的标记方式,那就是元组——(numsides, style, angle),numsides是边的个数,angle是旋转角度,style只有0,1,2,3四个值,举个例子。我设置marker=(9,0, 30),就出来个九边形的散点图,如下:

这种定义方式大大拓展了散点形状的自定义方式,本文所做的饼图也源于此。

03 单个带饼图的散点图绘制过程


但是,完全绘制成上述那个图形也并非那么容易,下面我们从一个带饼图的散点绘制讲起。
比如上面的a=1,b=2那个点的消费比例为:s1=[0.1,0.2,0.3,0.4],代码如下:

x = [0] + np.cos(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()#[0]表示x的初始值
y = [0] + np.sin(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist() #用tolist()形成数列
xy1 = list(zip(x, y))

x = [0] + np.cos(np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5)).tolist()
xy2 = list(zip(x, y))

x = [0] + np.cos(np.linspace(2 * np.pi * 0.3, 2 * np.pi* 0.6, 5)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * 0.3, 2 * np.pi* 0.6, 5)).tolist()
xy3 = list(zip(x, y))

x = [0] + np.cos(np.linspace(2 * np.pi * 0.6, 2 * np.pi*1, 5)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * 0.6, 2 * np.pi*1, 5)).tolist()
xy4 = list(zip(x, y))
fig, ax = plt.subplots()
ax.scatter(a[0], b[0], marker=(xy1),
s=500,facecolor='blue')
ax.scatter(a[0], b[0], marker=(xy2),
s=500,facecolor='y')
ax.scatter(a[0], b[0], marker=(xy3),
s=500,facecolor='red')
ax.scatter(a[0], b[0], marker=(xy4),
s=500,facecolor='green') #为了饼图看得清,散点的size要大一些

plt.show()

得到结果就是:

首先讲讲x、y变量的生成,其原理是先根据每个占比数值所形成的角度(乘以2π,如2 * np.pi * 0.1),然后再用np.linspace函数五等分形成6个角度值,每个值赋予cos、sin函数,这是因为cos2θ+sin2θ=1,所有经过cos、sin函数的值会自动形成一个圆形。
值得注意的是,因为四个占比要围成一个圆形,所以除了第一个占比外,后边的都要用累计占比,如第二个0.2的占比np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5),第三个0.3的就是0.3-0.6之间,第四个是0.6-1之间。
如果还没明白那就单独把一个x、y生成的变量,单独作图可以看一下:

x = [0] + np.cos(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()
y = [0] + np.sin(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()
plt.plot(x,y,c='b')
plt.show()

可以看出,第一个占比x、y的轨迹就是一个弧形,然后再用初始值(0,0)牵引着,这样再对这个轨迹进行填充时,就会形成一个扇形,如下:

plt.scatter(a[0], b[0], marker=(xy1),
s=500,facecolor='blue')
plt.show()

这样一个x、y所形成的marker=(xy1)标记,再用facecolor='blue'填充就会形成一个扇形散点标记。最后用fig, ax = plt.subplots()把xy2、xy3、xy4所有子图都放上去,就形成一个圆形,然后用不同颜色填充,就形成一个饼图,每个饼图的角度大小由其占比比例决定。

04 所有点的饼图-散点图
上边是一个点的饼图-散点图,若是将a=[1,3,2,4,3],b=[2,1,3,3,5],以及b的所有个体衣食住行的消费比例全放进去,那就需要用到while、for循环条件,代码如下:

#5个消费水平下衣食住行的占比
s1=[0.1,0.2,0.3,0.4]
s2=[0.35,0.35,0.2,0.1]
s3=[0.2,0.25,0.25,0.3]
s4=[0.5,0.1,0.15,0.25]
s5=[0.0,0.25,0.4,0.35]
#计算累计占比
ss1=[s1[0],sum(s1[0:2]),sum(s1[0:3]),sum(s1[0:4])]
ss2=[s2[0],sum(s2[0:2]),sum(s2[0:3]),sum(s2[0:4])]
ss3=[s3[0],sum(s3[0:2]),sum(s3[0:3]),sum(s3[0:4])]
ss4=[s4[0],sum(s4[0:2]),sum(s4[0:3]),sum(s4[0:4])]
ss5=[s5[0],sum(s5[0:2]),sum(s5[0:3]),sum(s5[0:4])]

s=[ss1,ss2,ss3,ss4,ss5]
a=[1,3,2,4,3] #收入水平(千元)
b=[2,1,3,3,5] #消费水平(千元)
fig, ax = plt.subplots(figsize=(10,6))
i=0
while i<len(b):
x = [0] + np.cos(np.linspace(0, 2 * np.pi * s[i][0], 15)).tolist()
y = [0] + np.sin(np.linspace(0, 2 * np.pi * s[i][0], 15)).tolist()
xy1 = list(zip(x, y))

x = [0] + np.cos(np.linspace(2 * np.pi * s[i][0], 2 * np.pi * s[i][1], 15)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * s[i][0], 2 * np.pi * s[i][1], 15)).tolist()
xy2 = list(zip(x, y))

x = [0] + np.cos(np.linspace(2 * np.pi * s[i][1],
2 * np.pi* s[i][2], 15)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * s[i][1],
2 * np.pi* s[i][2], 15)).tolist()
xy3 = list(zip(x, y))

x = [0] + np.cos(np.linspace(2 * np.pi * s[i][2],
2 * np.pi*1, 15)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * s[i][2],
2 * np.pi*1, 15)).tolist()
xy4 = list(zip(x, y))
xy=[xy1,xy2,xy3,xy4]
c=['b','y','r','g']
for j in range(4):
ax.scatter(a[i], b[i], marker=(xy[j]),
s=800,facecolor=c[j])

i=i+1

plt.show()

最终得到本文前边那个图形。
另外还需说明一下,这个图形只适用于小样本数据,也就是图形三点的个数不能太多,每个点中比例数量也不能太多,否则影响展示效果。

写作不易,特别是技术类的写作,请大家多多支持,关注、点赞、转发等等,也欢迎大家关注知乎爬虫与数据分析专栏:https://zhuanlan.zhihu.com/zjying2000

推荐 3
本文由 Python爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册