我们如何在桑基图中格式化数字并在图表外设置标签?

How can we format numbers in a Sankey chart and set labels outside of the chart?

我有一些简单的代码可以生成漂亮的桑基图。

import holoviews as hv
import plotly.graph_objects as go
import plotly.express as pex
hv.extension('bokeh')


sankey1 = hv.Sankey(df_final, kdims=['Sub_Market', 'Sport League'], vdims=["Revenue"])
hv.Sankey(sankey1)

sankey1.opts(cmap='Colorblind',label_position='right',
                                 edge_color='Sub_Market', edge_line_width=0,
                                 node_alpha=1.0, node_width=40, node_sort=True,
                                 width=800, height=600, bgcolor="snow",
                                 title="Flow of Revenue between Sub Market and Conference")

不幸的是,数字呈指数增长。我真的想让它们以百万为单位显示。还有,有没有办法让右边的标签显示在右边,同时让左边的标签显示在左边,这样都在图表之外,更容易阅读?

感谢您的宝贵时间!

下面的解决方案适用于 holoviews,但(可能)对 plotly 无效。

holoviews 中,您可以添加 hv.Dimension(spec, **params),这样您就可以将带有关键字 value_format 的格式化程序应用于列名。此格式化程序可以预定义或定义创建。下面的示例显示了如何通过自定义 python 函数定义简单的格式化程序。

示例代码

import holoviews as hv
import pandas as pd

data = {'A':['XX','XY','YY','XY','XX','XX'],
        'B':['RR','KK','KK','RR','RK','KK'],
        'values':[1e6,5e5,8e4,15e3,19e2,1],
       }

df = pd.DataFrame(data)

def fmt(tick):
    if tick < 1e3:
        unit = ''
        num =  round(tick,2)
    elif tick < 1e6:
        unit = 'k'
        num =  round(tick/1e3,2)
    else:
        unit = 'm'
        num =  round(tick/1e6,2)
    return f'{num} {unit}'


hv.Sankey(df, vdims = hv.Dimension('values', value_format=fmt))

输出

首先,holoview 允许为维度配置自定义格式化程序。

要按原样呈现数字,您可以使用 str 函数作为维度的格式化程序。

我使用了一个示例数据框来展示如何实现这一点的示例。你可以 运行 它在 this runnable collab notebook.

import holoviews as hv
from holoviews.core import Store
import pandas as pd

hv.ipython.notebook_extension('bokeh')

Store.set_current_backend('bokeh')
renderer = Store.renderers['bokeh']

df_final = pd.DataFrame({
    'Sub_Market': ['Central texas', 'Southern California', 'Florida'],
    'Sport League': ['MLS', 'NBA', 'MLS'],
    'Revenue': [1.4981211 * 10**5, 2.921212* 10**6, 1.2121112*10**6]
})

graph = hv.Sankey(
    df_final, 
    kdims=['Sub_Market', 'Sport League'],
    vdims=[hv.Dimension("Revenue", value_format=str)],
)

现在要自定义标签的位置,您需要渲染图。

这里我们使用 bokeh 作为后端,可以通过将图形对象作为参数转发给 bokeh 渲染器的 get_plot 方法来获取绘图。

renderer = Store.renderers['bokeh']
plot = renderer.get_plot(graph)

现在,我们可以访问我们希望自定义的绘图句柄。 应用于所有标签的默认 x_offset 值为 0。 我们只需要在左侧标签上应用偏移量。

为此,我们扩充标签的数据源以包含一个 'x_offset' 字段,并为我们希望放置在四边形左侧的标签设置偏移量。

此外,我们需要设置plot.xrange的起点,这样剧情就不会被截断。

offset = -200
num_nodes = len(plot.handles['text_1_source'].data['x'])
plot.handles['text_1_source'].data['x_offset'] = [0]* num_nodes
num_left_nodes = 3
left_nodes_selection = slice(0, num_left_nodes)
plot.handles['text_1_source'].data['x_offset'][left_nodes_selection] = [offset]* num_left_nodes
plot.handles['text_1_glyph'].x_offset = {'field': 'x_offset' }
plot.handles['plot'].x_range.start += (2*offset)

最后,我们可以将绘图渲染为 SVG 组件并将其显示在笔记本中。

hv.ipython.notebook_extension('bokeh')
data, metadata = hv.ipython.display_hooks.render(plot, fmt='svg')
hv.ipython.display(hv.ipython.HTML(data["text/html"]))