铁门关市网站建设_网站建设公司_UI设计师_seo优化
2025/12/19 18:21:04 网站建设 项目流程

原文:towardsdatascience.com/graph-visualization-7-steps-from-easy-to-advanced-4f5d24e18056

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f11219b4d9364394a30b8524c3756a4f.png

Davis 的南方俱乐部图,图片由作者提供

一些数据类型,如社交网络或知识图谱,可以“原生”地以图形形式表示。这种数据的可视化可能具有挑战性,而且没有通用的配方。在这篇文章中,我将展示使用开源NetworkX库进行图形可视化的几个步骤。

让我们开始吧!

基本示例

如果我们想在 Python 中使用图表,NetworkX 可能是最受欢迎的选择。它是一个用于网络分析的开放源代码 Python 包,包括不同的算法和强大的功能。正如我们所知,每个图都包含节点(顶点)和它们之间的关系;我们可以在 NetworkX 中轻松创建一个简单的图:

importnetworkxasnx G=nx.Graph()G.add_node("A")G.add_node("B")G.add_edge("A","B")...

然而,以这种方式创建一个大型图可能会很累人,在这篇文章中,我将使用 NetworkX 库中包含的“Davis 的南方俱乐部女性”图(3-clause BSD 许可)。这些数据由 A. Davis 等人于 20 世纪 30 年代收集(A. Davis,1941 年,《深南》,芝加哥:芝加哥大学出版社)。它代表了 18 位南方女性参加 14 个社交活动的观察结果。让我们加载这个图并绘制它:

importnetworkxasnximportmatplotlib.pyplotasplt G=nx.davis_southern_women_graph()fig1=plt.figure(figsize=(12,8))nx.draw(G,with_labels=True)plt.show()

结果看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b5c7fb53c0265292a3e9bed3a1e88c1e.png

Davis 的南方俱乐部女性图,图片由作者提供

它是可行的,但这张图片肯定可以改进。让我们看看不同的方法来实现它。

1. 布局

根据定义,一个图本身只包含节点和它们之间的关系;它没有任何坐标。同一个图可以用许多不同的方式显示,NetworkX 中也有不同的布局可供选择。没有一种通用的解决方案适合所有情况,视觉印象也可能具有主观性。最好的方法是尝试不同的选项,找到更适合特定数据集的图像。

螺旋布局这种布局可以通过使用spiral_layout方法生成:

pos=nx.spiral_layout(G)print(pos)#> {'Evelyn Jefferson': array([-0.51048124, 0.00953613]),# 'Laura Mandeville': array([-0.59223481, -0.08317364]), ... }nx.draw(G,pos=pos,with_labels=True)

如我们从print输出中可以看到,布局本身只是一个包含坐标的字典。这个布局可以作为draw方法的可选参数指定。结果看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c612617ac081ef4f435ee54b2e0ff81e.png

螺旋布局,图片由作者提供

对于这种类型的图,这并不是最好的选择;让我们尝试其他方法。

圆形布局在这里,代码逻辑是相同的。首先,我们创建一个布局,然后我们在代码中使用它:

pos=nx.circular_layout(G)nx.draw(G,pos=pos,with_labels=True)

结果:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/eba20af5a3705254d27b49cda49c9b52.png

循环布局,图片由作者提供

就像上一个例子一样,圆形布局对于这个图表来说并不是最好的。

Kamada-Kawai 布局此方法使用 Kamada-Kawai 路径长度成本函数:

pos=nx.kamada_kawai_layout(G)nx.draw(G,pos=pos,with_labels=True)

结果看起来更好:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3ec1cb1a5a17fbaa89e815c4eb223588.png

Kamada-Kawai 布局,图片由作者提供

弹簧布局此方法使用 Fruchterman-Reingold 力导向算法,它作为一种“反重力力”,将节点彼此拉远,除非系统达到平衡。

pos=nx.spring_layout(G,seed=42)nx.draw(G,pos=pos,with_labels=True)

主观上,结果看起来最好:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0418362d12fc27548587d65fc97b029b.png

弹簧布局,图片由作者提供

这里的seed参数在我们要得到相同结果时很有用,否则,每次重绘都会产生另一个看起来不同的图表。

其他图形布局类型在 NetworkX 中可用;读者可以自己尝试它们。

2. 节点颜色

作为提醒,我们的图表代表了 18 位参与 14 个社交活动的女性。图表中的所有事件都有“Exx”名称;让我们改变它们的颜色以获得更好的视觉效果。

首先,我将创建一个辅助方法来检测节点是否是事件:

defis_event_node(node:str)->bool:""" Check if events starts with Exx """returnre.match("^Ed",node)isnotNone

在这里,我使用正则表达式来确定节点模式(我的第一次尝试是使用node.startswith("E")方法,但一些女性的名字也可以以"E"开头)。现在,我们可以轻松地为每个节点创建一个颜色数组,并使用它来绘制图表:

defget_node_color(node:str)->str:""" Get color of the individual node """return"#00AA00"ifis_event_node(node)else"#00AAEE"node_colors=[get_node_color(node)fornodeinG.nodes()]nx.draw(G,pos=pos,node_color=node_colors,with_labels=True)

结果看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/cb5bf4a4243256dda16511a47be1eed5.png

节点颜色,图片由作者提供

3. 节点大小

就像颜色一样,我们可以指定每个节点的大小。让我们使“事件”节点更大;节点大小也可以与它的连接数成比例:

edges={node:len(G.edges(node))fornodeinG.nodes()}defnode_size(node:str)->int:""" Get size of the individual node """k=4ifis_event_node(node)else1return100*k+100+50*edges[node]node_sizes=[node_size(node)fornodeinG.nodes()]nx.draw(G,pos=pos,node_color=node_colors,node_size=node_sizes,with_labels=True)

在这里,我将每个节点的边数保存到一个单独的字典中。我还使用了之前相同的is_event_node方法来使“事件”节点更大。

结果看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5e2845cbfd390133ce9d548c3f1794a7.png

不同的节点大小,图片由作者提供

4. 边颜色

我们不仅可以指定节点颜色,还可以指定边颜色。作为一个例子,让我们突出显示 Theresa Anderson 访问的所有事件。为此,我需要三个辅助方法:

highlighted_node="Theresa Anderson"defget_node_color(node:str)->str:""" Get color of the individual node """ifis_event_node(node):ifG.has_edge(node,highlighted_node):return"#00AA00"elifnode==highlighted_node:return"#00AAEE"return"#AAAAAA"defedge_color(node1:str,node2:str)->str:""" Get color of the individual edge """ifnode1==highlighted_nodeornode2==highlighted_node:return"#992222"return"#999999"defedge_weight(node1:str,node2:str)->str:""" Get width of the individual edge """ifnode1==highlighted_nodeornode2==highlighted_node:return3return1

在这里,我为 Theresa Anderson 的节点使用了单独的颜色。我还改变了她访问的所有事件的颜色;has_edge方法是一个简单的方法来查找两个节点是否有共同边。我还改变了边的权重。

现在,我们可以绘制图表:

edge_colors=[edge_color(n1,n2)forn1,n2inG.edges()]edge_weights=[edge_weight(n1,n2)forn1,n2inG.edges()]node_colors=[get_node_color(node)fornodeinG.nodes()]nx.draw(G,pos=pos,node_color=node_colors,node_size=node_sizes,edge_color=edge_colors,width=edge_weights,with_labels=True)

结果看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/00f8d128b2628847c7f1f4db8f2295ed.png

突出显示节点和边的图表,图片由作者提供

5. 节点标签

当我们有一个具有不同节点类型的图时,我们可以为不同的节点使用不同的字体。然而,令我惊讶的是,在 NetworkX 中,没有简单的方法来指定字体,就像我们为颜色所做的那样。为了绘制“事件”和“人物”节点,我们可以将图分成子图并分别绘制:

node_events=[nodefornodeinG.nodes()ifis_event_node(node)]node_people=[nodefornodeinG.nodes()ifnotis_event_node(node)]nx.draw(G,pos=pos,node_color=node_colors,node_size=node_sizes,with_labels=False)nx.draw_networkx_labels(G.subgraph(node_events),pos=pos,font_weight="bold")nx.draw_networkx_labels(G.subgraph(node_people),pos=pos,font_weight="normal",font_size=11)

在这里,我首先像以前一样绘制节点,但将with_labels参数设置为 False。然后我使用了两次draw_networkx_labels方法,并设置了不同的字体设置。

输出看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d1f171ea9877f381990f7d3574a724b6.png

具有不同标签的图,图片由作者提供

6. 节点属性

我们能够使用辅助 Python 方法设置节点颜色和大小。然而,使用节点属性,我们也可以将此信息保存到图中:

colors_dict={node:get_node_color(node)fornodeinG.nodes()}nx.set_node_attributes(G,colors_dict,"color")

我们也可以为一些节点手动指定属性:

custom_colors_dict={"Frances Anderson":"orange","Theresa Anderson":"orange","E3":"darkgreen","E5":"darkgreen","E6":"darkgreen"}nx.set_node_attributes(G,custom_colors_dict,"color")

然后,我们可以将图保存到文件中,所有信息都将被保留:

nx.write_gml(G,"davis_southern_women.gml")

然后,我们可以加载图并从节点属性中提取所有颜色;我们不再需要任何辅助方法:

attributes=nx.get_node_attributes(G,"color")node_color_attrs=[attributes[node]fornodeinG.nodes()]nx.draw(G,pos=pos,node_color=node_color_attrs,node_size=node_sizes,with_labels=False)nx.draw_networkx_labels(G.subgraph(node_events),pos=pos,font_weight="bold")nx.draw_networkx_labels(G.subgraph(node_people),pos=pos,font_weight="normal",font_size=11)

结果看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/14dd3991dd62f0424c6d41e4cda9eb68.png

具有节点属性的图,图片由作者提供

这里,我们看到与之前相同的颜色和四个具有自定义颜色的节点;所有信息都保存到了 GML 文件中。

7. 奖励:使用 D3.JS 绘制图

当我们绘制图时,NetworkX 在底层使用 Matplotlib。这对于像这样的小图来说是可以的,但如果图中包含 1000+个节点,Matplotlib 就会变得非常慢。使用D3.JS可以获得更好的结果。D3.JS 是一个开源的 JavaScript 库,可以更有效地进行图可视化。D3 是一个成熟的数据可视化项目(第一个版本于 2011 年发布),它不仅适用于图;在示例画廊中可以找到许多美丽的图像。

我没有找到将 NetworkX 图“原生”导出到 D3 的方法;然而,我们可以用几行代码做到这一点:

defconvert_to_d3(graph:nx.Graph)->dict:""" Convert nx.Graph to D3 data """nodes,edges=[],[]fornode_nameingraph.nodes:nodes.append({"id":node_name,"color":get_node_color(node_name),"radius":0.01*node_size(node_name)})fornode1,node2ingraph.edges:edges.append({"source":node1,"target":node2})return{"nodes":nodes,"links":edges}# Save in D3 formatd3=convert_to_d3(G)withopen('d3_graph.json','w',encoding='utf-8')asf_out:json.dump(d3,f_out,ensure_ascii=False,indent=2)

之后,我们可以将 JSON 数据加载到一个 JavaScript 页面中:

<scripttype="module">//Specify the dimensions of the chart.const width=window.innerWidth;const height=window.innerHeight;//Specify the color scale.const color=d3.scaleOrdinal(d3.schemeTableau10);const data=awaitd3.json("./d3_graph.json");const links=data.links.map(d=>({...d}));const nodes=data.nodes.map(d=>({...d}));//Create the SVG container.const svg=d3.create("svg").attr("width",width).attr("height",height).attr("viewBox",[0,0,width,height]).attr("style","max-width: 100%; height: auto;");...//Create a simulationwithseveral forces.const simulation=d3.forceSimulation(nodes).force("link",d3.forceLink(links).id(d=>d.id)).force("charge",d3.forceManyBody()).force("center",d3.forceCenter(width/2,height/2)).on("tick",ticked);//Append the SVG element.container.append(svg.node());</script>

本文不专注于 JavaScript 本身;在 D3.JS 中绘制图的代码示例很容易在网上找到(这个可以作为一个好的开始;完整源代码的链接也位于本页末尾)。

最后,我们可以运行本地服务器并在浏览器中打开一个页面。作为 JavaScript 的一个优点,我们可以使图交互式,甚至可以通过拖放移动节点:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3ae276fe9f6341972bac8284700e5cec.png

图可视化,图片由作者提供

作为缺点,几乎每次在 D3 渲染中的更改都需要深入到 JavaScript、CSS 和 HTML 样式。我不是前端网页开发者,即使是微小的调整也需要花费太多时间(然而,学习新事物总是令人愉快的:)。例如,我示例中的默认图形大小太小,我没有找到一种简单的方法来设置它的默认“缩放”值。然而,对于复杂的图形,别无选择,因为 Matplotlib 渲染太慢。我使用了 D3 库来可视化现代艺术家的图形,结果很好:

Python 数据分析:我们了解现代艺术家什么?

结论

在这篇文章中,我展示了使用 NetworkX 制作图形可视化的不同方法。正如我们所见,这个过程主要是直截了当的,我们可以轻松调整许多参数,如节点大小或颜色。更复杂的图形可以导出为 JSON,并与 JavaScript 一起使用。之后,我们可以使用强大的 D3.JS 库——在网页浏览器中的渲染过程可能是硬件加速的,并且速度更快。

在我之前的文章Python 数据分析:我们了解现代艺术家什么?中,我使用了 NetworkX 库来分析从维基百科收集的现代艺术家的数据。对社交数据分析感兴趣的人也可以阅读其他帖子:

  • 探索性数据分析:我们了解 YouTube 频道什么?

  • 德国住房租赁市场:使用 Python 进行探索性数据分析

  • 人们关于气候写了什么:使用 Python 进行 Twitter 数据聚类

  • 在 Twitter 帖子中寻找时间模式:使用 Python 进行探索性数据分析

  • Python 数据分析:我们了解流行歌曲什么?

如果你喜欢这个故事,请随意订阅Medium,你将在我新文章发布时收到通知,以及访问成千上万其他作者故事的完全权限。你还可以通过LinkedIn与我建立联系。如果你想获取这篇文章和其他文章的完整源代码,请随意访问我的Patreon 页面。

感谢阅读。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询