"""Knowledge-graph network analysis. (a) Degree distribution of the full 32k-node project knowledge graph. (b) Technology co-occurrence network (two technologies linked when used in the same project), with eigenvector centrality and greedy-modularity community detection -> 'technology ecosystems'. """ from __future__ import annotations import warnings from collections import Counter from itertools import combinations import numpy as np import pandas as pd import networkx as nx from common import DATA, set_style, savefig, write_table, save_results, esc import matplotlib.pyplot as plt warnings.filterwarnings("ignore") set_style() edges = pd.read_parquet(DATA / "kg_edges.parquet") nodes = pd.read_parquet(DATA / "kg_nodes.parquet") # ---- (a) full-graph degree distribution ----------------------------------- G = nx.from_pandas_edgelist(edges, "src", "dst", create_using=nx.DiGraph()) deg = np.array([d for _, d in G.degree()]) vals, cnts = np.unique(deg, return_counts=True) fig, ax = plt.subplots(figsize=(6.6, 4.0)) ax.scatter(vals, cnts / cnts.sum(), s=14, color="#1b3a5b", alpha=0.7) ax.set_xscale("log"); ax.set_yscale("log") ax.set_xlabel("Node degree"); ax.set_ylabel("P(degree)") ax.set_title("Degree distribution of the project knowledge graph") # rough power-law slope on the tail mask = (vals >= 2) slope = np.polyfit(np.log(vals[mask]), np.log(cnts[mask] / cnts.sum()), 1)[0] ax.text(0.05, 0.08, f"tail slope $\\approx$ {slope:.2f}", transform=ax.transAxes, fontsize=10) savefig(fig, "fig_degree_dist.pdf") # ---- (b) technology co-occurrence network --------------------------------- proj = pd.read_parquet(DATA / "projects.parquet") def norm(t): return str(t).strip().lower() freq = Counter() co = Counter() for techs in proj["technologies"]: techs = [] if techs is None else list(techs) ts = sorted({norm(t) for t in techs if str(t).strip()}) for t in ts: freq[t] += 1 for a, b in combinations(ts, 2): co[(a, b)] += 1 TOPK = 60 top = [t for t, _ in freq.most_common(TOPK)] top_set = set(top) H = nx.Graph() for t in top: H.add_node(t, freq=freq[t]) for (a, b), w in co.items(): if a in top_set and b in top_set and w >= 2: H.add_edge(a, b, weight=w) H.remove_nodes_from([n for n in list(H.nodes) if H.degree(n) == 0]) comms = list(nx.algorithms.community.greedy_modularity_communities(H, weight="weight")) modularity = nx.algorithms.community.modularity(H, comms, weight="weight") comm_of = {n: i for i, c in enumerate(comms) for n in c} eig = nx.eigenvector_centrality_numpy(H, weight="weight") pos = nx.spring_layout(H, k=0.55, seed=42, weight="weight") fig, ax = plt.subplots(figsize=(9.2, 7.2)) palette = ["#1b3a5b", "#c44e52", "#2e8b57", "#dd8452", "#8172b3", "#937860", "#da8bc3", "#64b5cd", "#ccb974", "#55a868"] ncolors = [palette[comm_of[n] % len(palette)] for n in H.nodes] nsizes = [60 + 14 * H.nodes[n]["freq"] ** 0.6 for n in H.nodes] nx.draw_networkx_edges(H, pos, alpha=0.18, width=[0.3 + 0.18 * H[u][v]["weight"] for u, v in H.edges], ax=ax) nx.draw_networkx_nodes(H, pos, node_size=nsizes, node_color=ncolors, alpha=0.9, linewidths=0, ax=ax) lab = {n: n for n in H.nodes if eig[n] > np.quantile(list(eig.values()), 0.55)} nx.draw_networkx_labels(H, pos, labels=lab, font_size=7, ax=ax) ax.set_title(f"Technology co-occurrence ecosystems " f"({len(comms)} communities, modularity = {modularity:.2f})") ax.axis("off") savefig(fig, "fig_tech_network.pdf") # table: top technologies by centrality cent = sorted(eig.items(), key=lambda kv: kv[1], reverse=True)[:15] lt = [r"\begin{tabular}{lrrr}", r"\toprule", r"Technology & Projects & Eigenvector cent. & Community \\", r"\midrule"] for t, ce in cent: lt.append(f"{esc(t.title())} & {freq[t]:,} & {ce:.3f} & {comm_of[t]+1} \\\\") lt += [r"\bottomrule", r"\end{tabular}"] write_table("tab_network", "\n".join(lt)) save_results("network", { "kg_nodes": int(len(nodes)), "kg_edges": int(len(edges)), "tail_slope": float(slope), "tech_nodes": int(H.number_of_nodes()), "tech_edges": int(H.number_of_edges()), "n_communities": int(len(comms)), "modularity": float(modularity), "top_central": [(t, float(c)) for t, c in cent[:6]], }) print(f"network done | {len(comms)} tech communities, modularity={modularity:.2f}")