圆形嵌套图Circular Packing能够将一组组圆形互相嵌套起来,以显示数据的层次关系。本文主要基于circlify实现圆形嵌套图的绘制。circlify包由Python实现。官方开源地址见:
circlify
您可以使用以下代码安装circlify:
pip install circlify
本文主要参考circlify和circular-packing
circlify可以在没有层次结构的情况下工作,即只有一组数字变量,将每个变量都将显示为圆形。请注意,该包仅计算每个圆形的位置和大小。完成后,matplotlib用于制作图表本身。此外circlify库也提供了一个bubbles()函数完成所有绘图的功能。但它没有提供很多定制,所以matplotlib在这里是一个更好的绘图选择。
基于一级层次结构的基本圆形嵌套图只需要两列数据框。第一列提供每个项目的名称(用于标记)。第二列提供项目的数值。它控制圆形大小。
import pandas as pd
df = pd.DataFrame({
'Name': ['A', 'B', 'C', 'D', 'E', 'F'],
'Value': [10, 2, 23, 87, 12, 65]
})
df
Name
Value
0
A
10
1
B
2
2
C
23
3
D
87
4
E
12
5
F
65
在具有一级层次结构的基本圆形嵌套图中,数据集的每个实体都由一个圆圈表示。圆圈大小与其代表的值成正比。工作中最难的部分是计算每个圆的位置和大小。幸运的是,该circlify库提供了一个circlify()进行计算的函数。它将作为绘图输入。
计算函数输入参数如下:
data :(必需)从大到小排序的正值列表
show_enclosure :(可选)一个布尔值,指示是否在所有圆之外添加一个最小包围圆(默认为 False)
target_enclosure :(可选)最小包围圆的参数信息(默认为单位圆 (0, 0, 1))
import circlify
circles = circlify.circlify(
df['Value'].tolist(),
show_enclosure=False,
target_enclosure=circlify.Circle(x=0, y=0, r=1)
)
circles[0]
Circle(x=-0.44578608966292743, y=0.537367020215489, r=0.08132507760370634, level=1, ex={'datum': 2})
通过circlify计算数据后,基础的圆形嵌套图绘制如下所示。
# import libraries
import circlify
import matplotlib.pyplot as plt
# Create just a figure and only one subplot
# 设置图像尺寸
fig, ax = plt.subplots(figsize=(7,7))
# Remove axes
# 设置matplotlib不显示坐标轴
ax.axis('off')
# Find axis boundaries
# 查找轴边界
lim = max(
max(
abs(circle.x) + circle.r,
abs(circle.y) + circle.r,
)
for circle in circles
)
plt.xlim(-lim, lim)
plt.ylim(-lim, lim)
# print circles
# 画圆
for circle in circles:
x, y, r = circle
# fill表示不填充圆
ax.add_patch(plt.Circle((x, y), r, alpha=0.2, linewidth=2, fill=False))
让我们从这里做一些更漂亮、更有洞察力的事情。我们将添加一个标题,给圆形着色并添加标签:
# import libraries
import circlify
import matplotlib.pyplot as plt
# Create just a figure and only one subplot
fig, ax = plt.subplots(figsize=(10,10))
# Title
# 添加标题
ax.set_title('Basic circular packing')
# Remove axes
ax.axis('off')
# Find axis boundaries
lim = max(
max(
abs(circle.x) + circle.r,
abs(circle.y) + circle.r,
)
for circle in circles
)
plt.xlim(-lim, lim)
plt.ylim(-lim, lim)
# list of labels
# 添加标签
labels = df['Name']
# print circles
for circle, label in zip(circles, labels):
x, y, r = circle
ax.add_patch(plt.Circle((x, y), r, alpha=0.2, linewidth=2))
# 添加标签
plt.annotate(
label,
(x,y ) ,
va='center',
ha='center'
)
可以轻松地在圆形之间添加间距。只需要提供半径参数的百分比给add_patch()(此处为70%)。
# Create just a figure and only one subplot
fig, ax = plt.subplots(figsize=(10,10))
# Title
ax.set_title('Basic circular packing')
# Remove axes
ax.axis('off')
# Find axis boundaries
lim = max(
max(
abs(circle.x) + circle.r,
abs(circle.y) + circle.r,
)
for circle in circles
)
plt.xlim(-lim, lim)
plt.ylim(-lim, lim)
# list of labels
labels = df['Name']
# print circles
for circle, label in zip(circles, labels):
x, y, r = circle
# facecolor设置圆的填充颜色,edgecolor设置边框颜色
ax.add_patch(plt.Circle((x, y), r*0.7, alpha=0.9, linewidth=2, facecolor="#69b2a3", edgecolor="black"))
# boxstyle设置边框形状,pad设置边框填充
plt.annotate(label, (x,y ) ,va='center', ha='center', bbox=dict(facecolor='white', edgecolor='black', boxstyle='round', pad=.5))
下面将解释如何构建具有多个层次结构的圆形嵌套图。它使用circlify库来计算圆形位置,并通过matplotlib用于渲染图形。
此示例考虑分层数据集。世界被大陆分割。大陆按国家划分。每个国家都有一个值(人口规模)。我们的目标是将每个国家表示为一个圆圈,其大小与其人口成正比。让我们创建这样一个数据集:
data = [{'id': 'World', 'datum': 6964195249, 'children' : [
{'id' : "North America", 'datum': 450448697,
'children' : [
{'id' : "United States", 'datum' : 308865000},
{'id' : "Mexico", 'datum' : 107550697},
{'id' : "Canada", 'datum' : 34033000}
]},
{'id' : "South America", 'datum' : 278095425,
'children' : [
{'id' : "Brazil", 'datum' : 192612000},
{'id' : "Colombia", 'datum' : 45349000},
{'id' : "Argentina", 'datum' : 40134425}
]},
{'id' : "Europe", 'datum' : 209246682,
'children' : [
{'id' : "Germany", 'datum' : 81757600},
{'id' : "France", 'datum' : 65447374},
{'id' : "United Kingdom", 'datum' : 62041708}
]},
{'id' : "Africa", 'datum' : 311929000,
'children' : [
{'id' : "Nigeria", 'datum' : 154729000},
{'id' : "Ethiopia", 'datum' : 79221000},
{'id' : "Egypt", 'datum' : 77979000}
]},
{'id' : "Asia", 'datum' : 2745929500,
'children' : [
{'id' : "China", 'datum' : 1336335000},
{'id' : "India", 'datum' : 1178225000},
{'id' : "Indonesia", 'datum' : 231369500}
]}
]}]
data
[{'id': 'World',
'datum': 6964195249,
'children': [{'id': 'North America',
'datum': 450448697,
'children': [{'id': 'United States', 'datum': 308865000},
{'id': 'Mexico', 'datum': 107550697},
{'id': 'Canada', 'datum': 34033000}]},
{'id': 'South America',
'datum': 278095425,
'children': [{'id': 'Brazil', 'datum': 192612000},
{'id': 'Colombia', 'datum': 45349000},
{'id': 'Argentina', 'datum': 40134425}]},
{'id': 'Europe',
'datum': 209246682,
'children': [{'id': 'Germany', 'datum': 81757600},
{'id': 'France', 'datum': 65447374},
{'id': 'United Kingdom', 'datum': 62041708}]},
{'id': 'Africa',
'datum': 311929000,
'children': [{'id': 'Nigeria', 'datum': 154729000},
{'id': 'Ethiopia', 'datum': 79221000},
{'id': 'Egypt', 'datum': 77979000}]},
{'id': 'Asia',
'datum': 2745929500,
'children': [{'id': 'China', 'datum': 1336335000},
{'id': 'India', 'datum': 1178225000},
{'id': 'Indonesia', 'datum': 231369500}]}]}]
然后我们需要用circlify()来计算表示每个国家和大陆圆形的位置,以及它们的半径。
# import the circlify library
import circlify
# Compute circle positions thanks to the circlify() function
# 计算
circles = circlify.circlify(
data,
show_enclosure=False,
target_enclosure=circlify.Circle(x=0, y=0, r=1)
)
circles
[Circle(x=0.0, y=0.0, r=1.0, level=1, ex={'id': 'World', 'datum': 6964195249, 'children': [{'id': 'North America', 'datum': 450448697, 'children': [{'id': 'United States', 'datum': 308865000}, {'id': 'Mexico', 'datum': 107550697}, {'id': 'Canada', 'datum': 34033000}]}, {'id': 'South America', 'datum': 278095425, 'children': [{'id': 'Brazil', 'datum': 192612000}, {'id': 'Colombia', 'datum': 45349000}, {'id': 'Argentina', 'datum': 40134425}]}, {'id': 'Europe', 'datum': 209246682, 'children': [{'id': 'Germany', 'datum': 81757600}, {'id': 'France', 'datum': 65447374}, {'id': 'United Kingdom', 'datum': 62041708}]}, {'id': 'Africa', 'datum': 311929000, 'children': [{'id': 'Nigeria', 'datum': 154729000}, {'id': 'Ethiopia', 'datum': 79221000}, {'id': 'Egypt', 'datum': 77979000}]}, {'id': 'Asia', 'datum': 2745929500, 'children': [{'id': 'China', 'datum': 1336335000}, {'id': 'India', 'datum': 1178225000}, {'id': 'Indonesia', 'datum': 231369500}]}]}),
Circle(x=-0.1891573044970616, y=0.7725949609994359, r=0.1964724487306323, level=2, ex={'id': 'Europe', 'datum': 209246682, 'children': [{'id': 'Germany', 'datum': 81757600}, {'id': 'France', 'datum': 65447374}, {'id': 'United Kingdom', 'datum': 62041708}]}),
Circle(x=-0.5193811141243917, y=-0.4774793174718824, r=0.22650056519090414, level=2, ex={'id': 'South America', 'datum': 278095425, 'children': [{'id': 'Brazil', 'datum': 192612000}, {'id': 'Colombia', 'datum': 45349000}, {'id': 'Argentina', 'datum': 40134425}]}),
Circle(x=-0.5250482991363239, y=0.4940564718994228, r=0.23988342689140008, level=2, ex={'id': 'Africa', 'datum': 311929000, 'children': [{'id': 'Nigeria', 'datum': 154729000}, {'id': 'Ethiopia', 'datum': 79221000}, {'id': 'Egypt', 'datum': 77979000}]}),
Circle(x=-0.7117329289789401, y=0.0, r=0.28826707102105975, level=2, ex={'id': 'North America', 'datum': 450448697, 'children': [{'id': 'United States', 'datum': 308865000}, {'id': 'Mexico', 'datum': 107550697}, {'id': 'Canada', 'datum': 34033000}]}),
Circle(x=0.28826707102105975, y=0.0, r=0.7117329289789401, level=2, ex={'id': 'Asia', 'datum': 2745929500, 'children': [{'id': 'China', 'datum': 1336335000}, {'id': 'India', 'datum': 1178225000}, {'id': 'Indonesia', 'datum': 231369500}]}),
Circle(x=-0.8015572298502232, y=0.13991165332617728, r=0.06017798041665636, level=3, ex={'id': 'Canada', 'datum': 34033000}),
Circle(x=-0.6218965087862706, y=-0.35827194898537407, r=0.06927524011838612, level=3, ex={'id': 'Argentina', 'datum': 40134425}),
Circle(x=-0.6715240632168605, y=-0.49229197511777817, r=0.07363823567480635, level=3, ex={'id': 'Colombia', 'datum': 45349000}),
Circle(x=-0.20484950837730978, y=0.8820383650518233, r=0.08590977893113161, level=3, ex={'id': 'United Kingdom', 'datum': 62041708}),
Circle(x=-0.2883116431566897, y=0.7291956444670085, r=0.08823620918716854, level=3, ex={'id': 'France', 'datum': 65447374}),
Circle(x=-0.5807524341097545, y=0.6266527390697123, r=0.09606159016055799, level=3, ex={'id': 'Egypt', 'datum': 77979000}),
Circle(x=-0.6616477217438194, y=0.4515509451194898, r=0.09682357206293761, level=3, ex={'id': 'Ethiopia', 'datum': 79221000}),
Circle(x=-0.10145547990466931, y=0.7291956444670085, r=0.09861995406485186, level=3, ex={'id': 'Germany', 'datum': 81757600}),
Circle(x=-0.8930220906231182, y=0.0, r=0.1069779093768817, level=3, ex={'id': 'Mexico', 'datum': 107550697}),
Circle(x=-0.42950888228212775, y=0.4515509451194898, r=0.13531526739875396, level=3, ex={'id': 'Nigeria', 'datum': 154729000}),
Circle(x=-0.44612447532083954, y=-0.49229197511777817, r=0.15176135222121462, level=3, ex={'id': 'Brazil', 'datum': 192612000}),
Circle(x=0.2610622289123354, y=0.3631857971321717, r=0.15273517341003195, level=3, ex={'id': 'Indonesia', 'datum': 231369500}),
Circle(x=-0.6047550196020585, y=0.0, r=0.18128916164417805, level=3, ex={'id': 'United States', 'datum': 308865000}),
Circle(x=-0.07879852383709784, y=0.0, r=0.34466733412078254, level=3, ex={'id': 'India', 'datum': 1178225000}),
Circle(x=0.6329344051418423, y=0.0, r=0.3670655948581576, level=3, ex={'id': 'China', 'datum': 1336335000})]
# import libraries
import circlify
import matplotlib.pyplot as plt
# Create just a figure and only one subplot
fig, ax = plt.subplots(figsize=(14,14))
# Title
ax.set_title('Repartition of the world population')
# Remove axes
ax.axis('off')
# Find axis boundaries
lim = max(
max(
abs(circle.x) + circle.r,
abs(circle.y) + circle.r,
)
for circle in circles
)
plt.xlim(-lim, lim)
plt.ylim(-lim, lim)
# Print circle the highest level (continents):
# 打印最高级别的圆形也就是数据中的大陆,这一部分circle的level为3
for circle in circles:
if circle.level != 2:
continue
x, y, r = circle
ax.add_patch( plt.Circle((x, y), r, alpha=0.5, linewidth=2, color="lightblue"))
# 打印次高级别的圆形也就是数据中的国家,这一部分circle的level为2
for circle in circles:
if circle.level != 3:
continue
x, y, r = circle
label = circle.ex["id"]
ax.add_patch( plt.Circle((x, y), r, alpha=0.5, linewidth=2, color="#69b3a2"))
# 画出国家的名字
plt.annotate(label, (x,y ), ha='center', color="white")
# Print labels for the continents
# 画出各大洲的标签
for circle in circles:
if circle.level != 2:
continue
x, y, r = circle
label = circle.ex["id"]
plt.annotate(label, (x,y ) ,va='center', ha='center', bbox=dict(facecolor='white', edgecolor='black', boxstyle='round', pad=.5))
如果只是想看看数据如何,可以用circlify的自带绘图函数bubbles,但是不能定制图形。circlify的bubbles函数好处就是不需要用matplotlib一层一层的画圆。
一级层次绘图
from pprint import pprint as pp
import circlify
# 定义圆
# show_enclosure=True表示显示外圈大圆,也就是结果中的#0
circles = circlify.circlify([19, 17, 13, 11, 7, 5, 3, 2, 1], show_enclosure=True)
# 美化输出
pp(circles)
# 展示结果
circlify.bubbles(circles)
[Circle(x=0.0, y=0.0, r=1.0, level=0, ex=None),
Circle(x=-0.633232604611031, y=-0.47732413442115296, r=0.09460444572843042, level=1, ex={'datum': 1}),
Circle(x=-0.7720311587589236, y=0.19946176418549022, r=0.13379089020993573, level=1, ex={'datum': 2}),
Circle(x=-0.43168871955473165, y=-0.6391381648617572, r=0.16385970662353394, level=1, ex={'datum': 3}),
Circle(x=0.595447603036083, y=0.5168251295666467, r=0.21154197162246005, level=1, ex={'datum': 5}),
Circle(x=-0.5480911056188739, y=0.5115139053491098, r=0.2502998363185337, level=1, ex={'datum': 7}),
Circle(x=0.043747233552068686, y=-0.6848366902134195, r=0.31376744998074435, level=1, ex={'datum': 11}),
Circle(x=0.04298737651230445, y=0.5310431146935967, r=0.34110117996070605, level=1, ex={'datum': 13}),
Circle(x=-0.3375943908160698, y=-0.09326467617622711, r=0.39006412239133215, level=1, ex={'datum': 17}),
Circle(x=0.46484095011516874, y=-0.09326467617622711, r=0.4123712185399064, level=1, ex={'datum': 19})]
多级层次绘图
from pprint import pprint as pp
import circlify
data = [
0.05, {'id': 'a2', 'datum': 0.05},
{'id': 'a0', 'datum': 0.8, 'children': [0.3, 0.2, 0.2, 0.1], },
{'id': 'a1', 'datum': 0.1, 'children': [
{'id': 'a1_1', 'datum': 0.05}, {'datum': 0.04}, 0.01],
},
]
circles = circlify.circlify(data, show_enclosure=True)
pp(circles)
# 展示结果
circlify.bubbles(circles)
[Circle(x=0.0, y=0.0, r=1.0, level=0, ex=None),
Circle(x=-0.5658030759977484, y=0.4109778665114514, r=0.18469903125906464, level=1, ex={'datum': 0.05}),
Circle(x=-0.5658030759977484, y=-0.4109778665114514, r=0.18469903125906464, level=1, ex={'id': 'a2', 'datum': 0.05}),
Circle(x=-0.7387961250362587, y=0.0, r=0.2612038749637415, level=1, ex={'id': 'a1', 'datum': 0.1, 'children': [{'id': 'a1_1', 'datum': 0.05}, {'datum': 0.04}, 0.01]}),
Circle(x=0.2612038749637414, y=0.0, r=0.7387961250362586, level=1, ex={'id': 'a0', 'datum': 0.8, 'children': [0.3, 0.2, 0.2, 0.1]}),
Circle(x=-0.7567888163564135, y=0.1408782365133844, r=0.0616618704777984, level=2, ex={'datum': 0.01}),
Circle(x=-0.8766762590444033, y=0.0, r=0.1233237409555968, level=2, ex={'datum': 0.04}),
Circle(x=-0.6154723840806618, y=0.0, r=0.13788013400814464, level=2, ex={'id': 'a1_1', 'datum': 0.05}),
Circle(x=0.6664952237042414, y=0.33692908734605553, r=0.21174557028487648, level=2, ex={'datum': 0.1}),
Circle(x=-0.1128831469183017, y=-0.23039288135707192, r=0.29945345726929773, level=2, ex={'datum': 0.2}),
Circle(x=0.1563193680487183, y=0.304601976765483, r=0.29945345726929773, level=2, ex={'datum': 0.2}),
Circle(x=0.5533243963620487, y=-0.23039288135707192, r=0.3667540860110527, level=2, ex={'datum': 0.3})]
手机扫一扫
移动阅读更方便
你可能感兴趣的文章