带有 networkx 有向图链接列表的数据框

dataframe with list of links to networkx digraph

我有一个包含 linked 文档集合的数据框,我想将其转换为具有边权重 link_weight 和节点属性 doc_attribute 的有向图。 有效 的方法是什么?我在这里提供了一个小示例,但实际数据针对的是 ~100k 文档,平均每个文档 ~10 links。

示例:

import pandas as pd
import numpy as np
from string import ascii_lowercase

N = 100
doc_ids = [f"doc_{j}" for j in range(N)]
doc_attrs = np.random.choice(list(ascii_lowercase), N)
link_weights = np.random.choice(10, N)
links = [random.choices(doc_ids, k=np.random.choice(4)) for j in range(N)]
    
df = pd.DataFrame(data={"doc_attribute": doc_attrs, "link_weight":link_weights, "linked_docs":links}, index=doc_ids) 

通知文档可能不包含 linked 文档或 link to docs:

      doc_attribute  link_weight               linked_docs
doc_0             b            3          [doc_55, doc_67]
doc_1             i            2                        []
doc_2             l            4                  [doc_72]
doc_3             f            1                  [doc_78]
doc_4             e            6                  [doc_50]
doc_5             k            3                  [doc_24]
doc_6             j            6    [doc_3, doc_6, doc_63]
doc_7             g            4  [doc_11, doc_59, doc_59]
doc_8             f            9                        []
doc_9             f            8                  [doc_57]

所需输出:nx.DiGraph 对象,其节点由 df.index、指向边 linked_docs、节点属性 doc_attribute 和 link 权重 link_weight。 Networkx 有 from_dataframe 功能,但适用于不同的输入格式。我不知道创建有向图的最有效方法。

首先使用pandas将链表列扩展为行,然后使用from_pandas_edgelist:

df["source_docs"] = df.index 
df = df.explode("linked_docs")
graph = nx.from_pandas_edgelist(
    df, 
    source="source_docs", 
    target="linked_docs",
    edge_attr="link_weight",
    create_using=nx.DiGraph,
)    
nx.set_node_attributes(graph, df['doc_attribute'].to_dict(), 'doc_attribute')
# remove the node created for source docs without links
graph.remove_node(np.nan)

示例输出:

df.head(10)

"""
      doc_attribute  link_weight linked_docs source_docs
doc_0             l            4      doc_62       doc_0
doc_1             k            1      doc_24       doc_1
doc_1             k            1      doc_20       doc_1
doc_2             g            1      doc_25       doc_2
doc_2             g            1      doc_47       doc_2
doc_3             u            6      doc_58       doc_3
doc_4             j            6      doc_83       doc_4
doc_4             j            6      doc_73       doc_4
doc_4             j            6      doc_51       doc_4
doc_5             w            2      doc_75       doc_5
"""

len(graph.nodes) # 100

sorted(graph.edges.data(), key=lambda x: int(x[0].split("_")[-1]))[0:10]
"""
[('doc_0', 'doc_62', {'link_weight': 4}),
 ('doc_1', 'doc_24', {'link_weight': 1}),
 ('doc_1', 'doc_20', {'link_weight': 1}),
 ('doc_2', 'doc_25', {'link_weight': 1}),
 ('doc_2', 'doc_47', {'link_weight': 1}),
 ('doc_3', 'doc_58', {'link_weight': 6}),
 ('doc_4', 'doc_83', {'link_weight': 6}),
 ('doc_4', 'doc_73', {'link_weight': 6}),
 ('doc_4', 'doc_51', {'link_weight': 6}),
 ('doc_5', 'doc_75', {'link_weight': 2})
]
"""


sorted(graph.nodes.data(), key=lambda x: int(x[0].split("_")[-1]))[0:10]
"""
[('doc_0', {'doc_attribute': 'l'}),
 ('doc_1', {'doc_attribute': 'k'}),
 ('doc_2', {'doc_attribute': 'g'}),
 ('doc_3', {'doc_attribute': 'u'}),
 ('doc_4', {'doc_attribute': 'j'}),
 ('doc_5', {'doc_attribute': 'w'}),
 ('doc_6', {'doc_attribute': 'b'}),
 ('doc_7', {'doc_attribute': 's'}),
 ('doc_8', {'doc_attribute': 'l'}),
 ('doc_9', {'doc_attribute': 'e'})
]
"""

然而,对于 N=100000,这比其他解决方案慢 40%,n_links 在我的机器上是 ~10,两个解决方案都超过一秒。

你可以使用 from_dict_of_dicts and then set the attributes of the nodes with set_node_attributes:

dod = {d['index']: {t: {"weight": d['link_weight']} for t in d['linked_docs']} for d in
       df[['linked_docs', 'link_weight']].reset_index().to_dict('records')}

dg = nx.from_dict_of_dicts(dod, create_using=nx.DiGraph)
nx.set_node_attributes(dg, df['doc_attribute'].to_dict(), 'doc_attribute')