A graph approach to reporting structures: stop wrestling recursion

organizational analytics graph analysis python

About a cleaner, faster way to compute some org metrics.


Author

Affiliation

Luděk Stehlík

 

Published

Oct. 30, 2025

Citation

Stehlík, 2025


From time to time, I need to compute organizational stats related to manager-report relationships.

I used to do this with nested loops or a recursive walk through the org chart plus memoization (i.e., caching results to avoid recomputation). It works - but it’s verbose and easy to get wrong (recursion limits, cycle guards, cache invalidation).

Recently I came upon a much more elegant and clear solution: rely on graph libraries like NetworkX, whose built-in functions let me easily traverse manager-report relationships and compute the metrics I need.

Some quick wins with this approach:️

So if you’re computing org stats and your code is full of nested loops, NetworkX and other similar libraries can make it shorter, safer, and more expressive. I only wish I’d discovered this earlier - I could have saved myself a lot of headaches 🫣

P.S. Below is a minimal working example of Python code that builds edges from manager_idemployee_id, followed by a simple loop to compute direct, indirect, and total reports for every manager.


import pandas as pd
import networkx as nx

# mydata: columns ['manager_id', 'employee_id']
df = (mydata.dropna(subset=['manager_id','employee_id'])  
              .drop_duplicates(['manager_id','employee_id']))
df = df[df['manager_id'] != df['employee_id']]  # drop self-loops just in case

G = nx.DiGraph()
G.add_edges_from(df[['manager_id','employee_id']].itertuples(index=False, name=None))

# Managers = anyone who manages at least one person
managers = set(df['manager_id'])

rows = []
for m in managers:
    total = len(nx.descendants(G, m))    # all reports at any depth (unique people)
    direct = G.out_degree(m)             # direct reports
    rows.append((m, direct, total - direct, total))

out = (pd.DataFrame(rows, columns=['manager_id','direct','indirect','total'])
         .sort_values(['total','direct'], ascending=False))

Footnotes

    Citation

    For attribution, please cite this work as

    Stehlík (2025, Oct. 31). Ludek's Blog About People Analytics: A graph approach to reporting structures: stop wrestling recursion. Retrieved from https://blog-about-people-analytics.netlify.app/posts/2025-10-31-org-stats-and-graph-analysis/

    BibTeX citation

    @misc{stehlík2025a,
      author = {Stehlík, Luděk},
      title = {Ludek's Blog About People Analytics: A graph approach to reporting structures: stop wrestling recursion},
      url = {https://blog-about-people-analytics.netlify.app/posts/2025-10-31-org-stats-and-graph-analysis/},
      year = {2025}
    }