尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

NetworkX实战:从业务问题到图算法,快速挖掘用户关系

NetworkX实战:从业务问题到图算法,快速挖掘用户关系 前几天一个做电商运营的朋友问我怎么在几万条用户购买记录里快速找出购买习惯相似的用户群我第一反应就是把数据读进来建图跑社区发现十分钟出结果。他听完有点懵这不该上大数据平台吗我说不用用 NetworkX 就够了。NetworkX 是 Python 生态里做网络分析和图计算最常用的开源库没有之一。它基于纯 Python 实现提供了标准的图数据结构无向图、有向图、多重图以及从连通性、最短路、中心性到社区发现、网络流在内的一大批经典图算法。最吸引人的一点是它和 pandas、numpy、matplotlib 这些数据分析组件配合得极其顺畅你完全可以把建图当作整个分析流水线的中间步骤而不是把图数据孤立地丢给某个特殊系统。这篇文章不打算写官方文档式的罗列而是按我实际项目里用 NetworkX 的习惯从业务问题、数据模型、算法调用、完整案例、性能坑这几个角度把怎么用 NetworkX 解决真实问题这条链路走一遍。无论你是刚接触图论的数据分析师还是想把关系数据玩明白的 Python 开发者应该都能从中找到能直接抄作业的部分。1. 先从业务问题说起什么样的数据适合交给NetworkX很多人一听到图计算就觉得是大厂搞推荐系统、知识图谱才用得上的东西其实完全不是。只要你的数据里存在实体之间的联系它就天然是一张图。我判断一个需求该不该用 NetworkX就看两件事第一数据里有没有跨行、跨表的关系第二我要算的指标是不是站在某个节点角度看它在整个网络里的位置。这两条满足任意一条都说明图建模比普通表格处理更合适。1.1 我把常见业务问题映射成图模型的经验先列几个我在不同行业里见过的典型问题它们对应的图模型和算法其实非常清晰业务问题图模型常用算法社交平台里哪些用户私下抱团无向图好友关系连通分量、社区发现找出整个传播路径里的关键转发节点有向图转发关系中心性、PageRank电商用户和商品之间的关系挖掘二部图投影、社区发现供应链某个环节断了会影响多少下游有向无环图可达性、最短路论文引用网络里的高影响力论文有向图PageRank、引用中心性地铁/快递路线怎么走最短加权无向图Dijkstra 最短路这里有个关键认知图不是一种特殊的数据格式而是一种思考问题的方式。你把用户当成节点把购买行为当成边那么哪些用户买了同样的东西就变成了哪些节点共享了同一个邻域算起来就很自然了。1.2 为什么不是 Neo4j、Spark GraphX 或者别的图数据库我经常被问到都有图数据库了为什么还要用 NetworkX我的回答是两者解决问题的阶段完全不同。Neo4j 这类图数据库解决的是图数据的存储、查询、事务管理而 NetworkX 解决的是图算法的快速验证、分析和建模。在大多数业务分析场景里你的图数据就活在 CSV、Excel、MySQL 或 pandas 里为了跑一个连通分量就把整个图导进 Neo4j成本太高了。NetworkX 更适合作为分析流程里的计算引擎而不是存储引擎。它不要求你先建 schema不要求你把数据搬到专门的服务器打开 Jupyter 就能跑。对于千万级以下的边规模它基本都能扛下来超过这个量级再考虑用 igraph、Networkit 或者 Spark GraphX 做加速也不迟。后面我会专门讲性能边界在哪里。1.3 从表格到图的思维转换我在带新人时发现最难的不是学 API而是转换思维。做数据分析久了人会习惯性地认为行就是样本列就是特征但图不一样节点和边是两个独立的对象节点携带节点属性边携带边属性你分析的单元既可以是节点比如谁最重要也可以是边比如哪条关系最脆弱还可以是子图比如社区 A 和社区 B 之间的连接强度。举个例子一份用户关注关系表用 pandas 看就是follower_id, followee_id两列你有 10 万条记录。但如果用 NetworkX 建模这 10 万条记录变成了 10 万条有向边节点是用户。接下来你想知道有多少用户处于被关注但从不回关的状态这就是一个典型的节点入度 0 且出度 0的查询你想知道从用户 A 出发最多经过几层关系能触达用户 Z这就是最短路问题。这两个问题用 SQL 写一个比一个痛苦但在 NetworkX 里就是几行代码的事。2. 数据模型先行Graph、DiGraph、MultiGraph 该怎么选NetworkX 最核心的设计就是它把图的类型分得很清楚。你选错图类型后面所有算法结果都是错的所以这个环节值得多花点时间。2.1 三种图类型背后的业务含义nx.Graph()无向图。边没有方向A 认识 B和B 认识 A是同一件事。适合好友关系、共现关系、相似度矩阵这类场景。nx.DiGraph()有向图。边有方向A 关注 B不等于B 关注 A。适合粉丝、转发、资金流向、网页链接这类有逻辑方向的场景。nx.MultiGraph()和nx.MultiDiGraph()多重图。允许两个节点之间有多条平行边。适合用户 A 和用户 B 之间既有交易关系又有互相评价关系这种多关系并存的场景。我自己的经验是能明确方向就用 DiGraph分不清方向或者方向不重要就用 Graph同一个节点对之间有多种关系时再用 MultiGraph。多重图的功能虽然强大但算法适配会少一些很多经典算法比如最短路的某些变体对多重图的处理要额外小心非必要不建议一上来就用它。2.2 怎么把 pandas DataFrame 直接塞进图里实际项目里数据基本都在 DataFrame 里NetworkX 给了一个非常方便的函数nx.from_pandas_edgelist()。假设我有这样一份数据import pandas as pd import networkx as nx df pd.DataFrame({ source: [A, B, C, A, D], target: [B, C, A, D, E], weight: [3, 2, 5, 1, 4], relation: [朋友, 同事, 家人, 朋友, 客户] }) G nx.from_pandas_edgelist( df, sourcesource, targettarget, edge_attr[weight, relation], create_usingnx.Graph() )这样建出来的图节点是A、B、C、D、E边带上了weight和relation两个属性。特别提醒source和target这两个参数名不要和你的列名搞混了它们是函数参数你要传的是 DataFrame 里对应的列名。如果你想给节点也加上属性可以单独遍历节点设置或者先建图再从另外的 DataFrame 里按 id 匹配node_info pd.DataFrame({ node: [A, B, C, D, E], 部门: [研发, 运营, 研发, 市场, 市场] }) for _, row in node_info.iterrows(): if row[node] in G.nodes: G.nodes[row[node]][部门] row[部门]2.3 邻接矩阵的构建与读取一个高频需求热词里专门有python构建邻接矩阵说明很多人会在这个点上卡住。NetworkX 里邻接矩阵的转换非常简单# 图 - numpy 邻接矩阵 adj nx.to_numpy_array(G, nodelistsorted(G.nodes), weightweight) print(adj) # 邻接矩阵 - 图 G2 nx.from_numpy_array(adj)这里有一个非常隐蔽的坑to_numpy_array()生成矩阵时节点顺序默认是按照nodelist参数指定的顺序排的。如果你不传nodelist它会默认按G.nodes()的迭代顺序生成矩阵。在 NetworkX 3.x 里节点顺序和节点加入图的顺序一致而不是按字典序排序。这意味着如果你两次构建图时节点插入顺序不同得到的邻接矩阵行顺序可能完全不同但矩阵里的数值又恰好对不上。所以我强烈建议只要涉及邻接矩阵就一定显式传nodelistsorted(G.nodes)。这样既能保证顺序可预测又方便后续把矩阵和外部 embedding 结果拼接。如果图太大可以转成稀疏矩阵nx.to_scipy_sparse_array(G)这个函数返回 scipy 的稀疏矩阵内存占用少很多。2.4 增删改查的基本操作图本身就是字典套字典的结构所以增删改查特别直白G.add_node(X, 属性值) # 加节点带属性 G.add_edge(A, B, weight1) # 加边带权重 G.remove_edge(A, B) G.remove_node(X) # 注意删节点会自动删和它相连的所有边 # 查询 G.nodes() # 所有节点 G.edges() # 所有边 G[A][B] # 边 A-B 的属性字典 G.degree[A] # 节点 A 的度记住一个关键点G.nodes()和G.edges()返回的是视图对象不是列表。如果你要判断节点是否在图中直接写if A in G.nodes:没问题但如果想把节点列表和别的数据做集合运算最好先转成set(G.nodes)否则可能在连乘、加和等操作上报出类型错误。3. 高频图算法实战这些函数我实际项目里用得最多3.1 连通性分析先看图的骨架拿到任何一张图我做的第一件事永远是看它是不是连通的。原因很简单一个不连通的图里面可能躺着好几个互不相干的群体你如果不先做连通分量分析后面跑中心性、最短路都是错的——因为不同连通分量之间根本没有路径算出来的距离是无穷大。G nx.Graph() edges [(A, B), (B, C), (D, E)] G.add_edges_from(edges) # 无向图的连通分量 components list(nx.connected_components(G)) print(components) # [{A, B, C}, {D, E}] # 有向图则要分强连通/弱连通 DG nx.DiGraph() DG.add_edges_from([(A, B), (B, C), (C, A), (D, C)]) print(list(nx.strongly_connected_components(DG))) print(list(nx.weakly_connected_components(DG)))在有向图里strongly_connected_components找的是强连通分量——任意两点之间互相可达的集合而weakly_connected_components是把有向边当成无向边来看的连通分量。这两个概念千万别搞混。我做传播路径分析时通常先看弱连通分量把握整体格局再看强连通分量找到那些内部高度互相可达的核心组织。3.2 最短路权重参数的三个认知层次最短路是图算法里的hello world但我在带项目时发现很多人对weight参数的理解停留在表面。第一层认知nx.shortest_path(G, A, D)是不带权重的最短路它计算的是最少经过几条边适用于无权图。第二层认知带权重是nx.shortest_path(G, A, D, weightweight)它计算的是路径上边权之和最小适用于路况、距离、成本等场景。第三层认知最容易被忽略weight参数不仅可以传字符串还可以传一个函数动态决定每条边的权重。这个能力在处理业务数据时极其好使。比如供应链场景里一条边的成本可能是运输距离乘以商品类型系数你可以在函数里做任何自定义计算def dynamic_weight(u, v, d): # d 是边属性字典 base d[distance] if d[product_type] 冷链: return base * 3 return base path nx.shortest_path(G, 工厂, 门店, weightdynamic_weight)另外注意 Dijkstra 算法的适用前提是边长非负。如果边权含负数要用nx.bellman_ford_path()如果图中包含负权环这两个算法都会出问题得先检查是否有负环。3.3 中心性指标五个概念各有各的用途中心性回答的问题是谁最重要但重要这个词在不同业务里有不同含义所以 NetworkX 提供了好几种中心性指标我列一个自己常用的选型表指标本质含义适合的业务问题函数度中心性谁的朋友最多找运营活动的超级用户nx.degree_centrality()介数中心性谁最频繁地出现在别人之间的最短路上找信息流通的必经之路/瓶颈节点nx.betweenness_centrality()接近中心性谁到其他所有节点的平均距离最短找信息扩散最快的源头nx.closeness_centrality()特征向量中心性谁的重要性由邻居重要性决定找强强联合的小圈子nx.eigenvector_centrality()PageRank谁被高质量节点引用的次数最多找影响力节点/重要内容nx.pagerank()举一个实际例子在社交电商场景里如果你想找能帮忙把活动消息扩散出去的人用接近中心性更合适因为这种人到所有人的路径都短如果你想找连接了两个不同人群的桥梁人物那必须用介数中心性而如果你想找拥有很多高质量关注者的意见领袖PageRank 的表现通常最好。每次跑中心性我都建议先把结果转成 DataFrame 排个序pr nx.pagerank(G, alpha0.85) pr_df pd.DataFrame({node: list(pr.keys()), pagerank: list(pr.values())}) pr_df.sort_values(pagerank, ascendingFalse).head(10)3.4 社区发现找出自然聚成的团社区发现是网络分析里最有业务价值的部分之一。NetworkX 内置了greedy_modularity_communities它通过模块度最大化的贪心策略把图划分成若干个社区。在 NetworkX 2.x 里它在nx.algorithms.community子模块中在 3.x 里推荐写法是nx.community.greedy_modularity_communities。但如果你想要效果更稳定的 Louvain 算法NetworkX 本身没有原生实现需要配合第三方库python-louvain注意不是louvain这两个包名很容易搞混pip install python-louvainimport community as community_louvain # community_louvain.best_partition 返回 {节点: 社区编号} 的字典 partition community_louvain.best_partition(G)我自己的经验是greedy_modularity_communities在小图上跑得快但社区粒度较粗python-louvain的分层结果通常更符合直觉而且能自动发现多层级结构。如果图里有明确的方向性用community_louvain.best_partition(G.to_undirected())时我会先明确自己是否真的要忽略方向——忽略方向会丢失很多信息有时候该用有向版本就得用directedTrue之类的参数如果有的话。3.5 随机图生成器仿真实验的好帮手很多初学者不知道 NetworkX 还可以用来生成合成网络做仿真实验。我在写算法验证脚本时经常用到三个生成器nx.erdos_renyi_graph(n, p)著名的 ER 随机图每个节点对以概率 p 连边。适合做假设没有结构的对照组。nx.watts_strogatz_graph(n, k, p)小世界网络具有高聚类和短平均路径。社交网络、神经网络这类小世界特征的仿真用这个很合适。nx.barabasi_albert_graph(n, m)无标度网络度分布符合幂律。真实世界里的互联网、合作网络通常符合这个特征。用它们的意义在于当你刚拿到一份真实网络数据时先和一个同等规模但结构随机的合成网络对比能很快判断出你的网络是否存在显著模式。比如真实网络的聚类系数远高于 ER 随机图就说明它确实有社区结构值得深入挖掘。4. 一个完整案例从用户购买记录到用户分群的实战链路理论讲再多都不如跑一个完整案例。我在这里模拟一个电商场景你有一张用户购买日志表每行是user_id, product_id要找出购买习惯相似的用户群并给运营团队输出一份可落地的用户分群结果。4.1 从原始日志构建二部图数据本身就是用户-商品之间的二部关系用户集合和商品集合是两个顶点集边代表买过。在 NetworkX 里我们可以直接把所有用户和商品都塞进同一个图然后根据节点类型区分import networkx as nx import pandas as pd import numpy as np # 模拟数据2000 个用户500 个商品每个用户随机买 1~10 个商品 np.random.seed(42) n_users 2000 n_products 500 records [] for uid in range(n_users): bought np.random.choice(n_products, sizenp.random.randint(1, 11), replaceFalse) for pid in bought: records.append((uid, pid)) df pd.DataFrame(records, columns[user_id, product_id]) B nx.Graph() B.add_nodes_from(df[user_id].unique(), bipartiteuser) B.add_nodes_from(df[product_id].unique(), bipartiteproduct) B.add_edges_from(df[[user_id, product_id]].values)这里给每个节点加了一个bipartite属性用来标记节点属于哪一方。NetworkX 有专门检测二部图的函数nx.algorithms.bipartite.is_bipartite(B)可以验证一下建模是否正确。4.2 把二部图投影成用户-用户同购网络分析用户相似度时两边都保留会很别扭通常的做法是投影到用户侧如果两个用户至少共同购买过一个商品就在用户之间连一条无向边边权重可以等于共同购买的商品个数。# 投影到用户侧 users [n for n, d in B.nodes(dataTrue) if d[bipartite] user] G_user nx.Graph() G_user.add_nodes_from(users) for p in df[product_id].unique(): # 买过该商品的所有用户两两之间加一条边 buyers [uid for uid, pid in records if pid p] for i in range(len(buyers)): for j in range(i 1, len(buyers)): if G_user.has_edge(buyers[i], buyers[j]): G_user[buyers[i]][buyers[j]][weight] 1 else: G_user.add_edge(buyers[i], buyers[j], weight1)这段代码在小数据量下没什么问题但如果商品数量很大、热门商品买的人很多双重循环会非常慢。实际项目里我会先按product_idgroupby再对每个组做集合级的两两组合并且只用重度用户构建投影比如购买记录大于 3 条的用户这样可以显著降低计算量。这里为了可读性我保留了最直观的写法真实场景记得换更高效的方式。4.3 计算核心指标并解释业务含义图建好之后我们开始做分析。# 连通分量看看用户网络整体的分裂状况 components list(nx.connected_components(G_user)) component_sizes sorted([len(c) for c in components], reverseTrue) print(连通分量数量:, len(components)) print(最大连通分量占比:, component_sizes[0] / n_users) # 核心用户度最高的用户 degree_sorted sorted(G_user.degree, keylambda x: x[1], reverseTrue) print(共同购买关联最多的Top10用户, degree_sorted[:10]) # 社区发现寻找购买风格相近的用户群 partition community_louvain.best_partition(G_user) grouped pd.DataFrame({user_id: list(partition.keys()), group: list(partition.values())}) print(grouped[group].value_counts().head(10))这三个指标分别回答三个问题用户之间是否联系紧密谁是最受欢迎的搭子用户整体可以分成多少个风格鲜明的小群体。运营同学拿到的不是一张表而是一个可以直接打标签的用户分群名单给第 3 组的用户推母婴品类给第 7 组的用户推数码产品比按人口学属性分群准确得多。4.4 把结果导出衔接下游分析做完之后结果必须能落到业务系统里。我一般输出两种格式# 1. 用户分群结果导出 CSV grouped.to_csv(user_communities.csv, indexFalse) # 2. 整个图导出成 GraphML方便用 Gephi 做可视化探索 nx.write_graphml(G_user, user_projection.graphml)GraphML 是一种通用的图格式Gephi、Cytoscape 都能读。我通常把 NetworkX 当分析引擎真正需要和业务方一起看图、找洞察时导到 Gephi 里做视觉探索体验比 matplotlib 直接画快得多。后面我会详细讲可视化选型。5. 可视化与性能别让布局算法拖垮你的分析5.1 布局算法的选择是可视化成败的第一关NetworkX 本身不自带高性能可视化引擎它依赖 matplotlib 画图。画图的难点不在画而在布局——节点摆在哪里。NetworkX 提供了一批布局算法我最常用的是布局函数效果适用场景注意事项nx.spring_layout()力导向布局节点互相排斥、边像弹簧拉近小图几百节点以内节点稍多就非常慢且结果有随机性nx.kamada_kawai_layout()基于距离矩阵的布局比较对称中规模连通图对不连通图容易失效nx.circular_layout()环形排列展示环状结构/时序关系信息量低nx.random_layout()随机摆放快速预览看不出结构nx.spectral_layout()基于拉普拉斯特征向量的布局大图快速概览质量中等但速度快我的个人经验是如果节点数在 200 以内直接spring_layout迭代次数iterations100左右效果就很好如果节点数超过 500千万别用 spring_layout否则跑几十秒是常事而且画出来的图密密麻麻根本看不懂。此时我会先抽子图再画或者直接换spectral_layout看一眼整体形状。5.2 画布上的绘图细节如何让图真正可读布局定了以后绘图函数的参数会决定这张图是能看还是能看懂。我积累了几个常用套路import matplotlib.pyplot as plt plt.figure(figsize(12, 8)) pos nx.spring_layout(G_user, seed42) # 节点大小按度数映射颜色按社区映射 node_sizes [300 100 * G_user.degree(n) for n in G_user.nodes()] node_colors [partition[n] for n in G_user.nodes()] nx.draw_networkx_nodes(G_user, pos, node_sizenode_sizes, node_colornode_colors, alpha0.8) nx.draw_networkx_edges(G_user, pos, width0.8, edge_colorgray, alpha0.5) # 只给关键节点加标签避免全图标签糊成一团 important [n for n, d in sorted(G_user.degree, keylambda x: x[1], reverseTrue)[:20]] labels {n: n for n in important} nx.draw_networkx_labels(G_user, pos, labelslabels, font_size10) plt.title(用户购买相似度网络) plt.axis(off) plt.tight_layout() plt.savefig(user_network.png, dpi150)画图时我总结了一条原则标签宁少勿多图例宁可手写。全图打标签的结果通常是标签互相遮挡谁也看不清。先画节点和边的结构再把你要强调的节点单独用不同颜色或大小突出才是真正能拿出去汇报的图。5.3 大图处理的三个实用策略如果图实在太大比如几十万甚至上百万节点NetworkX 的本体功能就会开始吃力。我用过三种缓解策略第一按子图拆解。先跑connected_components把最大连通分量抽出来单独分析。大网络通常有一个巨连通分量吞掉了大部分节点其他小分量结构简单、价值有限分开处理效率最高largest_cc max(nx.connected_components(G), keylen) G_sub G.subgraph(largest_cc).copy() # 记得 copy否则只是原图的视图第二按边权过滤。比如社交网络里的弱连接边权基本是 1强连接边权可能是 10 以上。把低于阈值的边过滤掉图一下就瘦下来了G_strong nx.Graph() for u, v, d in G.edges(dataTrue): if d.get(weight, 1) 5: G_strong.add_edge(u, v, weightd[weight])第三用采样近似。实在大到连 subgraph 也撑不住可以对节点做随机采样或者只保留 top 中心性的节点组成骨架网络先看结构趋势细节留到原生引擎上处理。5.4 性能边界NetworkX 不是银弹NetworkX 是纯 Python 实现优点是灵活、易二次开发缺点就是性能有天花板。我实测下来的体感几万节点的图跑 PageRank、最短路之类的经典算法都还很轻松到几十万节点、上百万边社区发现算法会明显变慢spring_layout基本不能看再往上到千万级边就直接超出它的舒适区了。遇到大图我的选型建议是工具特点适合场景NetworkX上手快、生态好、算法全教学、原型验证、千万边以内分析igraph底层 C 实现速度快百万到千万级边Networkit高性能并行图算法大规模网络、需要跑多种算法graph-toolC 实现功能非常全复杂统计建模、需要高性能的场景但我要强调一点就算你最终要用 igraph 或 Networkit我也会建议先在 NetworkX 里把整条分析思路跑通再迁移到高性能库上去。因为 NetworkX 的 API 最直观、调试最容易用它做原型验证能把算法思路和性能问题分开至于性能优化那是在思路验证之后才需要考虑的事。6. 版本差异和踩坑记录从2.x到3.x容易翻车的几件事NetworkX 更新到 3.x 之后不少老代码会直接报错。我在迁移项目时踩过不少坑挑几个最常见的列出来看到的朋友可以少走弯路。6.1 节点标识类型不一致导致连了个寂寞NetworkX 允许任何可哈希对象做节点这意味着你既可以用字符串123也可以用整数123。看似灵活实际上是个大坑。假设 DataFrame 里user_id是字符串类型你建图时用的user_id是字符串后面另一个表里user_id是整数你再往图里加节点时它会被当成全新的节点而不会和你已有的字符串节点合并。G nx.Graph() G.add_edge(123, 456) print(G.has_edge(123, 456)) # False因为类型不同这个坑非常隐蔽因为查数据时G.nodes()里你看到的是123而不是123如果不仔细看数据类型根本发现不了。解决方法很简单建图前统一做astype(str)或者统一转成int类型保持全图节点标识只有一种类型。6.2 遍历图的同时修改图会直接崩如果你在循环遍历G.edges()的同时往图里加边、删边会触发RuntimeError: dictionary changed size during iteration。这几乎是每个 NetworkX 新手的必经之路。# 错误写法 for u, v in G.edges(): if some_condition: G.remove_edge(u, v) # 报错 # 正确写法先把待处理的边收集到列表里 edges_to_remove [(u, v) for u, v in G.edges() if some_condition] G.remove_edges_from(edges_to_remove)记住迭代和修改不能同时进行要修改就先收集、再批量操作。先收集、再修改这个原则在图分析里非常重要它不仅是避免报错的技巧也在提醒你图操作是批量的不是单条的。6.3 权重参数不设置最短路就跑出了错答案这个坑和算法无关和业务语义有关。很多实际网络里的边是有权重的比如距离成本延迟。如果你调用shortest_path()时不传weight它默认把每条边的权重当作 1算出来的是最少跳数路径而不是成本最低路径。我见过不止一个项目团队用nx.shortest_path()做路径推荐出来的结果看着不对劲一查才发现边权重压根没被用上。所以每次调用带权重的算法之前我会先打印一条边的属性确认weight字段存在且数值正确u, v, d list(G.edges(dataTrue))[0] print(d) # 确认有没有 weight 字段6.4 from_pandas_edgelist 的 source 和 target 参数容易写反看起来是小问题但写反的代价是整个图的结构完全颠倒。尤其是当你用DiGraph建模时source和target写反所有有向关系就变成了反向PageRank、最短路径全部得出错误结论。我的习惯是每次建完图都跑一个探针验证比如有向图里查G.has_edge(实际源头, 实际目标)确认方向符合预期。6.5 社区算法的版本差异python-louvain 的导入坑NetworkX 2.x 里greedy_modularity_communities在nx.algorithms.community3.x 里推荐从nx.community导入。如果你安装了python-louvain导入方式却是import community as community_louvain这个包名和模块名的错位容易让人懵。我的建议是先pip install python-louvain然后固定写法import community as community_louvain partition community_louvain.best_partition(G)如果import community报错先检查是否把包名写成了louvain。这两个名字坑过非常多的人网上存档的旧教程里也有大量混用示例遇到报错别慌换包名装一次就行。6.6 子图是视图不是副本G.subgraph(nodes)返回的不是新的独立图而是原图的一个视图。也就是说你在子图上做的修改会同步影响原图。如果你只是想提取一个子图做实验记得加.copy()sub G.subgraph(nodes).copy() # 独立于原图的子图这个坑的危害在于你在子图上删了一条边回头一看原图的边也没了还找不到原因。6.7 节点和边的视图不是列表G.nodes()返回NodeViewG.edges()返回EdgeView。它们可以直接遍历、判断成员但不是列表。有些操作比如G.nodes[0]会报错除非节点编号恰好是整数 0 且按列表索引理解也别指望G.nodes()直接切片。需要列表操作时先list(G.nodes())或set(G.nodes())转换。6.8 邻接矩阵构建时 node ordering 不一致前面 2.3 节提过to_numpy_array默认不排序节点。如果你两次构建矩阵时节点插入顺序不同你会得到看起来是同一个图但矩阵下标完全错位的局面。尤其是要和机器学习模型对接时节点 embedding 和矩阵行索引对齐是刚需建议所有涉及矩阵的操作都传nodelistsorted(G.nodes())一步到位避免后患。7. 我的 NetworkX 工作流和个人建议最后分享一点我现在的工作流算是给这篇文章收个尾。我现在做图分析时默认就是pandas 做数据清洗和透视NetworkX 做关系建模、指标计算和社区发现matplotlib 或 Gephi 做可视化结果落到 CSV 或数据库给业务方。整个过程在 Jupyter 里就能完成遇到需要和深度学习结合的任务就把 NetworkX 算出的中心性、社区编号、邻接矩阵整理成特征再丢给 PyTorch 或 TensorFlow 做后续建模。NetworkX 不是最快的图计算引擎也不是可视化最强的工具但它是把图论思路变成代码最快的方式。它的生态黏性很强数据能进 pandas就能进 NetworkX算完能出 numpy 矩阵就能对接机器学习。对大多数业务级别的图数据它完全够用。根据我个人经验踩过这么多坑之后真正重要的不是背下所有 API而是先想清楚三件事你的实体是什么、你的关系是什么、你要从这张图里得到什么。想明白了NetworkX 就是那一把顺手就能拿起来用的瑞士军刀想不明白再强大的图数据库也只是个昂贵的摆设。
返回列表