查找Python中

时间:2018-04-17 20:40:13

标签: python pandas recursion tree descendant

我需要得到所有后代用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中删除所有行,这会导致某些问题。但是,如果有人要我发布代码,我当然会。

所需的输出将是这样的:

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,那么停止,不要再往前走了。

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

3 个答案:

答案 0 :(得分:4)

您可以使用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]

答案 1 :(得分:3)

您的规则不一致且您的定义不明确,因此您可能需要在此处添加一些约束,因为目前还不清楚您的具体要求。通过组织数据结构以适应问题为遍历构建更强大的功能(如下所示),可以更轻松地根据需要添加/编辑约束 - 并解决完全是问题。

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都可以解决问题。我修改了链接中的那个以使用此示例。

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

## 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}

答案 2 :(得分:2)

如果您使用额外的导入功能,则可以将其作为路径问题添加到图表中,并使用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}