Получение иерархии из двух столбцов, которые имеют родительские дочерние отношения

У меня есть такой фрейм данных:

data = {
    'Parent': [None, None,  'A',    'B',    'C',    'I',    'D',    'F',    'G',    'H',    'Z',    'Y',    None,None,None,None,    'AA',   'BB',   'CC',   'EE',   'FF',   None,   None],
    'Child': ['A',  'B',    'D',    'D',    'D',    'C',    'E',    'E',    'F',    'F',    'G',    'H',    'Z',    'Y',    'AA',   'BB',   'CC',   'CC',   'DD',   'DD',   'DD',   'EE',   'FF']
}

df = pd.DataFrame(data)
        
   Parent Child
0    None     A
1    None     B
2       A     D
3       B     D
4       C     D
5       I     C
6       D     E
7       F     E
8       G     F
9       H     F
10      Z     G
11      Y     H
12   None     Z
13   None     Y
14   None    AA
15   None    BB
16     AA    CC
17     BB    CC
18     CC    DD
19     EE    DD
20     FF    DD
21   None    EE
22   None    FF

Мне нужен выходной фрейм данных следующим образом:

Я попробовал использовать пакет networkx, как предложено в этом посте , Это код, который я использовал

df['parent']=df['parent'].fillna('No Parent')

leaves =set(df['parent']).difference(df['child'])
g= nx.from_pandas_edgelist(df, 'parent', 'child', create_using=nx.DiGraph())
ancestors = {
    n: nx.algorithms.dag.ancestors(g, n) for n in leaves
}

df1=(pd.DataFrame.from_dict(ancestors, orient='index')
 .rename(lambda x: 'parent_{}'.format(x+1), axis=1)
 .rename_axis('child')
 .fillna('')
 )

Но я получаю пустой фрейм данных. Есть ли элегантный способ добиться этого?

🤔 А знаете ли вы, что...
Python позволяет создавать сценарии для автоматизации задач и обработки данных.


1
79
2

Ответы:

Решено

Один из вариантов — сделать финальный DataFrame from_dict из предшественников :

DG = nx.from_pandas_edgelist(
    df.fillna("#"), "parent", "child", create_using=nx.DiGraph
)

DG.remove_node("#") # remove the placeholder

out = (
    pd.DataFrame.from_dict(
        {n: DG.predecessors(n) for n in DG}, orient = "index"
    ).rename(columns=lambda c: f"Parent {c+1}").reset_index(names = "Child")
)

# {"Parent 1": "deepskyblue", "Parent 2": "lightcoral", "Parent 3": "springgreen"}


Используйте cumcount, чтобы отличить нескольких родителей, затем pivot.

Используйте sort_values, если вам важен порядок вывода, иначе вы можете удалить его.

out = (
    df.assign(rank=df.groupby("Child").cumcount() + 1)
    .pivot(index = "Child", columns = "rank", values = "Parent")
    .add_prefix("Parent ")
    .sort_values(by = "Child", key=lambda col: col.map(lambda x: (len(x), x)))
    .reset_index()
)
out.columns.name = None
   Child Parent 1 Parent 2 Parent 3
0      A     None      NaN      NaN
1      B     None      NaN      NaN
2      C        I      NaN      NaN
3      D        A        B        C
4      E        D        F      NaN
5      F        G        H      NaN
6      G        Z      NaN      NaN
7      H        Y      NaN      NaN
8      Y     None      NaN      NaN
9      Z     None      NaN      NaN
10    AA     None      NaN      NaN
11    BB     None      NaN      NaN
12    CC       AA       BB      NaN
13    DD       CC       EE       FF
14    EE     None      NaN      NaN
15    FF     None      NaN      NaN