Иерархия родительских дочерних путей в Python с использованием панд

Я пытаюсь получить дочернюю родительскую иерархию в новом столбце панд. В настоящее время мой код выглядит так, но он ищет всех родителей, но должен искать тот, который находится в соответствующей строке.

Вот пример того, как это должно работать.

Базовая таблица (вход):

CHILD_ID PARENT_ID ПУНКТ_1 ITEM_A ПУНКТ_2 ITEM_B ITEM_A ITEM_X ITEM_A ITEM_Y ITEM_B ITEM_Z

Первая итерация

CHILD_ID PARENT_ID Иерархия ПУНКТ_1 ITEM_A ПУНКТ_1, ПУНКТ_А ПУНКТ_2 ITEM_B ПУНКТ_2, ПУНКТ_Б ITEM_A ITEM_X ITEM_A, ITEM_X ITEM_A ITEM_Y ITEM_A, ITEM_Y ITEM_B ITEM_Z ITEM_B, ITEM_Z

Вторая итерация (в данном случае желаемый результат, поскольку родителей больше нет)

CHILD_ID PARENT_ID Иерархия ПУНКТ_1 ITEM_A ITEM_1, ITEM_A, ITEM_X ПУНКТ_1 ITEM_A ITEM_1, ITEM_A, ITEM_Y ПУНКТ_2 ITEM_B ITEM_2, ITEM_B, ITEM_Z ITEM_A ITEM_X ITEM_A, ITEM_X ITEM_A ITEM_Y ITEM_A, ITEM_Y ITEM_B ITEM_Z ITEM_B, ITEM_Z

Я изо всех сил пытаюсь добиться этого результата, вот мой код:

def get_parents(child_id):
    list_of_parents = []

    def dfs(child_id, parents_list):
        parent_ids = df[df["child_id"] == child_id]["parent_id"].values
        if len(parent_ids) == 0:
            return
        for parent_id in parent_ids:
            if parent_id not in parents_list:
                parents_list.append(parent_id)
                dfs(parent_id, parents_list)

    dfs(child_id, list_of_parents)
    return list_of_parents

df["parent_hierarchy"] = df["child_id"].apply(get_parents)

пример проблемы:

При попытке запустить код на всем кадре данных возникает эта ошибка: NetworkXNoPath: нет пути между 4614837_12_13200 и 4975995_5_13200.

После фильтрации фрейма данных только по этим идентификаторам с обеих сторон код работал, но все еще не работает для входного фрейма данных.

родительский_ид child_id 4975995_5_13200 4789551_5_13200 5003373_3_13200 4614837_12_13200 4975995_5_13200 4602153_19_13200 4975995_5_13200 4789551_7_13200 4974894_5_13200 4614837_12_13200 4975995_5_13200 4789551_10_13200 4975995_5_13200 4789551_3_13200 4975995_5_13200 4789551_6_13200 4975995_5_13200 4789551_2_13200

Код SQL, который использовался в черновике, но в конечном итоге будет на Python.

SELECT
CONCAT_WS(
  "|", 
  concat(
    bl1.parent_item_id 
  ), 
  IF(
    bl2.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl2.parent_item_id 
    )
  ), 
  IF(
    bl3.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl3.parent_item_id 
    )
  ), 
  IF(
    bl4.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl4.parent_item_id 
    )
  ), 
  IF(
    bl5.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl5.parent_item_id 
    )
  ), 
  IF(
    bl6.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl6.parent_item_id 
    )
  ), 
  IF(
    bl7.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl7.parent_item_id 
    )
  ), 
  IF(
    bl8.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl8.parent_item_id 
    )
  ), 
  IF(
    bl9.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl9.parent_item_id 
    )
  ), 
  IF(
    bl10.parent_item_id IS NULL, 
    NULL, 
    concat(
      bl10.parent_item_id 
    )
  )
) AS PATH -- joins to find 10 levels of items
FROM 
  baseln AS bl1 
  LEFT JOIN baseln AS bl2 ON bl1.parent_item_id = bl2.child_item_id 
  AND bl1.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl3 ON bl2.parent_item_id = bl3.child_item_id 
  AND bl2.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl4 ON bl2.parent_item_id = bl4.child_item_id 
  AND bl3.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl5 ON bl2.parent_item_id = bl5.child_item_id 
  AND bl4.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl6 ON bl2.parent_item_id = bl6.child_item_id 
  AND bl5.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl7 ON bl2.parent_item_id = bl7.child_item_id 
  AND bl6.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl8 ON bl2.parent_item_id = bl8.child_item_id 
  AND bl7.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl9 ON bl2.parent_item_id = bl9.child_item_id 
  AND bl8.child_item_id IS NOT NULL 
  LEFT JOIN baseln AS bl10 ON bl2.parent_item_id = bl10.child_item_id 
  AND bl9.child_item_id IS NOT NULL 

NetworkX выдает ошибку с приведенным ниже кадром данных

{'parent_id':['4974894_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','5003373_3_13200','4975995_5_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','4975995_5_13200','4974894_5_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','4975995_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','4975995_5_13200','5003373_3_13200','4974894_5_13200','5003373_3_13200','4974894_5_13200','4975995_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','4975995_5_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200','4974894_5_13200','4975995_5_13200','5003373_3_13200','4974894_5_13200','4974894_5_13200','5003373_3_13200','5003373_3_13200','5003373_3_13200'],'child_id':['4602363_15_13200','4613145_13_13200','4602528_21_13200','4613145_11_13200','4614837_8_13200','4613145_11_13200','4613370_11_13200','4595322_17_13200','4602576_18_13200','4595310_14_13200','4595310_14_13200','4602528_23_13200','4602528_22_13200','4595322_16_13200','4595310_13_13200','4602384_16_13200','4602153_18_13200','4602384_17_13200','4602576_17_13200','4602528_18_13200','4789551_5_13200','4613046_18_13200','4602576_17_13200','4595310_9_13200','4602363_10_13200','4595322_16_13200','4602528_21_13200','4602171_17_13200','4602153_18_13200','4602153_12_13200','4595322_17_13200','4602576_12_13200','4613145_9_13200','4602576_11_13200','4602363_15_13200','4602171_12_13200','4602528_15_13200','4613370_15_13200','4614837_12_13200','4613370_14_13200','4602363_16_13200','4595322_11_13200','4595322_10_13200','4613370_15_13200','4602153_19_13200','4614837_11_13200','4613046_13_13200','4602384_10_13200','4602363_16_13200','4613370_14_13200','4789551_7_13200','4602171_10_13200','4595322_13_13200','4613046_18_13200','4602363_14_13200','4614837_12_13200','4602384_17_13200','4602576_16_13200','4613370_16_13200','4602528_18_13200','4602528_22_13200','4613145_14_13200','4602384_16_13200','4789551_10_13200','4602528_23_13200','4613046_17_13200','4602153_19_13200','4602363_14_13200','4789551_3_13200','4595310_13_13200','4602153_19_13200','4613370_16_13200','4602363_9_13200','4789551_6_13200','4602576_18_13200','4613145_14_13200','4595322_13_13200','4602384_13_13200','4602576_16_13200','4789551_2_13200','4614837_11_13200','4613145_13_13200','4602171_17_13200','4613046_17_13200','4614837_7_13200','4602153_11_13200']}

Входные данные и ожидаемый результат неясны. Вам нужны все итерации или только финальное состояние?

mozway 14.03.2024 13:34

@mozway Я отредактировал ввод. Теперь должно быть ясно

Rafał 14.03.2024 13:49

Не полностью, вы не сказали, хотите ли вы последний шаг или все промежуточные

mozway 14.03.2024 13:59

@mozway, хорошо, поэтому я хочу, чтобы последний шаг был моим результатом. Я представил только первую итерацию как «демо-версию процесса», к сожалению.

Rafał 14.03.2024 14:03
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
4
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

usw — комбинация операций с данными и рекурсивной функции. вот новый код, вы сможете реализовать его после загрузки данных

def get_hierarchy(child_id, hierarchy=[]):
    hierarchy.append(child_id)
    parent_id = df.loc[df["CHILD_ID"] == child_id, "PARENT_ID"].values
    if len(parent_id) > 0:
        get_hierarchy(parent_id[0], hierarchy)
    return hierarchy

df["Hierarchy"] = df["CHILD_ID"].apply(get_hierarchy)

Это ответ, сгенерированный ИИ? Кажется, это не работает

mozway 14.03.2024 13:42

Обратите внимание на параметр иерархия=[].

ggorlen 15.03.2024 03:14
Ответ принят как подходящий

IIUC, используйте networkx для создания иерархии:

import networkx as nx

# create graph
G = nx.from_pandas_edgelist(df, create_using=nx.DiGraph,
                            source='CHILD_ID', target='PARENT_ID')

out = {}
# for each subgraph, form pairs of nodes -> leaf
# find the path
for c in nx.weakly_connected_components(G):
    H = G.subgraph(c)
    leaves = {n for n, d in H.out_degree() if d==0}
    for n in c-leaves:
        for l in leaves:
            try:
                out.setdefault(n, []).append(nx.shortest_path(H, n, l))
            except nx.NetworkXNoPath:
                pass

# merge to unique CHILD_ID
# recreate PARENT_ID
# reorder based on original DataFrame
out = df.merge(df[['CHILD_ID']].drop_duplicates()
                 .merge(pd.Series(out, name='Hierarchy').explode(),
                        left_on='CHILD_ID', right_index=True)
                 .assign(PARENT_ID=lambda d: d['Hierarchy'].str[1]),
               how='left')

Выход:

  CHILD_ID PARENT_ID                 Hierarchy
0   ITEM_1    ITEM_A  [ITEM_1, ITEM_A, ITEM_X]
1   ITEM_1    ITEM_A  [ITEM_1, ITEM_A, ITEM_Y]
2   ITEM_2    ITEM_B  [ITEM_2, ITEM_B, ITEM_Z]
3   ITEM_A    ITEM_X          [ITEM_A, ITEM_X]
4   ITEM_A    ITEM_Y          [ITEM_A, ITEM_Y]
5   ITEM_B    ITEM_Z          [ITEM_B, ITEM_Z]

График:

Я столкнулся с этой ошибкой. Это связано с плохим качеством данных? Я не могу проверить весь набор данных. NetworkXNoPath: нет пути между 4602576_16_13200 и 4975995_5_13200.

Rafał 14.03.2024 14:32

Нет, тогда код нужно адаптировать. Можете ли вы обновить свой пример, чтобы воспроизвести эту ошибку?

mozway 14.03.2024 14:34

Я обновил вопрос с проблемным примером.

Rafał 14.03.2024 14:43

Этот пример у меня отлично работает

mozway 14.03.2024 15:20

Да, действительно, это очень странно, потому что со всем кадром данных я получил следующую ошибку: NetworkXNoPath: нет пути между 4614837_12_13200 и 4975995_5_13200. Когда я фильтрую весь df, чтобы он содержал только эти идентификаторы в обеих комбинациях (родительский/дочерний), это работает, но не для всего фрейма данных.

Rafał 15.03.2024 08:50

Вот почему я спросил, можете ли вы сгенерировать небольшое подмножество DataFrame, которое выдает эту ошибку (возможно, выберите несколько строк случайным образом, если вы получите ошибку, экспортируйте воспроизводимый df с помощью df.to_dict('list'))

mozway 15.03.2024 10:04

Я добавил пример df, как было предложено

Rafał 15.03.2024 10:20

@Rafał, так это вызывает ошибку? Для меня это тоже работает нормально. Какие версии Python/Pandas/Networkx вы использовали?

mozway 15.03.2024 10:42

Да, словарь, который я вставил в свой вопрос, вызывает эту ошибку. NetworkXNoPath: нет пути между 4602384_10_13200 и 4975995_5_13200. Версия Python: 3.11.4 Версия NetworkX: 3.1 Версия Pandas: 2.0.3

Rafał 15.03.2024 12:26

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

mozway 15.03.2024 12:34

Другие вопросы по теме