Python - перебрать список ребер, для узлов с указанным атрибутом c найти все подключенные узлы с другим указанным атрибутом c? - PullRequest
0 голосов
/ 07 марта 2020

У меня есть список ребер, содержащий 24 000 разных ребер между продуктами. Между A и B создается ребро, если продукт B является подкомпонентом A.

Список ребер имеет следующий формат:

 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

Таким образом, путем группировки по Root I хочу, чтобы для всех родителей в форме DD* и FF* были найдены дети в форме EE*, с которыми они напрямую связаны. В приведенном выше примере я хочу, чтобы выходной фрейм данных выглядел как

 Parent | Child | Root | Child Meta
   DD1     EE1    AA2      ... 
   DD1     EE2    AA2      ...
   FF1     EE3    AA2      ...

. Единственный способ, которым я знаю, как это сделать, - это перебирать pandas DataFrame и использовать рекурсивные функции, перебирающие дочерние элементы, пока я не нажму EE* ребенок. Это занимает вечность. Есть ли умный способ использовать networkx здесь, может быть? Или есть какой-нибудь другой способ сделать это, используя pandas, который будет быстрее?

1 Ответ

1 голос
/ 07 марта 2020

Если я правильно понимаю проблему, то, возможно, будет быстрее, если вы начнете снизу и найдете узлы, идущие вверх.

Поскольку вы знаете подмножество детей (E *), которое вы хотите найти, если вы начинаете с целевых потомков, все родители по определению являются частью результата, и вам не нужно фильтровать.

В простом итеративном подходе Python, что-то вроде этого будет искать все родительские узлы для "E *" children:

(Обратите внимание, что я добавил дополнительную строку с "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 перебирает всех «интересных» детей и их родителей, расширяя родителей только для новых узлов, поэтому мы никогда не работаем дважды для одного и того же узла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...