Python-遍历边缘列表,对于具有特定属性的节点,查找所有具有不同特定属性的连接节点?

时间:2020-03-07 09:08:39

标签: python pandas networkx

我有一个边缘列表,其中包含产品之间的24 000个不同的边缘。如果乘积B是A的子组件,则会在A和B之间创建一条边。

边缘列表采用以下格式:

 Parent | Child | Root | Child Meta
  AA1      BB1    AA1      ...  
  AA1      BB2    AA1      ...
  BB2      CC1    AA1      ...  
  AA2      BB3    AA2
  AA2      BB4    AA2
  BB4      CC1    AA2      ... 
  BB4      DD1    AA2      ...
  DD1      EE1    AA2
  DD1      EE2    AA2
  BB4      FF1    AA2
  FF1      GG1    AA2      ...
  GG1      EE3    AA2

因此,我希望通过对RootDD*形式的所有父母进行分组,以FF*形式找到与他们有直接关系的孩子。在上面的示例中,我希望输出数据框看起来像

EE*

我知道如何做到这一点的唯一方法是遍历pandas DataFrame并使用递归函数遍历子级,直到我碰到一个 Parent | Child | Root | Child Meta DD1 EE1 AA2 ... DD1 EE2 AA2 ... FF1 EE3 AA2 ... 子级。这需要永远。 也许这里有使用EE*的聪明方法吗?还是有其他方法可以使用速度更快的熊猫来做到这一点?

1 个答案:

答案 0 :(得分:1)

如果我对问题的理解正确,那么从底部开始并找到向上的节点可能会更快。

由于您知道要查找的子代(E *),因此如果从目标子代开始,则根据定义,所有父代都是结果的一部分,而不必进行筛选。

在普通的迭代Python方法中,类似这样的事情将找到“ E *”子代的所有父节点:

(请注意,我在“ BB3 DD1 AA2”中添加了额外的一行,以进行其他重复。)

data = """AA1      BB1    AA1
  AA1      BB2    AA1 
  BB2      CC1    AA1 
  AA2      BB3    AA2
  AA2      BB4    AA2
  BB4      CC1    AA2 
  BB3      DD1    AA2
  BB4      DD1    AA2
  DD1      EE1    AA2
  DD1      EE2    AA2
  BB4      FF1    AA2
  FF1      GG1    AA2
  GG1      EE3    AA2"""

# tuple (parent, child, root)
tuples = {tuple(l.split()) for l in data.split("\n")}

parentsByChild = {}
for node in tuples:
    p = set(parentsByChild.get(node[1], frozenset()))
    p.add(node)
    parentsByChild[node[1]] = frozenset(p)
# alternatively:
# from itertools import groupby
# parentsByChild = {c:frozenset(nodes) for c, nodes in groupby(sorted(tuples, key=lambda n: n[1]), lambda n: n[1])}

def expand(nodes):
    todo, found = set(nodes), set() 
    while todo:
        node = todo.pop()        
        if not node in found:
            found.add(node)
            todo.update((p for p in parentsByChild.get(node[0], set()) if p not in found))
    return found

leaves = {n for n in tuples if n[1].startswith("E")}
for t in expand(leaves):
    print(t)

这在边缘数量上应该是线性的:我们遍历它们一次以收集元组,然后第二次对父母进行分组。 expand调用会迭代所有“有趣”的子代及其父代,仅针对新节点扩展父代,因此我们永远不会为同一节点重复两次。