Python社交网络分析实战:从数据建模到业务决策的七道生死关
1. 这不是一张“关系图”而是一张“影响力地图”你打开微信通讯录看到583个联系人刷微博时首页滚动着27个你关注的人发的动态在LinkedIn上系统提示“你和某位CTO有3度连接”。这些数字背后藏着一个被大多数人忽略的事实人与人之间的连接从来不是随机散点而是具有可测量、可建模、可预测的拓扑结构。所谓“Social Network Analysis in Python”绝不是用networkx画几条线、标几个点就完事的玩具项目——它是一套完整的社会系统解剖学工具包核心目标是把“谁认识谁”“谁影响谁”“信息怎么流动”“群体如何分裂或凝聚”这些模糊的日常观察转化成可计算、可验证、可干预的量化结论。我第一次真正意识到SNA的价值是在帮一家本地社区团购平台做用户流失分析时。他们发现每月有12%的老用户突然停止下单客服反馈“就是不买了没说原因”。常规做法是查订单、看浏览时长、做问卷。但我们换了个思路把过去6个月所有用户的下单行为、拼团邀请关系、群内发言互动脱敏后构建成一个加权有向网络用Python跑出每个用户的介数中心性Betweenness Centrality和聚类系数Clustering Coefficient。结果发现流失用户中有63%的人在流失前两周其所在子群的聚类系数骤降40%以上——这意味着他们原本紧密的小圈子正在瓦解而平台并未察觉。这不是“用户不满意”而是“社交锚点松动了”。这个洞察直接推动产品团队上线了“老带新裂变激励熟人拼团专属频道”三个月后用户次月留存率回升了9.2个百分点。这就是SNA的真实战场它不关心单个节点的属性比如用户年龄、消费金额而是死磕节点之间的关系模式。它能告诉你为什么一个粉丝只有800人的小红书博主转发一条测评就能带动3家工厂紧急补货也能解释为什么某款App的邀请码裂变活动在A城市爆发式传播到了B城市却像石沉大海——问题不在文案而在两地用户网络的平均路径长度和社区发现模块度Modularity根本不同。本文接下来要拆解的就是如何用Python这把手术刀一层层切开社交网络的肌理。你会看到从原始数据清洗到社区发现从关键节点识别到信息流模拟每一步都藏着决定成败的细节。这不是教程汇编而是我踩过27次坑、重写过11版核心脚本后把血泪经验压缩进的实操手册。2. 整体设计思路为什么必须放弃“先建图再分析”的线性思维很多人拿到SNA任务的第一反应是赶紧把用户ID和好友关系导出来用pandas读成DataFrame然后networkx一画万事大吉。这种思路看似高效实则埋下了后续所有分析失效的种子。真正的SNA工作流根本不是“数据→图→指标”这条直线而是一个三维闭环业务问题驱动数据定义 → 数据特征反哺网络构建逻辑 → 网络结构验证业务假设。下面我用三个真实案例说明为什么跳过这个闭环会直接翻车。2.1 案例一把“点赞”当“信任”差点让风控模型集体误判某内容平台想识别高风险水军账号。初始方案很简单把用户A给用户B点赞视为A信任B构建有向信任网络再计算入度中心性In-degree Centrality——入度越高说明越多人信任它越可能是真实KOC。但上线一周后风控团队发现误杀率高达35%。复盘时我们调取了TOP100高入度账号的行为日志发现其中72个账号的点赞行为高度集中它们只在凌晨2-4点批量给同一类低质视频点赞且点赞间隔精确到1.8秒。问题出在哪我们错误地把平台交互行为点赞等同于社会学意义上的信任关系。真正的信任网络需要叠加时间维度是否持续互动、内容维度是否对同类内容反复互动、强度维度点赞/评论/转发/私信的权重必须不同。最终方案是定义“信任边权重 0.3×点赞次数 0.5×评论次数 0.8×转发次数 1.0×私信次数”并过滤掉单日互动超过500次的异常时段。调整后误杀率降至4.1%且成功捕获了3个伪装成普通用户的水军控制中心。2.2 案例二忽略“无向边”的隐含方向性导致社区发现完全失真某企业内网想通过员工协作网络优化部门架构。HR提供了“谁和谁一起参与过项目”的名单我们默认这是无向边A-B合作 B-A合作用Louvain算法跑社区发现结果分出7个“协作社区”。但业务负责人一看就摇头“第三组里市场部和法务部根本不可能高频协作这分组没意义。”深入检查原始数据才发现项目表里有“主导方”和“支持方”字段而我们的建图脚本直接忽略了。真实的协作关系是强方向性的市场部主导的项目法务部只是签字背书但法务部主导的合规审计项目市场部几乎不参与。我们立刻重构网络将每条记录拆成两条有向边——A主导B支持记为A→B权重0.7B主导A支持记为B→A权重0.3。再用Directed Louvain算法重新计算社区数变为5个且每个社区内部职能高度协同如研发测试运维完全匹配业务实际。2.3 案例三静态快照 vs 动态演化错过关键预警信号某在线教育平台监控讲师影响力每月导出一次“学生报名-讲师”关系表建静态图计算讲师的PageRank值。连续半年数据平稳直到某月PageRank突降30%的讲师投诉“流量被限”。我们调取实时日志才发现该讲师的课程在抖音的自然推荐量暴增但平台内流量确实下滑——因为学生不再通过APP首页搜索找课而是直接从抖音跳转绕过了APP内的“关注-点击-报名”路径。静态图只记录了“报名”这一终点事件却丢失了路径依赖的断裂。解决方案是引入时间切片将一个月划分为4个周粒度构建4张快照图计算每张图的PageRank并用滑动窗口检测变化率。当某讲师连续两周PageRank下降斜率超过-15%/周系统自动触发“渠道迁移”预警运营团队随即在抖音落地页嵌入APP下载引导两周后APP内回流报名提升22%。这三个案例指向同一个底层逻辑SNA不是数据处理的终点而是业务理解的起点。你的网络构建规则必须是你对业务本质的数学翻译。没有放之四海皆准的“标准建图法”只有“这个问题下最合理的建图法”。所以在动手写第一行Python代码前请务必回答清楚这条边代表什么现实行为它的强度如何量化这条边是否有方向性方向性由什么业务规则决定这个网络是静态快照还是需要捕捉动态演化时间粒度怎么定哪些节点/边需要过滤过滤规则是技术限制如数据缺失还是业务常识如“同事关系”不等于“信任关系”这些问题的答案将直接决定你后续所有分析的生死线。别急着import networkx先拿出纸笔把业务逻辑画成流程图。我见过太多人花三天调通Louvain算法却因建图逻辑错误让整个分析变成一场昂贵的自欺欺人。3. 核心细节解析从原始数据到可用网络的七道生死关把业务逻辑翻译成网络结构只是万里长征第一步。真正消耗精力、决定结果质量的是数据清洗与网络构建过程中的七道硬核关卡。每一关都藏着让分析结果全盘作废的陷阱而这些陷阱90%的教程和文档都不会提。下面我按实操顺序逐条拆解。3.1 关卡一ID标准化——你以为的“同一用户”可能根本不是一个人场景整合APP登录日志、微信公众号粉丝表、小程序用户表三份数据构建全渠道用户网络。表面看三份表都有“user_id”字段直接merge不就完了错。APP日志里的user_id是UUID格式如a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8公众号粉丝表是OpenIDoAbcDeFgHiJkLmNoPqRsTuVwXyZ小程序用户表是UnionIDu1v2w3x4y5z6a7b8c9d0e1f2g3h4i5j6。更致命的是同一个真实用户在不同渠道可能注册了多个账号张三用手机号A注册APP用手机号B绑定公众号用微信号C登录小程序。如果强行用字符串匹配会把一个用户拆成三个孤立节点彻底破坏网络连通性。实操方案必须建立统一的业务主键Business Primary Key。我们采用三级映射策略一级设备指纹。用device_id安卓IDFA/苹果IDFVip_address前两段user_agent哈希值生成设备指纹。同一设备在同一IP段的UA哈希值99.7%概率属于同一人。二级手机号归一。对所有含手机号的字段APP注册、公众号绑定、小程序授权进行脱敏清洗移除86、空格、横线再MD5哈希。同一手机号哈希值必然相同。三级行为关联。当设备指纹A在72小时内先后触发了手机号B的APP登录和公众号关注则建立A↔B强关联。最终用图数据库Neo4j存储这三层关系执行Cypher查询MATCH (d:Device)-[r:LINKED_TO]-(p:Phone) WITH d, collect(p.phone_hash) as phones WHERE size(phones) 1 RETURN d.device_fingerprint, phones输出所有存在多手机号关联的设备人工抽检确认后将这些设备下的所有节点合并为一个业务主键biz_uid。这个过程耗时最长我们花了17小时处理2300万条原始记录但它是整个网络可信度的基石。没有这一步后面所有中心性计算都是空中楼阁。提示永远不要相信原始数据里的“user_id”是全局唯一的。我经手的12个项目中有9个存在ID体系混乱问题。宁可多花两天做ID Mapping也不要带着错误ID跑算法。3.2 关卡二边权重校准——别让“点赞”和“转账”拥有同等话语权很多初学者直接用“是否发生行为”作为边权重1或0这是最大误区。现实世界的关系强度天差地别用户A给B转账10万元和A给B朋友圈点一个赞其社会学意义能一样吗NetworkX的nx.degree_centrality()等函数对权重极其敏感权重设错中心性排名直接颠倒。实操方案采用业务价值锚定法校准权重。以电商场景为例定义“交易关系”为最高权重基准1.0“咨询关系”用户A向客服询问B店铺商品设为0.6因为咨询是交易前置动作“浏览关系”A在B店铺页面停留30秒设为0.3因为停留是弱意向信号“点赞关系”A点赞B发布的商品帖设为0.1因为点赞成本最低。但这样还不够。我们发现同一类行为在不同情境下强度也不同。比如“咨询关系”如果A咨询的是B店铺的爆款商品月销1000件权重应提升至0.8如果咨询的是滞销品月销10件权重降至0.4。因此最终权重公式为weight base_weight × (1 log2(popularity_score 1))其中popularity_score是商品月销量/同类目平均销量的比值。这个公式保证基准权重不变popularity0时log2(1)0爆款商品咨询权重自然放大popularity1000时log2(1001)≈10权重≈1.0×1111但需归一化到0-1区间避免极端值1防止log0错误。我们用pandas实现# 假设df_edges包含behavior_type, item_popularity列 base_weights {transaction: 1.0, consult: 0.6, browse: 0.3, like: 0.1} df_edges[base_weight] df_edges[behavior_type].map(base_weights) df_edges[weight] df_edges[base_weight] * (1 np.log2(df_edges[item_popularity] 1)) # 归一化到0-1 df_edges[weight] (df_edges[weight] - df_edges[weight].min()) / (df_edges[weight].max() - df_edges[weight].min())这个看似简单的归一化让我们的关键意见领袖KOL识别准确率从68%提升到89%。因为真正的KOL往往不是咨询最多的人而是咨询高热度商品最多的人。3.3 关卡三自环边Self-loop处理——为什么你的网络里总有个“孤岛节点”NetworkX默认允许节点连接自己即A→A这在社交网络中毫无意义没人会“关注自己”或“给自己转账”但原始数据中常因ETL错误产生大量自环边。比如某次数据同步脚本bug把用户A的支付记录错误写成“A向A支付”导致A节点出现自环。在计算PageRank时自环边会像黑洞一样吸走A的全部权重使其PageRank趋近于0同时拉低整个网络的收敛速度。实操方案在构建图对象前强制删除所有自环。import networkx as nx G nx.DiGraph() # 添加边时过滤 for _, row in df_edges.iterrows(): if row[source] ! row[target]: # 关键过滤条件 G.add_edge(row[source], row[target], weightrow[weight]) # 或者构建后批量删除 G.remove_edges_from(nx.selfloop_edges(G))更进一步我们发现某些业务场景下自环边其实隐含重要信息。比如在线教育平台“学生反复回看同一节课程视频”这个行为虽然源节点目标节点但它代表极高的学习投入度。此时我们不删除自环而是将其转换为节点属性# 统计每个节点的自环边总权重 self_loop_weights {} for u, v, data in G.selfloop_edges(dataTrue): self_loop_weights[u] self_loop_weights.get(u, 0) data[weight] # 将其作为节点属性存入 nx.set_node_attributes(G, self_loop_weights, rewatch_score)这样既避免了自环干扰网络拓扑计算又保留了其业务价值。记住删除不是目的理解数据背后的业务含义才是关键。3.4 关卡四稀疏性陷阱——当99.8%的边权重为0时你还在用Dense Matrix当网络规模达到百万级节点时一个致命问题浮现你的邻接矩阵Adjacency Matrix99.8%的位置都是0。如果用numpy.array存储一个100万×100万的float64矩阵将占用约7.4TB内存任何机器都会瞬间OOM。但很多教程仍教新手用nx.to_numpy_array(G)这是灾难性误导。实操方案全程使用稀疏矩阵Sparse Matrix。NetworkX原生支持# 构建图时指定为稀疏存储 G nx.DiGraph() # 计算中心性时networkx内部已优化但需注意参数 pr nx.pagerank(G, alpha0.85, max_iter100, tol1e-06) # 自动使用稀疏求解器 # 如需手动获取邻接矩阵必须用scipy.sparse from scipy import sparse adj_matrix nx.to_scipy_sparse_array(G, dtypefloat, formatcsr) # CSR格式最省内存 print(fMatrix shape: {adj_matrix.shape}, Non-zero elements: {adj_matrix.nnz})CSRCompressed Sparse Row格式将内存占用从TB级降到GB级。我们处理230万节点、8900万边的电商网络时CSR邻接矩阵仅占1.2GB内存而稠密矩阵理论值为7.4TB。此外所有基于矩阵的算法如PageRank、Eigenvector Centrality在scipy.sparse下运行速度提升3-5倍。如果你的nx.info(G)显示Number of edges: 0但len(G.edges())有值大概率是稀疏矩阵初始化失败立刻检查format参数。注意不要试图用to_numpy_array再转回稀疏矩阵这是二次浪费。从数据加载开始就规划好稀疏存储路径。3.5 关卡五孤立节点Isolates处置——留着它们还是踢出去孤立节点指既无入边也无出边的节点degree0。在100万节点的网络中常有15-20万孤立节点如注册未激活用户、爬虫僵尸号。传统做法是G.remove_nodes_from(list(nx.isolates(G)))一键清除。但我们在某金融风控项目中发现孤立节点的分布本身就是强风险信号。比如某批新注册用户中83%在24小时内成为孤立节点无任何交易、无任何好友添加而正常用户该比例仅为7%。这批孤立节点后来被证实是黑产批量注册的养号。实操方案采用双轨制处理分析轨保留所有孤立节点但为其打上is_isolateTrue标签并统计其在时间、地域、设备维度的聚集度。用nx.number_of_isolates(G)监控比例变化。计算轨在运行中心性、社区发现等算法前创建子图G_core G.subgraph([n for n in G.nodes() if G.degree(n) 0])确保算法效率。关键代码# 标记孤立节点 isolates list(nx.isolates(G)) nx.set_node_attributes(G, {n: True for n in isolates}, is_isolate) nx.set_node_attributes(G, {n: False for n in G.nodes() if n not in isolates}, is_isolate) # 创建核心子图用于计算 G_core G.subgraph([n for n in G.nodes() if G.degree(n) 0]) print(fOriginal nodes: {G.number_of_nodes()}, Core nodes: {G_core.number_of_nodes()}) # 但分析时仍可访问孤立节点属性 isolate_stats pd.DataFrame([ {region: G.nodes[n][region], device_type: G.nodes[n][device_type]} for n in isolates ])这样既保障了算法性能又不丢失业务洞察。记住数据清洗不是删减而是分层。3.6 关卡六多重边Multi-edge合并——当A给B点了100次赞你只记了1条边原始数据中A和B之间常存在多条同类型边如100次点赞、5次转账。如果直接用G.add_edge(A, B)NetworkX会覆盖前99条只保留最后1条导致强度信息完全丢失。实操方案必须用nx.MultiDiGraph或手动聚合。我们倾向后者因为更可控# 按source-target-behavior_type聚合 df_aggregated df_edges.groupby([source, target, behavior_type]).agg({ weight: sum, # 对已校准的权重求和 timestamp: [min, max] # 记录首次和末次发生时间 }).reset_index() # 展开为单边记录每条记录代表一种行为的一次聚合 df_final df_aggregated.copy() df_final[weight] df_aggregated[(weight, sum)] df_final[first_time] df_aggregated[(timestamp, min)] df_final[last_time] df_aggregated[(timestamp, max)] df_final df_final.drop(columns[(weight, sum), (timestamp, min), (timestamp, max)])然后用聚合后的df_final构建图。这样A→B的点赞边权重就是100而非1。这对计算加权中心性至关重要。例如nx.betweenness_centrality(G, weightweight)会自动用边权重作为距离倒数权重越大路径越“短”中介性计算才准确。3.7 关卡七动态网络的时间切片——如何让“昨天的网络”和“今天的网络”可比静态网络分析无法捕捉演化规律。但简单按天切分会产生大量碎片化小图如某天只有3条边导致社区发现算法崩溃。我们需要平衡时间粒度与网络密度。实操方案采用自适应滑动窗口。核心思想以网络连通性为锚点而非固定时间。步骤1计算每日新增边数找到“活跃阈值”。比如过去30天日均新增边数为5000则设定阈值为3000。步骤2从首日开始累积当累积边数≥3000时切出第一个时间片继续累积直到下一个3000边切出第二个时间片……步骤3对每个时间片构建子图并计算核心指标如平均聚类系数、模块度Q值。代码实现def slice_by_edge_count(df_edges, min_edges3000): slices [] current_slice [] edge_count 0 # 按时间排序 df_sorted df_edges.sort_values(timestamp) for _, row in df_sorted.iterrows(): current_slice.append(row) edge_count 1 if edge_count min_edges: slices.append(pd.DataFrame(current_slice)) current_slice [] edge_count 0 # 处理剩余不足min_edges的边 if current_slice: if slices: # 合并到最后一个slice slices[-1] pd.concat([slices[-1], pd.DataFrame(current_slice)]) else: slices.append(pd.DataFrame(current_slice)) return slices # 使用 time_slices slice_by_edge_count(df_edges, min_edges3000) for i, slice_df in enumerate(time_slices): G_slice nx.from_pandas_edgelist(slice_df, source, target, weight, create_usingnx.DiGraph()) modularity community.modularity(community.best_partition(G_slice), G_slice) print(fSlice {i}: {len(slice_df)} edges, Modularity {modularity:.4f})这种方法让每个时间片都有足够密度支撑算法稳定运行同时真实反映网络演化节奏。在某社交App的冷启动分析中我们用此法发现用户关系网络的模块度Q值在第17天达到峰值0.63之后缓慢下降这精准对应了产品团队上线“兴趣小组”功能的日期——证明新功能成功催化了社区形成。这七道关卡每一道都曾让我在凌晨三点对着报错日志抓狂。但跨过去之后你得到的不再是一张漂亮的图而是一个真正能呼吸、能演化的业务数字孪生体。4. 实操过程详解从零构建一个可落地的SNA分析流水线现在我们把前面所有原则和技巧组装成一条端到端的、可直接复制粘贴的Python分析流水线。以“识别某知识付费平台的关键传播节点KPN”为具体任务完整演示从原始数据到业务决策的全过程。所有代码均经过生产环境验证参数已根据200万用户规模优化。4.1 环境准备与依赖安装别跳过这一步。SNA对库版本极其敏感尤其是igraph和leidenalg不同版本API差异巨大。我们锁定以下组合2024年实测最稳# 创建独立虚拟环境强烈推荐 python -m venv sna_env source sna_env/bin/activate # Linux/Mac # sna_env\Scripts\activate # Windows # 安装核心库注意顺序和版本 pip install numpy1.24.3 pandas2.0.3 matplotlib3.7.2 pip install networkx3.1 # 3.2有已知内存泄漏bug pip install scikit-learn1.3.0 pip install python-louvain0.16 # 注意不是python-louvain2 pip install leidenalg0.10.2 pip install igraph0.10.4 # 必须用conda安装pip会失败 # conda install -c conda-forge python-igraph pip install tqdm4.65.0提示python-louvain和leidenalg不能共存于同一环境。Louvain适合中小网络50万节点Leiden在大规模网络上更快更准。我们这里用Leiden因其模块度优化更优。4.2 原始数据样例与ID映射假设你拿到三份CSV文件users.csv:user_id, phone_hash, region, device_fingerprint, register_timeinteractions.csv:user_a, user_b, behavior_type, item_id, timestamp含1000万条记录items.csv:item_id, category, sales_volume_30d首先执行ID标准化关卡一import pandas as pd import numpy as np from tqdm import tqdm # 读取数据 users pd.read_csv(users.csv) interactions pd.read_csv(interactions.csv) items pd.read_csv(items.csv) # 步骤1建立phone_hash到biz_uid的映射 phone_to_biz {} for _, row in users.iterrows(): if pd.notna(row[phone_hash]): if row[phone_hash] not in phone_to_biz: phone_to_biz[row[phone_hash]] fbiz_{len(phone_to_biz)1} # 步骤2建立device_fingerprint到biz_uid的映射对无手机号用户 device_to_biz {} for _, row in users.iterrows(): if pd.isna(row[phone_hash]) and pd.notna(row[device_fingerprint]): if row[device_fingerprint] not in device_to_biz: device_to_biz[row[device_fingerprint]] fbiz_{len(phone_to_biz)len(device_to_biz)1} # 步骤3为每个user_id分配biz_uid users[biz_uid] None for idx, row in users.iterrows(): if pd.notna(row[phone_hash]): users.loc[idx, biz_uid] phone_to_biz[row[phone_hash]] elif pd.notna(row[device_fingerprint]): users.loc[idx, biz_uid] device_to_biz[row[device_fingerprint]] else: users.loc[idx, biz_uid] fbiz_{len(phone_to_biz)len(device_to_biz)idx1} # 步骤4映射interactions表 user_to_biz dict(zip(users[user_id], users[biz_uid])) interactions[biz_a] interactions[user_a].map(user_to_biz) interactions[biz_b] interactions[user_b].map(user_to_biz) # 过滤掉映射失败的记录如user_a不存在于users表 interactions interactions.dropna(subset[biz_a, biz_b]) print(fID mapping done. Interactions reduced from {len(interactions)} to {len(interactions)})运行后interactions表新增biz_a和biz_b列所有ID已统一为biz_uid格式。4.3 边权重校准与网络构建基于业务规则校准权重关卡二# 合并items表获取商品热度 interactions interactions.merge(items[[item_id, sales_volume_30d]], onitem_id, howleft) interactions[sales_volume_30d] interactions[sales_volume_30d].fillna(1) # 默认热度为1 # 定义基础权重和热度放大系数 base_weights {follow: 0.8, comment: 0.6, share: 0.9, like: 0.2} interactions[base_weight] interactions[behavior_type].map(base_weights) # 计算最终权重base × (1 log2(sales_volume 1)) interactions[weight] interactions[base_weight] * (1 np.log2(interactions[sales_volume_30d] 1)) # 归一化到0-1区间 interactions[weight] (interactions[weight] - interactions[weight].min()) / (interactions[weight].max() - interactions[weight].min()) # 过滤自环关卡三 interactions interactions[interactions[biz_a] ! interactions[biz_b]] # 聚合多重边关卡六 df_agg interactions.groupby([biz_a, biz_b, behavior_type]).agg({ weight: sum, timestamp: min }).reset_index() df_agg[weight] df_agg[weight] / df_agg[weight].max() # 再次归一化 # 构建图使用稀疏存储 import networkx as nx G nx.DiGraph() for _, row in df_agg.iterrows(): G.add_edge(row[biz_a], row[biz_b], weightrow[weight]) print(nx.info(G)) # 输出类似Name: , Type: DiGraph, Number of nodes: 1842301, Number of edges: 7823456, Average degree: 4.2470此时G是一个包含184万节点、782万边的加权有向图内存占用约2.1GB完全可操作。4.4 关键节点识别四维评估法单纯用PageRank或度中心性会漏掉关键角色。我们采用四维融合评估影响力Influence: PageRank衡量被多少重要节点指向枢纽性Hubness: Out-degree Centrality衡量指向多少节点中介性Brokerage: Betweenness Centrality衡量控制信息流的能力稳定性Stability: 节点在时间切片中的存活率关卡七from community import community_louvain import leidenalg as la import igraph as ig # 步骤1计算PageRank影响力 pr nx.pagerank(G, alpha0.85, max_iter100, tol1e-06) nx.set_node_attributes(G, pr, pagerank) # 步骤2计算出度中心性枢纽性 out_degree {n: G.out_degree(n, weightweight) for n in G.nodes()} nx.set_node_attributes(G, out_degree, out_degree_weighted) # 步骤3计算中介性中介性- 注意对大网络用approximate算法 bc nx.betweenness_centrality(G, k10000, weightweight, endpointsFalse) # k10000采样1万个节点 nx.set_node_attributes(G, bc, betweenness) # 步骤4时间切片稳定性需先切片 time_slices slice_by_edge_count(df_agg, min_edges50000) # 每片5万边 stability_scores {} for i, slice_df in enumerate(time_slices): G_slice nx.from_pandas_edgelist(slice_df, biz_a, biz_b, weight, create_usingnx.DiGraph()) slice_nodes set(G_slice.nodes()) for node in slice_nodes: stability_scores[node] stability_scores.get(node, 0) 1 # 归一化稳定性 total_slices len(time_slices) for node in G.nodes(): stability_scores[node] stability_scores.get(node, 0) / total_slices nx.set_node_attributes(G, stability_scores, stability) # 步骤5四维标准化与融合 node_data [] for node in G.nodes(): data { node: node, pagerank: pr.get(node, 0), out_degree: out_degree.get(node, 0), betweenness: bc.get(node, 0), stability: stability_scores.get(node, 0) } node_data.append(data) df_nodes pd.DataFrame(node_data) # 标准化Min-Max for col in [pagerank, out_degree, betweenness, stability]: df_nodes[col _norm] (df_nodes[col] - df_nodes[col].min()) / (df_nodes[col].max() - df_nodes[col].min() 1e-8) # 融合得分权重根据业务调整影响力0.4枢纽性0.3中介性0.2稳定性0.1 df_nodes[kpn_score] ( 0.4 * df_nodes[pagerank_norm] 0.3 * df_nodes[out_degree_norm] 0.2 * df_nodes[