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

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]

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

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

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),就出来个九边形的散点图,如下:
image.png
这种定义方式大大拓展了散点形状的自定义方式,本文所做的饼图也源于此。

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()

得到结果就是:
image.png

首先讲讲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()
image.png

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

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

这样一个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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容