查找 Python 中点的所有后代

Find all descendants for points in Python

我需要获取所有用 side_a - side_b 表示的链接的后代点(在一个数据框中),直到到达每个 side_a 他们的 end_point (在其他数据框)。所以:

df1:
side_a   side_b
  a        b
  b        c
  c        d
  k        l
  l        m
  l        n
  p        q
  q        r
  r        s

df2:
side_a    end_point
  a          c
  b          c
  c          c
  k          m
  k          n
  l          m
  l          n
  p          s
  q          s
  r          s

重点是获取每个 side_a 值的所有点,直到从 df2 达到该值的 end_point。 如果它有两个 end_point 值(就像 "k" 那样),它应该是两个列表。

我有一些代码,但不是用这种方法编写的,如果 df1['side_a'] == df2['end_points'] 它会删除 df1 中的所有行,这会导致某些问题。但是,如果有人要我 post 我当然会的代码。

期望的输出是这样的:

side_a    end_point
  a          [b, c]
  b          [c]
  c          [c]
  k          [l, m]
  k          [l, n]
  l          [m]
  l          [n]
  p          [q, r, s]
  q          [r, s]
  r          [s]

还有一件事,如果两边相同,那一点根本不需要列出,我可以稍后再追加,不管它更容易。

import pandas as pd
import numpy as np
import itertools

def get_child_list(df, parent_id):
    list_of_children = []
    list_of_children.append(df[df['side_a'] == parent_id]['side_b'].values)
    for c_, r_ in df[df['side_a'] == parent_id].iterrows():
        if r_['side_b'] != parent_id:
            list_of_children.append(get_child_list(df, r_['side_b']))

    # to flatten the list 
    list_of_children =  [item for sublist in list_of_children for item in sublist]
    return list_of_children

new_df = pd.DataFrame(columns=['side_a', 'list_of_children'])
for index, row in df1.iterrows():
    temp_df = pd.DataFrame(columns=['side_a', 'list_of_children'])
    temp_df['list_of_children'] = pd.Series(get_child_list(df1, row['side_a']))
    temp_df['side_a'] = row['side_a']

    new_df = new_df.append(temp_df)

因此,此代码的问题在于,如果我从 df2 中删除 side_a 等于 end_point 的行,该代码会起作用。我不知道如何实现条件,即如果在 side_b 列中捕获 df2,则停止,不要继续。

欢迎任何帮助或提示,真的。 提前致谢。

您的规则不一致且定义不明确,因此您可能需要在各处添加一些限制条件,因为不清楚您的确切要求。通过组织数据结构来解决问题构建更强大的遍历函数(如下所示),add/edit 根据需要约束 - 并彻底解决问题。

df 转换为 dict 以更好地表示树结构

如果将数据结构转换为更直观的问题,而不是尝试在当前结构的上下文中解决问题,这个问题就会简单很多。

## Example dataframe
df = pd.DataFrame({'side_a':['a','b','c','k','l','l','p','q','r'],'side_b':['b','c','d','l','m','n','q','r','s']})

## Instantiate blank tree with every item
all_items = set(list(df['side_a']) + list(df['side_b']))
tree = {ii : set() for ii in all_items}

## Populate the tree with each row
for idx, row in df.iterrows():
    tree[row['side_a']] =  set(list(tree[row['side_a']]) + list(row['side_b']))

遍历树

既然数据结构很直观,这就简单多了。任何标准 Depth-First-Search algorithm w/ path saving 都可以。我修改了 link 中的那个来处理这个例子。

编辑:再次阅读它看起来你在 endpoint 中有一个搜索终止条件(你需要在你的问题中更清楚什么是输入和什么是输出)。您可以调整 dfs_path(tree,**target**, root) 并将终止条件更改为 return 只有正确的路径。

## Standard DFS pathfinder
def dfs_paths(tree, root):
    stack = [(root, [root])]
    while stack:
        (node, path) = stack.pop()
        for nextNode in tree[node] - set(path):
            # Termination condition. 
            ### I set it to terminate search at the end of each path.
            ### You can edit the termination condition to fit the 
            ### constraints of your goal
            if not tree[nextNode]:
                yield set(list(path) + list(nextNode)) - set(root)
            else:
                stack.append((nextNode, path + [nextNode]))
        

从我们产生的生成器构建数据框

如果您对生成器不是很满意,您可以构建 DFS 遍历,以便它以列表形式输出。而不是发电机

set_a = []
end_points = []
gen_dict = [{ii:dfs_paths(tree,ii)} for ii in all_items]
for gen in gen_dict:
    for row in list(gen.values()).pop():
        set_a.append(list(gen.keys()).pop())
        end_points.append(row)
                      
## To dataframe
df_2 = pd.DataFrame({'set_a':set_a,'end_points':end_points}).sort_values('set_a')

输出

df_2[['set_a','end_points']]


set_a   end_points
a       {b, c, d}
b       {c, d}
c       {d}
k       {n, l}
k       {m, l}
l       {n}
l       {m}
p       {s, r, q}
q       {s, r}
r       {s}

如果您接受额外的导入,这可以作为图形上的路径问题,并使用 NetworkX:

在几行中解决
import networkx

g = networkx.DiGraph(zip(df1.side_a, df1.side_b))

outdf = df2.apply(lambda row: [row.side_a, 
                               set().union(*networkx.all_simple_paths(g, row.side_a, row.end_point)) - {row.side_a}], 
                  axis=1)    

outdf 看起来像这样。请注意,这包含集合而不是所需输出中的列表 - 这允许以简单的方式组合所有路径。

  side_a  end_point
0      a     {c, b}
1      b        {c}
2      c         {}
3      k     {l, m}
4      k     {l, n}
5      l        {m}
6      l        {n}
7      p  {r, q, s}
8      q     {r, s}
9      r        {s}

您可以使用 networkx 库和图表:

import networkx as nx
G = nx.from_pandas_edgelist(df, source='side_a',target='side_b')
df2.apply(lambda x: [nx.shortest_path(G, x.side_a,x.end_point)[0],
                     nx.shortest_path(G, x.side_a,x.end_point)[1:]], axis=1)

输出:

  side_a  end_point
0      a     [b, c]
1      b        [c]
2      c         []
3      k     [l, m]
4      k     [l, n]
5      l        [m]
6      l        [n]
7      p  [q, r, s]
8      q     [r, s]
9      r        [s]