簡體   English   中英

Pandas 中的遞歸操作

[英]Recursive Operation in Pandas

我有一個像這樣的數據幀:

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

我能夠關閉它,但不確定如何正確地進行遞歸,因為 dict 無法處理 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}

檢查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]]]

讓我們嘗試手動滾動解決方案,因為考慮這種遞歸算法是有教育意義的。 (當然,在現實世界中只使用現有的庫是合適的;它可能會更加容錯。)

您顯示的代碼構建了圖形本身的可識別表示,但為了一致性,即使只有一個后繼節點,也最好對值使用列表(或集合或元組)。 我認為集合在這里最有意義,因為如果輸入中有重復的條目,那么我們應該丟棄重復的圖節點。 所以讓我們假設我們從以下開始:

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

我們同意將自己限制在考慮有向無環圖上。 我建議可以遞歸地找到來自我們根節點的路徑,如下所示:遞歸地檢查來自每個后繼節點的每條路徑; 累積這些結果,並在每個結果前面加上從根到相應后繼的鏈接。

當然,當我們編寫遞歸代碼時,我們希望避免副作用,因為它們使推理變得更加困難。 因此,讓我們改為說:對於每個后繼,對於來自該后繼的每個 pat,所有路徑的累積,定義為(從節點到后繼的鏈接)+(從后繼到結束的路徑)。 當然,我們表示“從節點到后繼節點的鏈接”的方式只是當前節點名稱和一個箭頭; 我們從遞歸中得到路徑的其余部分,包括后繼名稱。

然后我們需要一個基本情況:如果沒有后繼者,那么我們有一條從這里到末尾的路徑(因為我們在末尾),這就是節點名稱本身。 如果我們的圖中的死胡同用空集表示,我們的代碼會更簡單; 但顯然省略這些鍵更容易生成圖形。 因此,當我們進行檢查時,我們將依靠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)]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM