美文网首页
用Tableau画嵌套气泡图

用Tableau画嵌套气泡图

作者: 扫地sir | 来源:发表于2021-10-10 22:01 被阅读0次

最近研究CJ Mayes博客里的一篇文章《Don’t Burst My Bubble》,里面介绍了circular packing的做法。

https://cj-mayes.com/2021/09/21/dont-burst-my-bubble/

经过一段时间的研究,源代码只包含生成图形的部分,我对源代码进行了一些修改,增加了数据处理过程,用Tableau画圆的部分也采用目前自己最喜欢的并集+表计算的方式。

由于涉及代码和tableau画圆的知识,属于高级图形的范畴,所以本文不做过多原理的讲解。

数据处理

原文直接使用的是json格式的数据。

这种数据我们不好获取,正常情况下,我们excel里的数据是这样存储的。

所以需要通过python转成有层次的json格式,代码如下:

import pandas as pd
import json
df=pd.read_csv('test.csv',encoding='gb2312')

Level1_sum=df.groupby('Level1').sum('value').reset_index()
Level1_sum.rename(columns={'Level1':'Lebel'},inplace=True)
concat_sum=pd.concat([Level1_sum])
dic_Level1=[]
for x in range(Level1_sum.shape[0]):
    Level2_sum=df[df.Level1==Level1_sum.Lebel[x]].groupby('Level2').sum('value').reset_index()
    Level2_sum.rename(columns={'Level2':'Lebel'},inplace=True)
    concat_sum=pd.concat([concat_sum,Level2_sum])
    dic_Level2=[]
    for y in range(Level2_sum.shape[0]):
        Level3_sum=df[df.Level2==Level2_sum.Lebel[y]].groupby('Level3').sum('value').reset_index()
        Level3_sum.rename(columns={'Level3':'Lebel'},inplace=True)
        concat_sum=pd.concat([concat_sum,Level3_sum])
        dic_Level3=[]
        for z in range(Level3_sum.shape[0]):
            dic_Level3.append(dict(id=Level3_sum.Lebel[z] ,datum=Level3_sum.value[z]))
        dic_Level2.append(dict(id=Level2_sum.Lebel[y] ,datum=Level2_sum.value[y],children=dic_Level3))
    dic_Level1.append(dict(id=Level1_sum.Lebel[x] ,datum=Level1_sum.value[x],children=dic_Level2))

#生成json
data=json.dumps(eval(str(dic_Level1))) 
#第二部分输出没有value值,增加一个输出文件,方便tableau里添加标签
concat_sum.reset_index(drop=True).to_csv('test_label.csv',encoding='utf-8-sig')  

生成图形

首先要安装circlify库(请自行百度),代码如下:(我对源代码进行了些许修改)

import csv
import circlify
import matplotlib.pyplot as plt

# 将第一部分的json数据赋值给data
data=json.loads(data)

# Compute circle positions thanks to the circlify() function
# The maximum radius is set to 1.
circles = circlify.circlify(
    data,
    show_enclosure=False,
    target_enclosure=circlify.Circle(x=0, y=0, r=1)
)

# To ensure in Tableau the sizing works the fig size is the same x*y.
fig, ax = plt.subplots(figsize=(10,10))

# Add axes
ax.axis('on')

# Find axis boundaries, lim will be 1.0 with current settings
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)

# Amend Radius zone, Make sure Inner pad always > than Outer pad
padding_outer = 1
padding_inner = 1

header = ['ID', 'X co-ord', 'Y-cord', 'Radius', 'Rank',"Level"]
rank = 1

with open('test_result.csv', 'w',newline="") as f:
    writer = csv.writer(f)
    writer.writerow(header)

    # World Level
    for circle in circles:
        if circle.level != 1:
            continue
        x, y, r = circle
        level =1
        label = circle.ex["id"]
        #print(label,x,y,r,level)
        printdata = [label, x, y, r, rank,level]
        rank = rank + 1
        writer.writerow(printdata)

        # Not needed for Tableau
        ax.add_patch(plt.Circle((x, y), r*padding_outer, alpha=0.5, linewidth=2, color="lightblue"))

        # Continent Level
        for circle in circles:
            if circle.level != 2:
                continue
            x, y, r = circle
            level =2
            label = circle.ex["id"]
            #print(label,x,y,r,level)
            printdata = [label, x, y, r, rank,level]
            rank = rank + 1
            writer.writerow(printdata)

            # Not needed for Tableau
            ax.add_patch(plt.Circle((x, y), r*padding_outer, alpha=0.5, linewidth=2, color="lightblue"))

        # Country Level 
        for circle in circles:
            if circle.level != 3:
                continue
            x, y, r = circle
            level =3
            label = circle.ex["id"]
            #print(label,x,y,r)
            printdata = [label, x, y, r, rank,level]
            rank = rank + 1
            writer.writerow(printdata)

            # Not needed for Tableau
            ax.add_patch(plt.Circle((x, y), r*padding_inner, alpha=0.5, linewidth=2, color="#69b3a2"))
            plt.annotate(label, (x,y), ha='center', color="white")

# Show The Example Of The Graph We Will Be Recreating!
plt.show()

python里生成的图形如下:

同时生成了一个test_result.csv的文件。文件记录了圆形的ID,原点的x、y坐标和半径r。

制作图形

导入test_result.csv文件,做一次并集。

创建计算字段

  • 新建计算字段path
    IF [表名称]='test_result.csv' then 0 ELSE 360 END

  • 创建path数据桶,数据桶大小是1

  • 新建计算字段index
    index()

  • 创建计算字段X
    SIN(2*PI()*[index]/360)*WINDOW_MAX(MAX([Radius]))
    +WINDOW_MAX(MAX([X co-ord]))

  • 创建计算字段Y
    COS(2*PI()*[index]/360)*WINDOW_MAX(MAX([Radius]))
    +WINDOW_MAX(MAX([Y-cord]))

  • 检查path字段“显示了缺失值”是否已经勾选

  • 标记选择线,将ID拖到详细信息,path数据桶拖到路径,Level放到颜色上

  • 将X、Y分别放到行、列功能区,计算依据选择path数据桶

如果得到了上面的图形,就证明没有任何问题,我在利用其他数据源的时候,出现过ID字段重名的情况,用Rank字段作为区分也没有问题,不过建议还是处理一下数据,保证ID值唯一。

最终,我们使用多边形,并修改好颜色就是我们想要的结果了。如果需要增加value值的提示,可以用生成的test_label.csv文件做关联。

至于图形的其他玩法,比如嵌套更多层次的圆形,那么就需要你自己去探索了,可能需要修改代码。

另外画圆的原理,我推荐我自己在cctalk上的课程《Tableau高级图表进阶》,第三部分第一课(免费)。

此篇文章已发布到我的公众号: saodisir,有兴趣也可关注一下

相关文章

网友评论

      本文标题:用Tableau画嵌套气泡图

      本文链接:https://www.haomeiwen.com/subject/qbyqoltx.html