我需要得到所有后代用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,那么停止,不要再往前走了。
这里真的欢迎任何帮助或提示。 提前致谢。
答案 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)
您的规则不一致且您的定义不明确,因此您可能需要在此处添加一些约束,因为目前还不清楚您的具体要求。通过组织数据结构以适应问题和为遍历构建更强大的功能(如下所示),可以更轻松地根据需要添加/编辑约束 - 并解决完全是问题。
如果您将数据结构转换为对问题更直观,而不是尝试在当前结构的上下文中解决问题,则此问题要简单得多。
## 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}