Chapter 8 — Applying computational methods

Networks, simulation, prediction, and embeddings

Setup notes and library pointers for Chapter 8’s exercises: networks, simulation, prediction, and embeddings over the corpus you have already built.

Chapter 8 asks you to choose a computational method for a stated research purpose, then evaluate what its output supports.

What you will produce. A network, a diffusion simulation, a predictive model, and an embedding space built from your corpus. For each, state the research purpose and explain what evidence you need to assess the result.

Materials for this chapter

Network analysis notebook

Bipartite construction, co-citation projection, k-core pruning, visualization, and a regression specification, with saved outputs and sample network data. See the notebook notes before setting up a new environment.

Sample network data

38,237 policy-to-scholarship edges as parquet, plus the projected co-citation network as GraphML, ready for Gephi.

Supervised learning, gently

Work through this before §8.1.3 if you have never trained a model. Its small demo uses NumPy, pandas, and scikit-learn.

Computational analysis tools

Network, simulation, prediction, and embedding libraries, maintained.

Chapter 7 develops the distinction between computational capacities and research purposes.

Prerequisites

The exercises reuse the coauthor network from Chapter 4 and the coded corpus from Chapter 6.

pip install networkx python-louvain gensim scikit-learn matplotlib mesa

§8.1.1 — Building the coauthor network

import itertools, networkx as nx

G = nx.Graph()
for authors in df["author_list"]:                 # list of author IDs per work
    G.add_nodes_from(authors)
    G.add_edges_from(itertools.combinations(sorted(set(authors)), 2))

print(G.number_of_nodes(), "authors;", G.number_of_edges(), "collaboration ties")
largest = G.subgraph(max(nx.connected_components(G), key=len))
print("largest component:", largest.number_of_nodes(),
      "| diameter:", nx.diameter(largest))

nx.shortest_path(G, a, b) returns a shortest path between two connected nodes. Decide what that sequence of collaboration ties means for your question. If there is no path, the nodes belong to different connected components.

§8.1.2 — Diffusion simulation

Make update order an explicit argument so you can compare its effects:

import random

def diffuse(G, seed_node, p, steps=10, synchronous=True, seed=42):
    rng = random.Random(seed)             # a fresh, repeatable stream for each call
    adopted = {seed_node}
    for _ in range(steps):
        newly = set()
        source = set(adopted)                    # snapshot: synchronous updating
        for node in G.nodes():
            if node in adopted:
                continue
            visible = source if synchronous else adopted   # asynchronous sees updates
            if any(nb in visible for nb in G.neighbors(node)) and rng.random() < p:
                newly.add(node)
                if not synchronous:
                    adopted.add(node)   # later nodes can see this adoption immediately
        adopted |= newly
    return adopted

Run both modes for the same number of steps and with the same seed. In the synchronous mode, all nodes respond to the previous step’s adopters. In the asynchronous mode, a node can respond to an adoption earlier in the same loop, so node order also matters. Compare the final number of adopters across several seeds and step counts.

This simple model gives eligible nodes another adoption chance on each step. A step with no new adopters therefore does not end the simulation.

§8.1.3 — Non-linear prediction

Compare a linear baseline against a non-linear model on the same features and the same split. Report accuracy, precision, and recall on held-out data, and check the majority-class baseline first. See Supervised learning, gently for the full protocol and the leakage warning about fitting features before splitting.

§8.1.4 — Word embeddings

from gensim.models import Word2Vec

sentences = [t.lower().split() for t in df["text"]]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=5, seed=42, workers=4)

model.wv.most_similar("equity", topn=10)
model.wv.most_similar(positive=["qualitative", "survey"], negative=["quantitative"])

Embeddings trained on a few thousand abstracts are noisy. If your corpus is small, load a pretrained model and treat the training run as a lesson about the algorithm rather than as a measurement instrument.

§8.2 — Epistemic purposes

Each exercise reuses the artifacts above under a different research goal: centrality and community detection for description, community membership as a predictor for explanation, held-out forecasting for prediction, and cross-period evaluation for integration. The chapter develops the interpretation and validation each purpose requires.

import community as community_louvain
partition = community_louvain.best_partition(G, random_state=42)   # record the seed
nx.set_node_attributes(G, partition, "community")

Run community detection under several seeds before treating any partition as a finding. Chapter 10’s uncertainty exercise turns that instability into a figure.

§8.3 — AI validity audit

Compare the LLM labels from Chapter 6 against your dictionary or hand-coded labels on the same records, then read the disagreements. Chapter 7 (§7.4) supplies the framing: coding real data and generating synthetic data are different claims with different failure modes.

Where to go next