Plotly 旭日形图周围的值注释

Posted

技术标签:

【中文标题】Plotly 旭日形图周围的值注释【英文标题】:Value annotations around Plotly sunburst diagram 【发布时间】:2022-01-04 19:21:58 【问题描述】:

在这个取自the docs 的 Plotly sunburst 图示例中,如何将值显示为最外层之外的文本?我想为每个段添加一个注释,显示确定其径向宽度的值。

My actual plots 在最外层有数百个段,因此任何解决方案都应该是全自动的,不需要硬编码值。

import plotly.express as px
df = px.data.tips()
fig = px.sunburst(df, path=['day', 'time', 'sex'], values='total_bill')
fig.show()

【问题讨论】:

【参考方案1】:

我认为 Plotly 旭日形图没有任何内置注释,因此您需要手动添加注释。

在您的情况下,我认为使用 go.Scatter() 和参数 mode='text' 将允许您将注释放置在旭日形图上。这种方法的优点是可以将旭日形图放置在最方便的任何坐标上。

例如,如果您将 x 轴和 y 轴的范围设置为 [-1,1],这将确保旭日形图以 (0,0) 为中心,半径约为 1(这取决于不幸的是,您的浏览器窗口的纵横比)。您可能需要在这些范围上进行一些填充,以确保文本在接近范围的上端或下端时不会被截断。

然后你可以使用极坐标根据r和theta来确定x和y坐标。所以如果你想把注解"1227"放在45度角,那么设置x=r*cos(45˚)y=r*sin(45˚).,然后把你想放的所有注解都重复这个过程。

更新:虽然 Plotly 以正确的顺序呈现带有类别的旭日形图,但似乎这些信息并没有存储在可访问的对象中,这让我们的任务是确定类别的顺序及其各自角度我们自己。

对于 Plotly sunburst 图表,其父类别中的类别(daytimesex)的总和决定了它们在图表上从 0 度开始的放置顺序。例如,total_tips 类别 day 的总和最大的是 Sat,其次是 Sun, Thur, Fri,这是这些类别及其值在图表上的排列顺序。对于父类别中的子类别,同样的模式适用:例如,Sat/Dinner/Maletotal_tips 之和大于 Sat/Dinner/Female,因此 Sat/Dinner/Male 对应的值放在 Sat/Dinner 之前/女。

我们可以使用groupbysort_values 的组合重现此排序:

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from math import sin,cos,pi

df = px.data.tips()
fig = px.sunburst(df, path=['day', 'time', 'sex'], values='total_bill')

totals_groupby =  df.groupby(['day', 'time', 'sex']).sum()
totals_groupby["day_sum"] = df.groupby(['day', 'time', 'sex']).total_bill.sum().groupby(level='day').transform('sum')
totals_groupby["day_time_sum"] = df.groupby(['day', 'time', 'sex']).total_bill.sum().groupby(level=['day','time']).transform('sum')
totals_groupby["day_time_sex_sum"] = df.groupby(['day', 'time', 'sex']).total_bill.sum().groupby(level=['day','time','sex']).transform('sum')
totals_groupby = totals_groupby.sort_values(by=["day_sum","day_time_sum","day_time_sex_sum"], ascending=[0,0,0])

下面是totals_groupby DataFrame,我们在其中复制了与 Plotly express sunburst 图表相同的类别顺序:

>>> totals_groupby
                    total_bill     tip  size  day_sum  day_time_sum  day_time_sex_sum
day  time   sex                                                                      
Sat  Dinner Male       1227.35  181.95   156  1778.40       1778.40           1227.35
            Female      551.05   78.45    63  1778.40       1778.40            551.05
Sun  Dinner Male       1269.46  186.78   163  1627.16       1627.16           1269.46
            Female      357.70   60.61    53  1627.16       1627.16            357.70
Thur Lunch  Male        561.44   89.41    73  1096.33       1077.55            561.44
            Female      516.11   79.42    77  1096.33       1077.55            516.11
     Dinner Female       18.78    3.00     2  1096.33         18.78             18.78
Fri  Dinner Male        164.41   21.23    16   325.88        235.96            164.41
            Female       71.55   14.05    10   325.88        235.96             71.55
     Lunch  Female       55.76   10.98     9   325.88         89.92             55.76
            Male         34.16    5.70     5   325.88         89.92             34.16

我们想要的注解是totals_groupbytotal_bill列中的值,其顺序与plotly.express sunburst图对应。

然后我们可以通过将total_bill 列除以total_bill 的总数并乘以360 以度数为单位来计算每个类别的角度对向。请注意,这不是我们要放置注释的最终角度:要获得它,我们需要从 0 开始对这些角度进行滚动平均。

annotations = [format(v,".0f") for v in totals_groupby.total_bill.values]

## calculate the angle subtended by each category
sum_total_bill = df.total_bill.sum()
delta_angles = 360*totals_groupby["total_bill"] / sum_total_bill

## calculate cumulative sum starting from 0, then take a rolling mean 
## to get the angle where the annotations should go
angles_in_degrees = pd.concat([pd.DataFrame(data=[0]),delta_angles]).cumsum().rolling(window=2).mean().dropna().values

>>> annotations
['1227', '551', '1269', '358', '561', '516', '19', '164', '72', '56', '34']
>>> list(angles_in_degrees[:,0])
[45.76087924652581, 112.06726915325291, 179.94370071482274, 240.6112138730718, 274.8807006133266, 315.0563924959142, 334.9993889518348, 341.82949891979104, 350.6271011253642, 355.3737646988153, 358.726368488971]

现在我们可以使用辅助函数将所有这些信息放在旭日图上,将角度转换为 x,y 坐标。

def get_xy_coordinates(angles_in_degrees, r=1):
    return [r*cos(angle*pi/180) for angle in angles_in_degrees], [r*sin(angle*pi/180) for angle in angles_in_degrees]

x_coordinates, y_coordinates = get_xy_coordinates(angles_in_degrees, r=1.13)
fig.add_trace(go.Scatter(
    x=x_coordinates,
    y=y_coordinates,
    mode="text",
    text=annotations,
    hoverinfo="skip",
    textfont=dict(size=14)
))

padding = 0.20
fig.update_layout(
    xaxis=dict(
        range=[-1 - padding, 1 + padding], 
        showticklabels=False
    ), 
    yaxis=dict(
        range=[-1 - padding, 1 + padding],
        showticklabels=False
    ),
    plot_bgcolor='rgba(0,0,0,0)'
)

fig.show()

【讨论】:

我正在编写一个绘图函数,所以我没有对任何角度或排序顺序进行硬编码的奢侈。希望会有另一种更自动化的方法。但是感谢您展示了解决方案中的所有部分! 是的,我完全理解不想对解决方案的任何部分进行硬编码。在这种情况下,您需要更深入地研究如何在幕后构建旭日图。也许有一个公式可以用来根据父级、ID 和相对值的层次结构确定角度或排序顺序 - 如果我想到什么,我会更新我的答案 我已经更新了我的答案!类别顺序和角度可以从数据中确定而无需硬编码......希望这更接近您正在寻找的内容 酷!使用plotly.graph_objects.Scatterpolar 跟踪类型而不是转换回笛卡尔坐标不是更容易吗? 哦,这是一个很好的观点 - 这绝对可以让事情变得更容易

以上是关于Plotly 旭日形图周围的值注释的主要内容,如果未能解决你的问题,请参考以下文章

如何实现条形图的日期范围

我可以使用 plotly 子图在一个图形对象中显示不同的图表类型,例如雷达、旭日形、甜甜圈吗

可视化神器Plotly玩转箱形图

R语言动态交互绘图|plotly包-交互式柱形图

是否有可能在R中使用plotly创建单个堆叠的柱形图

更改 Plotly 树图中的边框宽度