spb/uqo-research-db
Public
1"""Knowledge-graph network analysis.23(a) Degree distribution of the full 32k-node project knowledge graph.4(b) Technology co-occurrence network (two technologies linked when used in the5 same project), with eigenvector centrality and greedy-modularity community6 detection -> 'technology ecosystems'.7"""8from __future__ import annotations910import warnings11from collections import Counter12from itertools import combinations1314import numpy as np15import pandas as pd16import networkx as nx1718from common import DATA, set_style, savefig, write_table, save_results, esc19import matplotlib.pyplot as plt2021warnings.filterwarnings("ignore")22set_style()2324edges = pd.read_parquet(DATA / "kg_edges.parquet")25nodes = pd.read_parquet(DATA / "kg_nodes.parquet")2627# ---- (a) full-graph degree distribution -----------------------------------28G = nx.from_pandas_edgelist(edges, "src", "dst", create_using=nx.DiGraph())29deg = np.array([d for _, d in G.degree()])30vals, cnts = np.unique(deg, return_counts=True)31fig, ax = plt.subplots(figsize=(6.6, 4.0))32ax.scatter(vals, cnts / cnts.sum(), s=14, color="#1b3a5b", alpha=0.7)33ax.set_xscale("log"); ax.set_yscale("log")34ax.set_xlabel("Node degree"); ax.set_ylabel("P(degree)")35ax.set_title("Degree distribution of the project knowledge graph")36# rough power-law slope on the tail37mask = (vals >= 2)38slope = np.polyfit(np.log(vals[mask]), np.log(cnts[mask] / cnts.sum()), 1)[0]39ax.text(0.05, 0.08, f"tail slope $\\approx$ {slope:.2f}", transform=ax.transAxes, fontsize=10)40savefig(fig, "fig_degree_dist.pdf")4142# ---- (b) technology co-occurrence network ---------------------------------43proj = pd.read_parquet(DATA / "projects.parquet")4445def norm(t):46 return str(t).strip().lower()4748freq = Counter()49co = Counter()50for techs in proj["technologies"]:51 techs = [] if techs is None else list(techs)52 ts = sorted({norm(t) for t in techs if str(t).strip()})53 for t in ts:54 freq[t] += 155 for a, b in combinations(ts, 2):56 co[(a, b)] += 15758TOPK = 6059top = [t for t, _ in freq.most_common(TOPK)]60top_set = set(top)61H = nx.Graph()62for t in top:63 H.add_node(t, freq=freq[t])64for (a, b), w in co.items():65 if a in top_set and b in top_set and w >= 2:66 H.add_edge(a, b, weight=w)67H.remove_nodes_from([n for n in list(H.nodes) if H.degree(n) == 0])6869comms = list(nx.algorithms.community.greedy_modularity_communities(H, weight="weight"))70modularity = nx.algorithms.community.modularity(H, comms, weight="weight")71comm_of = {n: i for i, c in enumerate(comms) for n in c}72eig = nx.eigenvector_centrality_numpy(H, weight="weight")7374pos = nx.spring_layout(H, k=0.55, seed=42, weight="weight")75fig, ax = plt.subplots(figsize=(9.2, 7.2))76palette = ["#1b3a5b", "#c44e52", "#2e8b57", "#dd8452", "#8172b3", "#937860",77 "#da8bc3", "#64b5cd", "#ccb974", "#55a868"]78ncolors = [palette[comm_of[n] % len(palette)] for n in H.nodes]79nsizes = [60 + 14 * H.nodes[n]["freq"] ** 0.6 for n in H.nodes]80nx.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)81nx.draw_networkx_nodes(H, pos, node_size=nsizes, node_color=ncolors, alpha=0.9, linewidths=0, ax=ax)82lab = {n: n for n in H.nodes if eig[n] > np.quantile(list(eig.values()), 0.55)}83nx.draw_networkx_labels(H, pos, labels=lab, font_size=7, ax=ax)84ax.set_title(f"Technology co-occurrence ecosystems "85 f"({len(comms)} communities, modularity = {modularity:.2f})")86ax.axis("off")87savefig(fig, "fig_tech_network.pdf")8889# table: top technologies by centrality90cent = sorted(eig.items(), key=lambda kv: kv[1], reverse=True)[:15]91lt = [r"\begin{tabular}{lrrr}", r"\toprule",92 r"Technology & Projects & Eigenvector cent. & Community \\", r"\midrule"]93for t, ce in cent:94 lt.append(f"{esc(t.title())} & {freq[t]:,} & {ce:.3f} & {comm_of[t]+1} \\\\")95lt += [r"\bottomrule", r"\end{tabular}"]96write_table("tab_network", "\n".join(lt))9798save_results("network", {99 "kg_nodes": int(len(nodes)), "kg_edges": int(len(edges)),100 "tail_slope": float(slope),101 "tech_nodes": int(H.number_of_nodes()), "tech_edges": int(H.number_of_edges()),102 "n_communities": int(len(comms)), "modularity": float(modularity),103 "top_central": [(t, float(c)) for t, c in cent[:6]],104})105print(f"network done | {len(comms)} tech communities, modularity={modularity:.2f}")106