Рекурсивная функция в фрейме данных pandas

#python #pandas #recursion

#python #pandas #рекурсия

Вопрос:

У меня есть следующий фрейм данных, созданный

 import pandas as pd    
df = pd.DataFrame({'parent': ['AC1', 'AC2', 'AC3', 'AC1', 'AC11', 'AC5', 'AC5', 'AC6', 'AC8', 'AC9'],
                   'child': ['AC2', 'AC3', 'AC4', 'AC11', 'AC12', 'AC2', 'AC6', 'AC7', 'AC9', 'AC10']})
  

Которая выводит следующее:

     parent  child
0   AC1     AC2
1   AC2     AC3
2   AC3     AC4
3   AC1     AC11
4   AC11    AC12
5   AC5     AC2
6   AC5     AC6
7   AC6     AC7
8   AC8     AC9
9   AC9     AC10
  

Я хотел бы создать результирующий фрейм данных, в котором каждый из родительских элементов (что означает, что он не существует в дочернем столбце) указан с самыми последними дочерними элементами.

 df_result = pd.DataFrame({'parent': ['AC1', 'AC1', 'AC5', 'AC5', 'AC8', 'AC2'],
                     'child': ['AC4', 'AC12', 'AC4', 'AC7', 'AC10', 'AC4']})
    parent  child
0   AC1     AC4
1   AC1     AC12
2   AC5     AC4
3   AC5     AC7
4   AC8     AC10
5   AC2     AC4
  

Я запустил следующую функцию, но я не уверен, как ее завершить.

 def get_child(df):
result = {}
if df['parent'] not in df['child']:
    return result[df['parent']]
  

Комментарии:

1. Ваш ожидаемый результат кажется немного несовместимым с вашим описанием. Вы определяете родителей как «не существующие в дочернем столбце», но AC2 рассматривается как родительский для целей вывода.

Ответ №1:

Это древовидная структура, особый тип графика. Фрейм данных — не особенно удобный способ представления дерева; я рекомендую вам переключиться на networkx или какой-либо другой пакет на основе графов. Затем посмотрите, как выполнить простой обход пути; вы найдете прямую поддержку в документации пакета graph.

Если вы настаиваете на том, чтобы сделать это самостоятельно — что является разумным упражнением в программировании — вам просто нужно что-то вроде этого псевдокода

 for each parent not in "child" column:
    here = parent
    while here in parent column:
        here = here["child"]

    record (parent, here) pair
  

Комментарии:

1. Спасибо. Но мне нужен результат в фрейме данных, поскольку я присоединяюсь обратно к другим фреймам данных и, возможно, записываю в Redshift. Я взгляну на networkx.

2. Спасибо за разъяснение. Вот почему я дал вам алгоритм обхода, а также некоторые условия поиска.

Ответ №2:

Хотя ваш ожидаемый результат кажется несколько несоответствующим вашему описанию (AC2 кажется, что его не следует считать родительским, поскольку он не является исходным узлом), я почти уверен, что вы хотите выполнить обход с каждого исходного узла, чтобы найти все его листья. Делать это в фрейме данных неудобно, поэтому мы можем использовать df.values и создать словарь списка смежности для представления графика. Я предполагаю, что на графике нет циклов.

 import pandas as pd
from collections import defaultdict

def find_leaves(graph, src):
    if src in graph:
        for neighbor in graph[src]:
            yield from find_leaves(graph, neighbor)
    else:
        yield src

def pair_sources_to_leaves(df):
    graph = defaultdict(list)
    children = set()

    for parent, child in df.values:
        graph[parent].append(child)
        children.add(child)

    leaves = [[x, list(find_leaves(graph, x))] 
               for x in graph if x not in children]
    return (pd.DataFrame(leaves, columns=df.columns)
              .explode(df.columns[-1])
              .reset_index(drop=True))

if __name__ == "__main__":
    df = pd.DataFrame({
        "parent": ["AC1", "AC2", "AC3", "AC1", "AC11", 
                   "AC5", "AC5", "AC6", "AC8", "AC9"],
        "child": ["AC2", "AC3", "AC4", "AC11", "AC12", 
                  "AC2", "AC6", "AC7", "AC9", "AC10"]
    })
    print(pair_sources_to_leaves(df))
  

Вывод:

   parent child
0    AC1   AC4
1    AC1  AC12
2    AC5   AC4
3    AC5   AC7
4    AC8  AC10
  

Комментарии:

1. Спасибо. Вы правы, похоже, я действительно допустил ошибку с AC2. Одна вещь, в которой я запутался, — это как вызвать функцию. Создаю ли я свой df внутри него или это было просто для того, чтобы показать пример.

2. Я не совсем уверен, что вы имеете в виду. Ваш ввод — это жестко закодированный df в main, как вы показываете в своем вопросе, а результатом является окончательный df после построения графика и выполнения обходов. Я обновил код, чтобы сделать его немного понятнее, что pair_sources_to_leaves превращает это преобразование в черный ящик — возможно, вам потребуется добавить параметры, если вы ожидаете, что ваши столбцы будут динамическими.