美文网首页
用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