熊猫的递归运算

时间:2020-09-04 02:26:23

标签: python pandas dataframe

我有一个这样的DataFrame:

vals = {"operator": [1, 1, 1, 2, 3, 5], "nextval": [2, 3, 6, 4, 5, 6]}
df = pd.DataFrame(vals)

   operator  nextval
0         1        2
1         1        3
2         1        6
3         2        4
4         3        5
5         5        6

我想做的是使用运算符和nextval,而不是最短路径,从起点(如1)和终点(如6)获取所有可能路径的列表。 输出可能很灵活,但我正在寻找类似或传达这种信息的东西:

1 -> 6
1 -> 2 -> 4 
1 -> 3 -> 5 -> 6

我能够将其关闭,但由于字典无法处理2个相同的键,因此不确定如何正确执行递归:

import pandas as pd

vals = {"operator": [1, 1, 1, 2, 3, 5], "nextval": [2, 3, 6, 4, 5, 6]}
df = pd.DataFrame(vals)

df1 = df.set_index("operator")

dictvals = {}
for x in df1.index.unique():
    dictvals[x] = []
    df2 = df1.loc[x]
    if isinstance(df2, pd.DataFrame):
        for idx, rowdata in df2.iterrows():
            dictvals[x].append(rowdata["nextval"])
    else:
        dictvals[x] = df2[0]

print(dictvals) 

{1: [2, 3, 6], 2: 4, 3: 5, 5: 6}

2 个答案:

答案 0 :(得分:4)

使用networkx进行检查,您需要一个方向图,其中有'root''leaf'的路径

import networkx as nx
G=nx.from_pandas_edgelist(df,source='operator',target='nextval', edge_attr=None, create_using=nx.DiGraph())
road=[]
for n in G:
       if G.out_degree(n)==0: #leaf
           road.append(nx.shortest_path(G, 1, n))
           
road
Out[82]: [[1, 2, 4], [1, 3, 5, 6]]

更新

import networkx as nx
G=nx.from_pandas_edgelist(df,source='operator',target='nextval', edge_attr=None, create_using=nx.DiGraph())
road=[]
for n in G:
       if G.out_degree(n)==0: #leaf
           road.append(list(nx.all_simple_paths(G, 1, n)))
           
road
Out[509]: [[[1, 3, 5, 6], [1, 6]], [[1, 2, 4]]]

答案 1 :(得分:4)

让我们尝试动手解决方案,因为考虑这种递归算法是有教育意义的。 (当然,仅在现实世界中使用现有的库是适当的;它可能更具容错性。)

您显示的代码可建立图形本身的可识别表示形式,但是为了保持一致性,即使在只有一个后继节点的情况下,也最好使用列表(或集合或元组)作为值。我认为这里的集合最有意义,因为如果输入中有重复的条目,那么我们应该丢弃重复的图节点。因此,让我们假设我们开始于:

graph = {1: {2, 3}, 2: {4}, 3: {5}, 5: {6}}

我们已经同意将自己局限于考虑有向无环图。我建议可以如下递归找到来自根节点的路径:递归地检查 each 后继节点的 each 路径;累积这些结果,并在每个结果前加上从根到相应后继项的链接。

当然,当我们编写递归代码时,我们希望避免副作用,因为它们使推理变得更加困难。因此,让我们改为说:对于每个后继者,对于该后继者的每个拍子,定义为(从节点到后继者的链接)+(从后继者到末端的路径)的所有路径的累积。当然,我们表示“从节点到后继者的链接”的方式只是当前节点的名称和箭头。我们从递归中获取其余路径,包括后继名称。

然后,我们需要一个基本情况:如果没有后继,那么我们只有一条从此处到末尾的路径(因为我们位于末尾),它本身就是那个节点名。如果图形中的死角用空集表示,那么对于我们的代码而言,这将更加简单。但是生成图形显然很容易,因为省略了这些键。因此,我们在进行检查时将依靠dict.get而不是索引。

好吧,第一部分听起来对我来说很像一个列表理解(带有两个for子句。)对于基本情况,为了匹配它,我们需要一个包含一个路径的列表。这给了我们:

def paths(graph, root):
    successors = graph.get(root, set())
    if not successors:
        return [str(root)] # a single path starting here and going nowhere.
    return [
        f'{root} -> {path}'
        for successor in successors
        for path in paths(graph, successor)
    ]

让我们尝试一下:

>>> paths({1: {2, 3}, 2: {4}, 3: {5}, 5: {6}}, 1)
['1 -> 2 -> 4', '1 -> 3 -> 5 -> 6']

或者,您可以使用生成器表达式而不是列表推导,甚至可以将其写为递归生成器(使用yieldyield from)。

(如果我们足够脸颊,可以使用条件表达式来继续进行功能编程主题:)

def paths(graph, root):
    successors = graph.get(root, set())
    return [
        f'{root} -> {path}'
        for successor in successors
        for path in paths(graph, successor)
    ] if successors else [str(root)]