I am a beginner in using Python. I have a MS Access database containing a family tree. I wish to use Python to query the family tree to list all the descendants of identified ancestors. The family tree is held in a table I have exported to excel. It has three columns: Relationship ID, Node1_ID, Node2_ID. Relationships listed in each row are uni-directional from Node1_ID to Node2_ID. The tree is non-binary, whereby the tree can have several Node2_IDs descending from a Node1_ID.
Each partner of a marriage in the position of Node1_ID relate to the same partnership node in Node2_ID (in the example below parent Nodes 1 and 2 relate to partnership Node 3). The partnership node then relates to children Nodes (e.g., partnership Node 3 connects to children Nodes 4, 5 and 6).
RID | Node1_ID | Node2_ID |
---|---|---|
1 | 1 | 3 |
2 | 2 | 3 |
3 | 3 | 4 |
4 | 3 | 5 |
5 | 3 | 6 |
I wish to use a code to call the excel spreadsheet and allow me to query a list of all descendants from a particular node through recursion. I then wish to write that list to a new excel file that I can import back into my MS Access database or to visualise in other software like GraphViz.
In searching online, I have come across the following code, which seems to do the trick:
import pandas as pd
import sys
sys.setrecursionlimit(100000000)
df = pd.read_excel(r'excelfilepath')
columns = df.columns.ravel()
def get_ancestry_dataframe_flat(df):
def get_child_list(parent_id):
list_of_children = list()
list_of_children.append(df[df['P1ID'] == parent_id]['P2ID'].values)
for i, r in df[df['P1ID'] == parent_id].iterrows():
if r['P2ID'] != parent_id:
list_of_children.append(get_child_list(r['P2ID']))
# flatten list
list_of_children = [item for sublist in list_of_children for item in sublist]
return list_of_children
new_df = pd.DataFrame(columns=['descendant', 'ancestor']).astype(int)
for index, row in df.iterrows():
temp_df = pd.DataFrame(columns=['descendant', 'ancestor'])
temp_df['descendant'] = pd.Series(get_child_list(row['P1ID']))
temp_df['ancestor'] = row['P1ID']
new_df = new_df.append(temp_df)
new_df = new_df\
.drop_duplicates()\
.sort_values(['ancestor', 'descendant'])\
.reset_index(drop=True)
return new_df
writer = pd.ExcelWriter('20210408_descendant_relationships.xlsx',engine='xlsxwriter')
get_ancestry_dataframe_flat(df).to_excel(writer,sheet_name='Sheet1')
writer.save()
The problem with this code, is that I seem to hit the recursion limit when creating a list of descendants from all ancestors (The family tree is quite big). I wish to therefore query only descendants from specific ancestors. What edits can I make to do this please? Aside from the recursion limit issue, I will still want to identify descendants from specific ancestors.
I am a beginner in Python, so I would appreciate lamens terms if possible. Thank you for your time.
EDIT: code with advice from @Ajax1234
import pandas as pd
from collections import deque
df = pd.read_excel('input.xlsx')
tree = df.values.tolist()
def get_ancestors(node):
d, seen = deque([node]), set()
while d:
yield (n:=d.popleft())
seen.add(n)
d.extend([int(c) for _, b, c in tree if int(b) == int(n)])
r = dict([(next(n:=get_ancestors(i)), list(n)) for i in set([t[1] for t in tree])])
vals = [[[a, i] for i in b] for a, b in r.items()]
df1 = pd.DataFrame([dict(zip(['node', 'descendent'], i)) for i in vals])
print(r)
df1.to_excel('output.xlsx')
The intended output excel table should be something like:
Node | Descendant |
---|---|
1 | 3 |
1 | 4 |
1 | 5 |
1 | 6 |
2 | 3 |
2 | 4 |
2 | 5 |
2 | 6 |
3 | 4 |
3 | 5 |
3 | 6 |
You can use a breadth-first search for a non-recursive solution:
from collections import deque
tree = [[1, 1, 3], [2, 2, 3], [3, 3, 4], [4, 3, 5], [5, 3, 6]]
def get_ancestors(node):
d, seen = deque([node]), set()
while d:
yield (n:=d.popleft())
seen.add(n)
d.extend([int(c) for _, b, c in tree if int(b) == int(n)])
get_ancestors
takes in a node and produces all descendants of the node via a breadth-first search. As an example, to get all the descendants of the node1_id
s:
r = dict([(next(n:=get_ancestors(i)), list(n)) for i in set([t[1] for t in tree])])
Output:
{1: [3, 4, 5, 6], 2: [3, 4, 5, 6], 3: [4, 5, 6]}
Edit: writing to Excel:
writer = pd.ExcelWriter('20210408_descendant_relationships.xlsx',engine='xlsxwriter')
df = pd.read_excel(writer, 'sheet1')
tree = df.values.tolist()
r = dict([(next(n:=get_ancestors(i)), list(n)) for i in set([t[1] for t in tree])])
vals = [[a, i] for a, b in r.items() for i in b]
df1 = pd.DataFrame([dict(zip(['node', 'descendent'], i) for i in vals])
df1.to_excel(writer)