【问题标题】:Recursively list neighbors of a node in tree/directed graph, for all nodes in graph, one "level" at a time?递归地列出树/有向图中节点的邻居,对于图中的所有节点,一次一个“级别”?
【发布时间】:2020-01-02 03:32:48
【问题描述】:

我正在尝试从包含两列和多行的 Excel 工作表生成“佣金共享”组织的图表。左行包含分配给每个代理的唯一 ID。右侧的单元格列出了在左侧单元格中招募代理的代理的唯一 ID。我手动插入了一个新的第 1 行,其中包含以下值: 代理和雇佣人

我使用以下方法将 excel 数据转换为 pandas Edgelist:

import pandas as pd
XL='path to Excel file'
df=pd.read_excel(XL,sheet_name=3)
G=nx.from_pandas_edgelist(df,'agent','hired_by',create_using=nx.Graph)

为了这些目的,使用 nx.Graph 而不是 nx.DiGraph 很重要,因为“连接方向”并不重要。重要的是“级别”,即根节点和代理之间的跳数,用于确定补偿属性。

然后,我可以使用以下代码从根目录逐级确定层次结构,然后可以将其粘贴到工作中的 .dot(有向图)文件中,只需进行少量编辑。在哪里工作,我的意思是它以图形方式分隔各个级别,因为它由以下行组成:

“node01”-> {“node02”、“node03”、“node04”、“node05”、“node06”}

for n in G.nodes():
if len(nx.shortest_path(G, 'node01', n)) == 1: 
print(nx.shortest_path(G, 'node01', n),len(nx.shortest_path(G, 'node01', n)))

这会产生如下输出:

"node01" -> {"node02", "node03", "node04", "node05", "node06"} 1
...
"node06" -> {"node10", "node11", "node07"} 2
...
"node17" -> {"node21", "node22"} 4

这很好用,唯一的问题是我必须手动更改上面代码中每个级别的数字“1”,并将输出复制并粘贴到我的 .dot 文件中,然后重新排列行,因为输出不会将所有“级别 3”组合在一起。

目前这是可以接受的混乱程度,但我可能还有更多记录需要处理。

有没有办法让这项工作递归地从一个级别到另一个级别?

谢谢!

【问题讨论】:

  • 这个问题还有意义吗?
  • 是的。可以告诉我如何对以下代码进行递归,而不必手动增加节点编号:``` //read_excel,然后将 convert_to_pandas_edgelist 作为 Agraph 非有向图 // ///then: for n in G.nodes (): if len(nx.shortest_path(G, 'node01', n)) == 1: /// 我手动增加了这个值 print(nx.shortest_path(G, 'node01', n),len(nx. shortest_path(G, 'node01', n)))
  • 我将根据网站指南编辑原始帖子。

标签: pandas networking graphviz python-3.7 dot


【解决方案1】:

如果您已经创建了有向图(称为G),您可以使用以下代码来实现您要查找的内容:

from collections import defaultdict


recruit_dict = defaultdict(set)

# Use .edges instead of nodes to get connections between nodes
for agent, recruited in G.edges:
    if agent == recruited:
        continue
    recruit_dict[agent].add(recruited)

with open("dot.txt", "w") as out_handle:
    for parent, children in recruit_dict.items():
        children_str = ', '.join(
            f'"{child}"' for child in sorted(children)
        )
        out_handle.write(f"\"{parent}\" -> {children_str}\n")

根据您的示例输入,dot.txt 的内容:

"1" -> "2", "3", "6"
"2" -> "4", "7"
"3" -> "5", "8"

【讨论】:

  • @TTG 你真的有一个networkx.DiGraph 对象,它是按照你建议的方式构建的吗?如果你这样做了,它应该只是“工作”而不做任何更改(变量名除外)。
【解决方案2】:

如果您有兴趣根据包含“agent”和“hired_by”列的数据集(如您提供的第一个平面列表)获取每个其他代理雇用的代理,您可以使用像他这样的集体声明:

# case 1: directly hired
df_work.groupby('hired_by').agg(dict(agent=list))

现在,如果您想获取间接雇用人员的列表,即您的示例中的代理 1 雇用了代理 2-8(4、5、7 和 8 间接)。然后你可以试试下面的代码(同样df 是原始数据框):

# case 2: directly and indirectly hired
df_work= df.copy()
# add a distance since you mentioned, you want to know the "level"
df_work['distance']= 1

# construct a flattened data frame in which the
# relationship indirectly-hired-by is resolved
old_size= 0
new_size= df_work.shape[0]
# loop as long as the size doesn't change 
# (btw. it's guaranteed to terminate because
# you have a finite set of agentoyees, and thus
# also only a finite but maybe large number
# of distinct result records)
while old_size != new_size:
    # the size changed, so try again
    df_merged= df_work.merge(df_work, left_on='hired_by', right_on='agent', suffixes=['_agent', '_hired'])
    df_merged['distance']= df_merged['distance_agent'] + df_merged['distance_hired']
    df_merged.rename({'agent_agent': 'agent', 'hired_by_hired': 'hired_by'}, axis='columns', inplace=True)
    df_work= pd.concat([df_work, df_merged[['agent', 'hired_by', 'distance']]], axis='index', ignore_index=True, sort=False)
    # now get rid of the duplicates (ignoring the distance)
    df_work.drop_duplicates(['agent', 'hired_by'], keep='first', inplace=True)
    old_size= new_size
    new_size= df_work.shape[0]

# now construct the lists
df_work.groupby('hired_by').agg(dict(agent=list))

最后一组返回直接和间接雇用的所有代理的列表,其中包含雇用任何人的代理。 逻辑需要log_2(max_depth) 步骤来构建扁平化表示,其中max_depth 是从代理到间接雇用他的代理的最长路径(我想你会称之为最高级别)。

如果将此逻辑应用于您的测试数据:

raw="""1 1
2 1
3 1
4 2
5 3
6 1
7 2
8 3"""

import io
df= pd.read_csv(io.StringIO(raw), sep='\s+', dtype='Int64')
df.columns= ['agent', 'hired_by']

输出是:

# case 1: directly hired
df.groupby('hired_by').agg(dict(agent=list))
Out[1]: 
                agent
hired_by           
1         [2, 3, 6]
2            [4, 7]
3            [5, 8]

# case 2: directly and indirectly hired
Out[2]: 
                            agent
hired_by                       
1         [2, 3, 6, 4, 7, 5, 8]
2                        [4, 7]
3                        [5, 8]

如果您需要将其格式化为示例中的格式,您可以像这样包装它:

for hired_by, agents in df_work.groupby('hired_by').agg(dict(agent=list)).iterrows():
    print('{hired_by} -> {agents}'.format(hired_by=hired_by, agents=', '.join(map(str, agents['agent']))))

# case 2: directly and indirectly hired
1 -> 2, 3, 6, 4, 7, 5, 8
2 -> 4, 7
3 -> 5, 8

【讨论】:

  • 我得到了这个进行一些调整。感谢您花时间在这方面工作。但是,我不知道如何在 pd_work 或 pd_merged 中使用“级别”。
  • 我从中得到了一些用处。感谢您花时间在这方面工作。但是,我无法弄清楚如何在 pd_work 或 pd_merged 中使用“级别”,我需要根据“级别”在 .dot 文件中应用 Graphviz 属性(例如节点大小/颜色)。我认为这可以通过将 pandas 数据移动到 networkX 有向图并基于 list(DG.predecessors('NameOfNode') 递归查询并计算在到达“根”节点之前需要完成多少次来完成。但我也想不出如何做到这一点。不过,感谢您的出色帮助!
  • 我不确定我是否得到它。你想对关卡做什么?使用df_merged['distance'] 不是为了您的目的吗?
猜你喜欢
  • 2019-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-22
  • 1970-01-01
  • 1970-01-01
  • 2011-04-27
相关资源
最近更新 更多