Plotly:如何使用 pandas 数据框定义 sankey 图的结构?

Posted

技术标签:

【中文标题】Plotly:如何使用 pandas 数据框定义 sankey 图的结构?【英文标题】:Plotly: How to define the structure of a sankey diagram using a pandas dataframe? 【发布时间】:2019-08-13 13:00:57 【问题描述】:

这听起来可能是一个非常广泛的问题,但如果您让我描述一些细节,我可以向您保证它非常具体。以及令人沮丧、沮丧和激怒的情绪。


以下情节描述了一场苏格兰选举,并基于来自plot.ly 的代码:

情节 1:

数据集 1:

data = [['Source','Target','Value','Color','Node, Label','Link Color'],
        [0,5,20,'#F27420','Remain+No – 28','rgba(253, 227, 212, 0.5)'],
        [0,6,3,'#4994CE','Leave+No – 16','rgba(242, 116, 32, 1)'],
        [0,7,5,'#FABC13','Remain+Yes – 21','rgba(253, 227, 212, 0.5)'],
        [1,5,14,'#7FC241','Leave+Yes – 14','rgba(219, 233, 246, 0.5)'],
        [1,6,1,'#D3D3D3','Didn’t vote in at least one referendum – 21','rgba(73, 148, 206, 1)'],
        [1,7,1,'#8A5988','46 – No','rgba(219, 233, 246,0.5)'],
        [2,5,3,'#449E9E','39 – Yes','rgba(250, 188, 19, 1)'],
        [2,6,17,'#D3D3D3','14 – Don’t know / would not vote','rgba(250, 188, 19, 0.5)'],
        [2,7,2,'','','rgba(250, 188, 19, 0.5)'],
        [3,5,3,'','','rgba(127, 194, 65, 1)'],
        [3,6,9,'','','rgba(127, 194, 65, 0.5)'],
        [3,7,2,'','','rgba(127, 194, 65, 0.5)'],
        [4,5,5,'','','rgba(211, 211, 211, 0.5)'],
        [4,6,9,'','','rgba(211, 211, 211, 0.5)'],
        [4,7,8,'','','rgba(211, 211, 211, 0.5)']
        ]

情节是如何构建的:

我从各种来源收集了一些关于桑基图行为的重要细节,例如:

Sankey automatically orders the categories to minimize the amount of overlap

Links are assigned in the order they appear in dataset (row_wise)

For the nodes colors are assigned in the order plot is built.

挑战:

正如您将在下面的详细信息中看到的那样,节点、标签和颜色不会以与源数据框的结构相同的顺序应用于图表。 其中一些 是完美的,因为您有各种元素来描述相同的节点,如颜色、目标、值和链接颜色。一个节点'Remain+No – 28' 如下所示:

数据集的随附部分如下所示:

[0,5,20,'#F27420','Remain+No – 28','rgba(253, 227, 212, 0.5)'],
[0,6,3,'#4994CE','Leave+No – 16','rgba(242, 116, 32, 1)'],
[0,7,5,'#FABC13','Remain+Yes – 21','rgba(253, 227, 212, 0.5)'],

所以这部分源代码描述了一个节点[0],具有三个对应的目标[5, 6, 7]和三个值为[20, 3, 5]的链接。 '#F27420' 是节点的橙色(ish)颜色,颜色 'rgba(253, 227, 212, 0.5)''rgba(242, 116, 32, 1)''rgba(253, 227, 212, 0.5)' 描述了从节点到某些目标的链接的颜色。到目前为止,上面示例中没有用到的信息是:

数据样本 2(部分)

[-,-,--'-------','---------------','-------------------'],
[-,-,-,'#4994CE','Leave+No – 16','-------------------'],
[-,-,-,'#FABC13','Remain+Yes – 21','-------------------'],

并且该信息被用作图表的其余元素被引入。

那么,问题是什么?在下面的详细信息中,您将看到,只要数据集中的新数据行插入新链接,并对其他元素(颜色、标签)进行其他更改(如果尚未使用该信息),一切都是有意义的.我将更具体地使用我使用左侧绘图和右侧代码制作的设置中的两个屏幕截图:

以下数据样本按照上面描述的逻辑生成下图:

数据样本 3

data = [['Source','Target','Value','Color','Node, Label','Link Color'],
        [0,5,20,'#F27420','Remain+No – 28','rgba(253, 227, 212, 0.5)'],
        [0,6,3,'#4994CE','Leave+No – 16','rgba(242, 116, 32, 1)'],
        [0,7,5,'#FABC13','Remain+Yes – 21','rgba(253, 227, 212, 0.5)'],
        [1,5,14,'#7FC241','Leave+Yes – 14','rgba(219, 233, 246, 0.5)'],
        [1,6,1,'#D3D3D3','Didn’t vote in at least one referendum – 21','rgba(73, 148, 206, 1)']]

屏幕截图 1 - 带有数据样本 3 的部分图

问题:

在数据集中添加行 [1,7,1,'#8A5988','46 – No','rgba(219, 233, 246,0.5)'] 会在源 [5] 和目标 [7] 之间生成一个新链接,但同时将颜色和标签应用于目标 5。我认为要应用于图表的下一个标签是'Remain+Yes – 21',因为它尚未使用。但这里发生的情况是标签 '46 – No' 应用于目标 5。为什么?

屏幕截图 2 - 带有数据样本 3 的部分图 + [1,7,1,'#8A5988','46 – No','rgba(219, 233, 246,0.5)'] :

您如何根据该数据框辨别什么是源,什么是目标?

我知道这个问题既奇怪又难以回答,但我希望有人能提出建议。我也知道数据框可能不是 sankey 图表的最佳来源。也许用 json 代替?


为 Jupyter Notebook 轻松复制和粘贴的完整代码和数据示例:


import pandas as pd
import numpy as np
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
init_notebook_mode(connected=True)

# Original data
data = [['Source','Target','Value','Color','Node, Label','Link Color'],
    [0,5,20,'#F27420','Remain+No – 28','rgba(253, 227, 212, 0.5)'],
    [0,6,3,'#4994CE','Leave+No – 16','rgba(242, 116, 32, 1)'],
    [0,7,5,'#FABC13','Remain+Yes – 21','rgba(253, 227, 212, 0.5)'],
    [1,5,14,'#7FC241','Leave+Yes – 14','rgba(219, 233, 246, 0.5)'],
    [1,6,1,'#D3D3D3','Didn’t vote in at least one referendum – 21','rgba(73, 148, 206, 1)'],
    [1,7,1,'#8A5988','46 – No','rgba(219, 233, 246,0.5)'],
    [2,5,3,'#449E9E','39 – Yes','rgba(250, 188, 19, 1)'],
    [2,6,17,'#D3D3D3','14 – Don’t know / would not vote','rgba(250, 188, 19, 0.5)'],
    [2,7,2,'','','rgba(250, 188, 19, 0.5)'],
    [3,5,3,'','','rgba(127, 194, 65, 1)'],
    [3,6,9,'','','rgba(127, 194, 65, 0.5)'],
    [3,7,2,'','','rgba(127, 194, 65, 0.5)'],
    [4,5,5,'','','rgba(211, 211, 211, 0.5)'],
    [4,6,9,'','','rgba(211, 211, 211, 0.5)'],
    [4,7,8,'','','rgba(211, 211, 211, 0.5)']
    ]



headers = data.pop(0)
df = pd.DataFrame(data, columns = headers)
scottish_df = df

data_trace = dict(
    type='sankey',
    domain = dict(
      x =  [0,1],
      y =  [0,1]
    ),
    orientation = "h",
    valueformat = ".0f",
    node = dict(
      pad = 10,
      thickness = 30,
      line = dict(
        color = "black",
        width = 0
      ),
      label =  scottish_df['Node, Label'].dropna(axis=0, how='any'),
      color = scottish_df['Color']
    ),
    link = dict(
      source = scottish_df['Source'].dropna(axis=0, how='any'),
      target = scottish_df['Target'].dropna(axis=0, how='any'),
      value = scottish_df['Value'].dropna(axis=0, how='any'),
      color = scottish_df['Link Color'].dropna(axis=0, how='any'),
  )
)

layout =  dict(
    title = "Scottish Referendum Voters who now want Independence",
    height = 772,
    font = dict(
      size = 10
    ),    
)

fig = dict(data=[data_trace], layout=layout)
iplot(fig, validate=False)

【问题讨论】:

【参考方案1】:

这个问题看起来很奇怪,但只有在您分析plotly 中的 sankey 图是如何创建的:

当您创建 sankey 图时,您发送给它:

    节点列表 链接列表

这些列表彼此有界。当您创建 5 长度的节点列表时,任何边都会知道 0,1,2,3,4 的开始和结束。在您的程序中,您错误地创建了节点 - 您创建了链接列表,然后通过它并创建节点。看你的图。它有两个黑色节点,里面有undefined。你的数据集的长度是多少……是的,5。您的节点索引以4 结束,并且没有真正定义任何目标节点。您在数据集中添加第六个列表 - 宾果游戏! - 有nodes[5] 存在!只需尝试在您的数据集中添加另一个新行:

[1,7,1,'#FF0000','WAKA','rgba(219, 233, 246,0.5)']

你会看到另一个黑条被染成了红色。您有五个节点(因为您有 5 个链接,并且您通过迭代链接列表来创建节点),但链接目标索引是 5,6,7。您可以通过两种方式修复它:

    将数据集中的 Target 更改为 2,3,4 分别创建节点和链接(正确的方式)

我希望我能帮助你解决你的问题和情节创作的理解(更重要的是 IMO)。

编辑: 以下是创建单独节点/链接的示例(请注意,data_trace 中的 node 部分仅使用 nodes_df 数据,link 部分中的 data_trace 仅使用links_df数据和nodes_dflinks_df长度不相等):

import pandas as pd
import numpy as np
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
init_notebook_mode(connected=True)

nodes = [
    ['ID', 'Label', 'Color'],
    [0,'Remain+No – 28','#F27420'],
    [1,'Leave+No – 16','#4994CE'],
    [2,'Remain+Yes – 21','#FABC13'],
    [3,'Leave+Yes – 14','#7FC241'],
    [4,'Didn’t vote in at least one referendum – 21','#D3D3D3'],
    [5,'46 – No','#8A5988']
]
links = [
    ['Source','Target','Value','Link Color'],
    [0,3,20,'rgba(253, 227, 212, 0.5)'],
    [0,4,3,'rgba(242, 116, 32, 1)'],
    [0,2,5,'rgba(253, 227, 212, 0.5)'],
    [1,5,14,'rgba(219, 233, 246, 0.5)'],
    [1,3,1,'rgba(73, 148, 206, 1)'],
    [1,4,1,'rgba(219, 233, 246,0.5)'],
    [1,2,10,'rgba(8, 233, 246,0.5)'],
    [1,3,5,'rgba(219, 77, 246,0.5)'],
    [1,5,12,'rgba(219, 4, 246,0.5)']
]

nodes_headers = nodes.pop(0)
nodes_df = pd.DataFrame(nodes, columns = nodes_headers)
links_headers = links.pop(0)
links_df = pd.DataFrame(links, columns = links_headers)

data_trace = dict(
    type='sankey',
    domain = dict(
      x =  [0,1],
      y =  [0,1]
    ),
    orientation = "h",
    valueformat = ".0f",
    node = dict(
      pad = 10,
      thickness = 30,
      line = dict(
        color = "black",
        width = 0
      ),
      label =  nodes_df['Label'].dropna(axis=0, how='any'),
      color = nodes_df['Color']
    ),
    link = dict(
      source = links_df['Source'].dropna(axis=0, how='any'),
      target = links_df['Target'].dropna(axis=0, how='any'),
      value = links_df['Value'].dropna(axis=0, how='any'),
      color = links_df['Link Color'].dropna(axis=0, how='any'),
  )
)

layout =  dict(
    title = "Scottish Referendum Voters who now want Independence",
    height = 772,
    font = dict(
      size = 10
    ),    
)

fig = dict(data=[data_trace], layout=layout)
iplot(fig, validate=False)

编辑 2: 让我们更深入地研究:) 桑基图中的节点和链接几乎是完全独立的。限制它们的唯一信息 - 链接中源目标中的索引。所以我们可以为它们创建许多节点并且没有链接(只需用它替换 Edit1 代码中的节点/链接):

nodes = [
    ['ID', 'Label', 'Color'],
    [0,'Remain+No – 28','#F27420'],
    [1,'Leave+No – 16','#4994CE'],
    [2,'Remain+Yes – 21','#FABC13'],
    [3,'Leave+Yes – 14','#7FC241'],
    [4,'Didn’t vote in at least one referendum – 21','#D3D3D3'],
    [5,'46 – No','#8A5988'],
    [6,'WAKA1','#8A5988'],
    [7,'WAKA2','#8A5988'],
    [8,'WAKA3','#8A5988'],
    [9,'WAKA4','#8A5988'],
    [10,'WAKA5','#8A5988'],
    [11,'WAKA6','#8A5988'],

]
links = [
    ['Source','Target','Value','Link Color'],
    [0,3,20,'rgba(253, 227, 212, 0.5)'],
    [0,4,3,'rgba(242, 116, 32, 1)'],
    [0,2,5,'rgba(253, 227, 212, 0.5)'],
    [1,5,14,'rgba(219, 233, 246, 0.5)'],
    [1,3,1,'rgba(73, 148, 206, 1)'],
    [1,4,1,'rgba(219, 233, 246,0.5)'],
    [1,2,10,'rgba(8, 233, 246,0.5)'],
    [1,3,5,'rgba(219, 77, 246,0.5)'],
    [1,5,12,'rgba(219, 4, 246,0.5)']
]

并且这些节点不会出现在图表中。

我们只能创建没有节点的链接:

nodes = [
    ['ID', 'Label', 'Color'],
]
links = [
    ['Source','Target','Value','Link Color'],
    [0,3,20,'rgba(253, 227, 212, 0.5)'],
    [0,4,3,'rgba(242, 116, 32, 1)'],
    [0,2,5,'rgba(253, 227, 212, 0.5)'],
    [1,5,14,'rgba(219, 233, 246, 0.5)'],
    [1,3,1,'rgba(73, 148, 206, 1)'],
    [1,4,1,'rgba(219, 233, 246,0.5)'],
    [1,2,10,'rgba(8, 233, 246,0.5)'],
    [1,3,5,'rgba(219, 77, 246,0.5)'],
    [1,5,12,'rgba(219, 4, 246,0.5)']
]

我们将只有从无到有的链接。

如果你想添加(1)一个带有链接的新来源,你应该在nodes中添加一个新列表,计算它的索引(这就是我有ID列的原因)并添加links 中的新列表,Source 等于节点索引。

如果您想为现有节点添加 (2) 新目标 - 只需在 links 中添加一个新列表并正确写入其 SourceTarget

    [1,100500,10,'rgba(219, 233, 246,0.5)'],
    [1,100501,10,'rgba(8, 233, 246,0.5)'],
    [1,100502,10,'rgba(219, 77, 246,0.5)'],
    [1,100503,10,'rgba(219, 4, 246,0.5)']

(这里我为 4 个新目标创建了 4 个新链接。源是所有这些目标的索引为 1 的节点)。

(3+4):桑基图不区分来源和目标。所有这些都只是 Sankey 的节点。每个节点既可以是源也可以是目标。看看吧:

nodes = [
    ['ID', 'Label', 'Color'],
    [0,'WAKA WANNA BE SOURCE','#F27420'],
    [1,'WAKA WANNA BE TARGET','#4994CE'],
    [2,'WAKA DON\'T KNOW WHO WANNA BE','#FABC13'],

]
links = [
    ['Source','Target','Value','Link Color'],
    [0,1,10,'rgba(253, 227, 212, 1)'],
    [0,2,10,'rgba(242, 116, 32, 1)'],
    [2,1,10,'rgba(253, 227, 212, 1)'],
]

在这里,您将看到 3 列的桑基图。 0 节点是源,1 是目标,2 节点是 1 的源,并且2 的目标。

【讨论】:

感谢您的回答!你介意展示一下你会怎么做2. Create nodes and links separately (right way)吗? 添加了整个脚本。您可以将其复制粘贴到 Jupyter 笔记本中并检查。 酷!那么,您将如何 (1) 添加一个具有一个或多个链接的源? (2) 再增加一个目标? (3) 添加一个节点和一个目标? (4) 添加一个既是目标又是源的节点?我希望您不会介意后续跟进,但如果您也没有时间,我完全理解您。 再次更新:)

以上是关于Plotly:如何使用 pandas 数据框定义 sankey 图的结构?的主要内容,如果未能解决你的问题,请参考以下文章

Plotly:如何使用长格式或宽格式的 pandas 数据框制作线图?

如何使用 plotly express 创建线图,其中可以通过下拉菜单选择 pandas 数据框?

如何使用 Plotly 在 Python 中使用 Pandas 数据框列设置散点图悬停信息

Plotly:如何在绘图线图中的特定点添加标记(python / pandas)

Plotly:如何使用 go.Bar 为组指定颜色?

Pandas、matplotlib 和 plotly - 如何修复系列图例?