Before we start


Show of hands:

  • Who has done qualitative coding or content analysis (MAXQDA, NVivo, AQDA, …)?
  • Who has worked with network data?
  • Who uses R or Python?
  • Who has installed DNA?




Linocut of a row of mismatched empty chairs with small flags raised on some backrests, a child's chair among them

Outline


What we will cover

  • Why Discourse Network Analysis?

  • The intuition: from statements to networks

  • Coding by hand: a short exercise on AI regulation

  • Basics of Discourse Network Analyzer for Data Generation

  • From DNA to R: visualizing the network

  • Automating DNA with LLMs

What we will not cover

  • Intermediate and Advanced Descriptive Network Analysis (e.g., calculation of network metrics, community detection algorithms, etc)

  • Inferential Network Analysis

  • Implementation in R and visualization beyond the basics (rDNA or general packages like igraph, sna, ggraph, etc)

Why Discourse Network Analysis?

The Politics of Policymaking


Often, as political scientists we are interested in:

  • what political actors such as interest groups, government agencies, legislators, and scientific experts believe about or want a policy to be like;

  • how these actors organize into political coalitions to influence policymaking;

  • what (competing) policy ideas emerge in a discourse and how they are organized and expressed in frames, narratives or similar discursive structures;

  • how (elements of) policy ideas or political coalitions change, evolve or vary over time, across venues, institutional contexts, or in response to certain events.


‘Policy process theories or frameworks usually combine some of these elements to explain why, how, and when policy change happens’ (Leifeld 2020, 180).

The Study of Policy Networks



An important strand of research on the dynamics of policy change is the study of policy networks. While policy networks can and are often analyzed on the level of material interactions (e.g., exchange of resources or information), one can also study them on the level of ideas and discourse (e.g., shared beliefs, mutual learning).

DNA & Theories of the Policymaking Process


Discourse Network Analysis (DNA) is an empirical approach to studying the structure and dynamics of actor-based debates, such as policy debates or political discussions.

  • While DNA is compatible with many theories of the policymaking process, there are greater degrees of compatibility or ‘elective affinities’ with certain such theories. However, different theoretical starting points should be reflected in the concrete empirical implementation. For example:

    • The advocacy coalition framework (Jenkins-Smith et al. 2017) puts special emphasis on ‘policy core policy preferences’, i.e., preferred solutions for addressing a policy problem (Leifeld 2017, 303).
    • Argumentative discourse analysis posits that political conflicts are structured by ‘discourse coalitions’ which are defined as ‘a group of actors who share a social construct’ (Hajer 1993, 45).
  • These differences will lead to subtle differences in how coding schemes are constructed and what material is collected (e.g. focus on instruments or fundamental beliefs, public statements or technical papers). Theory and empirical approach should thus be aligned (see generally Hall 2003).

How does DNA work and what is it good for?


‘DNA is an attempt at measuring actors’ policy beliefs and discourses systematically using text sources and molding them into a data format that is compatible with policy network analysis’ (Leifeld 2020, 180).

  • DNA’s core innovation is to combine category-based content analysis and network analysis, which allows one to analyse discursive dynamics (clustering at the level of concepts) and patterns of coalition formation (clustering at the actor level) at the same time. Other than in traditional content or discourse analysis, one thus records both concepts (e.g. frames or narrative elements) and the actors who advance them.

  • This allows one to more systematically explore, describe, and draw inferences about discursive dynamics on both the actor and the content level. This, in turn, can not only illuminate political dynamics in specific policy areas but also shed light on the politics of policymaking more generally, e.g., the relationship between policy networks and discursive networks (Schaub and Metz 2020), endogenous dynamics of coalition formation through reciprocity or repulsion (Leifeld and Brandenberger 2019), the evolution of framing over time (Leifeld and Garic 2026), or the ‘stickiness’ of ideas in discourse (cf. Cox 2004).

Building the Intuition behind DNA

What are Networks?


‘At its core, a network consists of nodes (which represent the actors, whether individuals or organizations) and edges (which signify the relationships or connections between these actors)’ (Schoch and Shafie 2025).1


  • Nodes and edges can carry attributes. Some come from outside the network (an actor’s type, the weight of a tie), others are computed from its structure (centrality). Ties can be directed or undirected.
  • One-mode networks have one set of nodes; two-mode networks have two (e.g., actors and the concepts they refer to), with ties only between the sets.
  • A two-mode network can be turned into one-mode networks: the core move of DNA.

Developing the intuition

Does pineapple belong on pizza?

Linocut of a pizza with one pineapple chunk, a dark blue and a terracotta cocktail flag stuck into it

Visualizing the intuition behind DNA

Visualizing the intuition behind DNA II

Who is more similar?


  • Pair 1: A and B each take a position on 30 concepts. They agree on 10.
  • Pair 2: C and D each take a position on 5 concepts. They agree on all 5.


Which pair is more similar?


Raw count Average activity
A–B 10 10 / 30 = 0.33
C–D 5 5 / 5 = 1

Raw counts measure how much actors talk as much as whether they agree.

Normalization

Problem: Highly active actors produce more ties simply by talking more.

Goal: Measure similarity, not activity.

Scenario: A mentions 20 concepts, B mentions 30, with 12 in common.


Measure Calculation Value Notes
No normalization raw count 12
Average activity 12 / [(20 + 30) / 2] 0.48 Standard in DNA; divides by the mean activity of both actors; works for binary and weighted data
Jaccard similarity 12 / (20 + 30 − 12) 0.32 Penalizes non-overlap; best for binary presence/absence data
Cosine similarity 12 / √(20 × 30) 0.49 Works well with frequency data or duplicates; focuses on pattern rather than scale

Normalization changes absolute values; major network patterns usually remain similar.

Leveraging Network Analysis


We can use network metrics as measurements for theoretical concepts of interest. For example, one could make an argument that actors with high degree and betweenness centrality are central concepts, important both within communities and as bridges between them. Conversely, concepts with high degree centrality but low betweenness centrality might be more important within specific communities. Eigenvector centrality reveals which concepts are aligned with other central concepts—identifying core ideational clusters or elite discourse structures (see also Angst, Müller, and Walker 2025; Shim, Park, and Wilding 2015).

Data & Methods


DNA is an empirical approach, not a specific method: different data sources and ways of coding are possible.

Data sources

  • Documents the actors write themselves: consultation responses, position papers, press releases. Who speaks is known.
  • Documents reporting on many actors: newspaper articles, parliamentary debates. Who said what has to be extracted first.

Coding

  • Manual: coding scheme developed deductively, inductively or both; labour-intensive, but full control and flexibility.
  • (Semi-)automated: language models extract statements and classify concepts and stances; scales to large corpora, but needs validation.



Linocut of tweezers lifting one strip of paper from a pile next to a garden rake pulling in many strips, an autumn leaf caught in the rake

Example: Transformation of EU Trade Policy?


  • For decades, the EU’s trade policy has been centered around open(ing) markets. Why, then, has the EU recently embraced open strategic autonomy as the conceptual cornerstone of its renewed trade policy?

  • Schmitz and Seidl (2023) argue that the geopoliticization of trade policy has not only changed how the Commission thinks about how to best serve European interests (and values); it has also provided an opening for neo-mercantilist and socially oriented actors to challenge Europe’s embedded neoliberal compromise.

  • To further illuminate the coalitional and ideational politics behind the EU’s turn to autonomy, they complement a qualitative analysis of documents and interviews with a DNA of the contributions to the Commission’s 2020 trade policy review. They coded 319 of the 414 responses to the consultation (excluding responses by individuals), resulting in 1672 coded statements.

Actor congruence network of the 2020 trade policy review: a neoliberal coalition dominated by business and a challenger coalition of NGOs, trade unions and neo-mercantilist business groups, with the European Commission between them

Neoliberal coalition
Challenger coalition
Agree Disagree Agree Disagree
The EU should continue its commitment to an open, multilateral trading system 80% 0% 21% 4%
The EU should make sure trade does not undermine European social and environmental standards or regulatory capacity 4% 30% 56% 2%
Openness erodes internal capacity, and/or resilience and creates dependence 0% 82% 41% 7%
The EU should actively build up internal capacity to increase resilience and reduce dependency 1% 63% 41% 7%
The EU should protect European companies through trade policy 8% 31% 36% 2%
Openness can mean one-sidedness 19% 19% 47% 4%
The EU should become a global standard setter 28% 0% 43% 0%
The EU should protect European companies through industrial policy 7% 10% 18% 0%
Share of each coalition's members; the rest did not address the claim or both agreed and disagreed. Coalitions are fast-greedy communities of the actor congruence network. Source: Schmitz and Seidl (2023).

Hands-on I: Coding by Hand

From a quote to a statement

Christian Democrat MEP Markus Weber, by contrast, said his group would support postponing the high-risk obligations, “as long as the protection of fundamental rights is not watered down”.

Who is speaking, and what do they claim?

  • Actor: Markus Weber (Christian Democrats, EP group)
  • Claim: The obligations for high-risk AI systems should be postponed → agrees
  • Claim: AI systems threaten fundamental rights and need strict oversight → agrees

From claims to concepts

Claims that come up repeatedly can become concepts, formulated so that one can agree or disagree with them.

How fine-grained? The rights claim could be split (AI threatens fundamental rights; AI needs strict oversight) or broadened (AI needs regulation), depending on the granularity the research design requires.

Concepts can come from theory (deductively) or from the material (inductively). In practice, developing a codebook involves many decisions and can take several rounds of coding, revising and recoding, iterating between the material and theory, adjusting the level of granularity, and consolidating and complicating the codebook. Document every decision: definitions, examples, coding rules.

Our codebook for today


Now imagine we had coded a few more statements like this and developed the following codebook for the debate on postponing the AI Act’s rules for high-risk systems.

ID Concept
C1 Strict AI rules undermine Europe’s competitiveness.
C2 The obligations for high-risk AI systems should be postponed.
C3 AI systems threaten fundamental rights and need strict oversight.
C4 Legal certainty about AI rules is essential.
C5 Europe must build its own AI capabilities.


Each statement: actor (person, organization) × concept × agreement (yes/no) (+ further variables if the research design needs them, e.g., venue or type of argument)

Hard cases


A DNA statement: an actor says something about a concept from the codebook, for or against it. But this is not always entirely straightforward:

Statement Problem
“Critics also note that many large firms have long prepared for the rules.” No identifiable actor
“A Commission spokesperson rejected the accusation of deregulation.” An organization without a named person; and which concept?
Novak: “Whether the rules come in 2026 or 2027 matters less to us than knowing what exactly they require.” Explicitly indifferent on postponing (C2), but for legal certainty (C4)
“The Commission argues that the technical standards needed to apply the high-risk rules will not be ready in time.” The journalist’s paraphrase, not a quote

None of these has a correct answer in itself: the codebook has to decide.

Exercise


In pairs, about 20 minutes:

  1. Read the four texts. Underline every statement: blue if the actor agrees, red if they disagree; note actor and concept code in the margin.
  2. Enter each actor’s stances in the grid (+ agrees, − disagrees).
  3. Draw the actor congruence network.
  4. Note every decision you found hard, and every argument that does not fit the codebook.

Hands-on II: Discourse Network Analyzer

DNA and rDNA

Discourse Network Analyzer (DNA)

  • Desktop program for coding statements: import texts, mark who says what about which concept, for or against, and export the networks
  • Free and open source: github.com/leifeld-lab/dna, version 3.1.2; needs Java 11 or higher (e.g., Adoptium Temurin)
  • Start with a double-click on dna-3.1.2.jar or with java -jar dna-3.1.2.jar; on a Mac, allow it once under Privacy & Security
  • Statement types and their variables can be edited, e.g., a variable for the type of argument
  • Exports matrices as .csv (for R or a spreadsheet), networks as .graphml (for Gephi or visone), and event lists with one row per statement

rDNA

  • R package that connects R to a DNA database: export networks, read and add documents and statements
  • Makes the step from coding to analysis reproducible; needs Java and rJava
  • Built-in analyses, e.g., coalition detection, polarization, the backbone of a concept set, phase transitions

Network packages in R

  • igraph: the algorithms (centrality, communities, …)
  • tidygraph: a tidy interface to igraph; a tbl_graph is also an igraph object
  • ggraph: network plots in the grammar of ggplot2

DNA walkthrough


  1. GitHub repository: releases, Java, issue tracker
  2. Open DNA: file-based vs. server-based databases; open sample.dna (password: sample)
  3. Interface: documents, text, statements (similar to MAXQDA etc.)
  4. Click on a statement and explain the pop-up window
  5. Create a new statement (select text, Ctrl+1); NPF element and annotation
  6. Import documents: Document → Import from CSV… (new in 3.1.2), or Import from directory
  7. Edit statement types (Statements → edit statement types)
  8. Change attributes (Statements → attributes manager)

Exporting a network


Export → Open network export dialog. The settings for today, with the main alternatives:

Field Today Meaning and alternatives
Type of network One-mode network actor × actor; two-mode: actor × concept; event list: one row per statement
Variable 1, Variable 2 organization, concept the nodes, and what connects them
Qualifier aggregation congruence tie for the same stance on a concept; conflict: opposite stances; subtract: the difference
Normalization average divide by the actors’ average activity (see the normalization slide)
Duplicates include all duplicates repeated statements count again; ignore per document (week, month, …) counts them once
Exclude from variable – e.g., leave out C4 Legal certainty, which both camps invoke
File format .csv a matrix for R or a spreadsheet; .graphml for visone or Gephi

Your turn: the AI texts in DNA


  1. Create a new database
  2. Import the four texts (Documents → import CSV)
  3. Code the statements from your worksheet
  4. Export the actor congruence network (settings on the previous slide)

From DNA to R

library(tidyverse)
library(tidygraph)
library(ggraph)

# 1. read the file exported from DNA
#    (columns are separated by ";")
congruence <- read_delim(
  "../data/ai-act-congruence.csv",
  delim = ";"
)

# 2. the first column holds the actor names but
#    has no header, so R calls it "...1"; make
#    the names row names to get an actor × actor
#    matrix
congruence <- congruence |>
  column_to_rownames("...1") |>
  as.matrix()

# 3. turn the matrix into a network
network <- as_tbl_graph(congruence, directed = FALSE)
network
# A tbl_graph: 7 nodes and 13 edges
#
# An undirected simple graph with 1 component
#
# Node Data: 7 × 1 (active)
  name                      
  <chr>                     
1 Christian Democrats       
2 Digital Workers Europe    
3 EuroTech Industry Alliance
4 European Commission       
5 European Consumer Network 
6 Greens                    
7 Isarlogik                 
#
# Edge Data: 13 × 3
   from    to weight
  <int> <int>  <dbl>
1     1     2  0.4  
2     1     3  0.333
3     1     4  0.333
# ℹ 10 more rows

Plotting the network

# the layout starts at random: fix it to get
# the same picture every time
set.seed(1)

ggraph(network, layout = "stress") +
  # ties: thicker = more agreement
  geom_edge_link(aes(width = weight), colour = "grey60") +
  # actors
  geom_node_point(size = 5) +
  # names, pushed away from the points
  geom_node_text(aes(label = name), repel = TRUE, point.size = 5) +
  scale_edge_width(range = c(0.5, 3)) +
  theme(legend.position = "none")

Communities and centrality

# add two columns to the actors (nodes):
# their coalition (Louvain communities) and how
# strongly they are tied to others (weighted degree)
network <- network |>
  activate(nodes) |> # work on the nodes (the default)
  mutate(
    coalition = factor(group_louvain(weights = weight)),
    degree = centrality_degree(weights = weight)
  )

set.seed(1)

ggraph(network, layout = "stress") +
  geom_edge_link(aes(width = weight), colour = "grey60") +
  # colour by coalition, size by degree
  geom_node_point(aes(colour = coalition, size = degree)) +
  geom_node_text(aes(label = name), repel = TRUE, point.size = 8) +
  scale_size(range = c(3, 9)) +
  scale_edge_width(range = c(0.5, 3)) +
  theme(legend.position = "none")

The same with igraph

library(igraph)

# the matrix from DNA as an igraph object
g <- graph_from_adjacency_matrix(
  congruence,
  mode = "undirected",
  weighted = TRUE
)

# coalitions and weighted degree, stored as
# node attributes with V(g)$...
set.seed(1)
V(g)$coalition <- factor(membership(cluster_louvain(g)))
V(g)$degree <- strength(g)

# the same plot as before
ggraph(g, layout = "stress") +
  geom_edge_link(aes(width = weight), colour = "grey60") +
  geom_node_point(aes(colour = coalition, size = degree)) +
  geom_node_text(aes(label = name), repel = TRUE, point.size = 8) +
  scale_size(range = c(3, 9)) +
  scale_edge_width(range = c(0.5, 3)) +
  theme(legend.position = "none")

Automating Discourse Network Analysis

Automating Discourse Network Analysis


  • A key bottleneck for DNA has long been that manual annotation is very labor-intensive, limiting studies to small corpora, short time periods, or single contexts (one country, one policy, one venue).

  • The central obstacle to automation is that DNA requires relational annotation: we cannot just assign a label to a document and train a classifier, but need to extract structured tuples—who said what about which concept, with what stance.

  • Earlier automation attempts (Haunss et al. 2020) used ML as a ‘pseudo-annotator’ to assist human coders, improving recall modestly but still requiring large training sets and manual actor identification.

  • Today, we have new tools for information extraction and classification, but challenges remain—from validation (what is a good gold standard?) to aggregation (how good does extraction need to be to yield valid networks?) (see Angst, Müller, and Walker 2025; Haunss et al. 2020).

Sketching out the Challenge


Today we follow the lower path (news reports). With attributed documents, such as consultation responses or press releases, the extraction step falls away.

Challenges of Extraction


  • Sometimes, documents have attributed authorship—the author is the speaker. Here, we already have the first two parts of the actor-statement-concept-stance tuple (e.g., position papers, press releases, consultation responses).

  • In other cases, documents contain statements by multiple actors and we first need to extract structured actor-statement pairs (e.g., newspaper articles, parliamentary debates).

  • Sometimes, simple rules (regex) suffice for extraction (e.g., transcripts with consistent formatting). But often we need models that can parse natural language to identify who said what.

  • This is especially the case for newspaper articles, which generally select statements with a view to balancing different (prominent) viewpoints (Hertzum 2022).

  • The task then essentially becomes a quote extraction task (Zhang and Liu 2021), which recent work suggests LLMs should perform quite well on.

Extracting Statements with LLMs

library(tidyverse)
library(ellmer)

readRenviron("~/.config/secrets/api-keys.env")

texts <- read_csv("handouts/dna-walkthrough/ai-act-texts.csv") |>
  mutate(doc = row_number())
reference <- read_csv("data/ai-act-reference-coding.csv")
# five models via OpenRouter, all without "thinking", which gets expensive on large corpora:
# a small open-weights model that also runs on a laptop, e.g., chat_ollama(model = "qwen3.8:27b"),
# and four cheap hosted models
models <- tribble(
  ~model,                            ~coder,
  "qwen/qwen3.8-27b",                "Qwen3.8 27B",
  "deepseek/deepseek-v4.1-flash",    "DeepSeek V4.1 Flash",
  "google/gemini-3.8-flash",         "Gemini 3.8 Flash",
  "openai/gpt-6-luna",               "GPT-6 Luna",
  "minimax/minimax-m3.1-flash:free", "MiniMax M3.1 Flash"
)

# OpenRouter passes open-weights models to one of several providers, some of which serve quantized
# versions; for comparable runs, fix provider and quantization in api_args, e.g.,
# provider = list(only = "<provider>", quantizations = list("bf16"))

# a fresh chat with one model and one system prompt, thinking switched off
# (Gemini and MiniMax cannot switch it off; the lowest effort keeps it short)
new_chat <- function(model, system_prompt) {
  reasoning <- if (str_starts(model, "google/")) {
    list(effort = "low")
  } else if (str_starts(model, "minimax/")) {
    list(effort = "minimal")
  } else {
    list(enabled = FALSE)
  }
  chat_openrouter(model = model, system_prompt = system_prompt,
                  params = params(temperature = 0), api_args = list(reasoning = reasoning))
}
extraction_prompt <- "
You are an expert at extracting attributed statements from news articles.

Task: Identify all passages where actors (people or organizations) express
positions, and extract them verbatim.

Guidelines:
- Only extract statements from IDENTIFIABLE actors (named person or named organization)
- Skip anonymous sources like 'critics say', 'experts warn', 'sources report'
- One entry per continuous passage (the same actor may appear several times)
- Extract the FULL passage, including the journalist's description and any quoted speech
- Copy the passage exactly as written; do not add commentary
- If a person speaks for an organization, give both name and organization
- If only an organization is mentioned, leave name NULL
"

# structured output: instead of free text, the model has to answer in a fixed format
# (a JSON schema), which ellmer turns into a data frame with one row per statement
# - type_object(): one statement with three fields (name, organization, passage)
# - type_array(): a list of such statements, as many as the model finds in the text
#   (an article has an unknown number of statements, so we cannot fix the fields in advance)
# - the descriptions tell the model what goes into each field
# The model's answer then looks like this:
#   [{"name": "Jonas Berger", "organization": "EuroTech Industry Alliance", "statement": "..."},
#    {"name": "Claire Dubois", "organization": "European Consumer Network", "statement": "..."}]
actor_statements <- type_array(
  items = type_object(
    name = type_string("Person's name exactly as written. NULL if only an organization is mentioned.",
                       required = FALSE),
    organization = type_string("Organization the person represents. NULL if not mentioned.",
                               required = FALSE),
    statement = type_string("The continuous passage, verbatim as it appears in the text.")
  )
)

# extract the attributed statements of one text with one model: start a chat with the
# instructions above, send the article, and get the answer back in the structure defined
# above (a small data frame, one row per statement)
extract_statements <- function(text, model) {
  new_chat(model, extraction_prompt)$chat_structured(
    paste0("Extract all attributed statements from this article:\n\n", text),
    type = actor_statements
  )
}

extracted <- texts |>
  # every text with every model: 4 texts × 5 models = 20 rows, so 20 calls
  cross_join(models) |>
  # one call per row; each answer is a small data frame, kept in a list-column
  mutate(statements = map2(text, model, extract_statements)) |>
  # spread these data frames into rows: one row per text, model and statement
  unnest(statements) |>
  # is the passage copied verbatim? Only then can it be written back into DNA
  mutate(verbatim = str_detect(text, fixed(statement)))

# how many passages each model found, and how many it copied verbatim
extracted |> count(coder, verbatim)

Extracted statements

DeepSeek V4.1 Flash: 10 passages. Qwen3.8 27B extracted 10, Gemini 3.8 Flash 9, GPT-6 Luna 11, MiniMax M3.1 Flash 12; all copied verbatim. Our own coding marks 13 shorter passages.

Text Actor Statement
1 Jonas Berger Jonas Berger, director general of the EuroTech Industry Alliance, argued that the rules for high-risk systems would “drive the next generation of AI companies to the United States”. “We are not against regulation, but nobody knows yet how to comply,” Berger said. “Postponing the deadline is the only way to give companies legal certainty.”
1 Claire Dubois Claire Dubois, director of the European Consumer Network, warned that every month of delay leaves people exposed to discriminatory algorithms in hiring and credit scoring. “Legal certainty means sticking to the timeline that everyone agreed on,” she said.
2 Sofia Lindqvist In the European Parliament, the proposal has divided the major groups. Green MEP Sofia Lindqvist called the delay “a gift to Big Tech dressed up as simplification”.
2 Markus Weber Christian Democrat MEP Markus Weber, by contrast, said his group would support postponing the high-risk obligations, “as long as the protection of fundamental rights is not watered down”.
2 Markus Weber Weber added that Europe could only shape global AI rules if it also built its own models: “Rules without European champions are just a manual for American products.”
3 European Commission A Commission spokesperson rejected the accusation of deregulation. “This is simplification, not deregulation. Clear and workable rules are the best protection for citizens and companies alike,” she said. The Commission argues that the technical standards needed to apply the high-risk rules will not be ready in time.
3 Tomás Ferreira Tomás Ferreira, digital policy officer at the trade union federation Digital Workers Europe, was unconvinced. Workers, he said, should not “pay the price for the lobbying success of a handful of US platforms”, pointing to AI systems already used to monitor and rate employees.
4 Lea Novak Lea Novak, founder of the Munich start-up Isarlogik, said the uncertainty itself was the problem: “Whether the rules come in 2026 or 2027 matters less to us than knowing what exactly they require.”
4 Lea Novak She warned against reading start-ups’ complaints as a rejection of regulation. “Trustworthy AI is our selling point against the American giants.”
4 Lea Novak What the debate ignored, Novak added, was the energy consumption of large models.

Concept Detection & Stance Classification

concepts <- tribble(
  ~concept_id, ~concept,              ~definition,
  "C1",        "C1 Competitiveness",  "Strict AI rules undermine Europe's competitiveness.",
  "C2",        "C2 Postpone",         "The obligations for high-risk AI systems should be postponed.",
  "C3",        "C3 Rights",           "AI systems threaten fundamental rights and need strict oversight.",
  "C4",        "C4 Legal certainty",  "Legal certainty about AI rules is essential.",
  "C5",        "C5 Own capabilities", "Europe must build its own AI capabilities."
)
# an alternative to this second step: add concept and stance to the extraction schema above,
# e.g., concept = type_enum(c("C1", ..., "C5")) and agreement = type_boolean(); one call per
# text, with the whole article as context, but harder to check, and a boolean forces a yes
# or no where the stance is unclear

stance_prompt <- "
You are an expert at coding policy debates. For each policy position, decide whether the
speaker of the statement clearly supports it, clearly opposes it, or does not address it.

- 'support': explicit agreement or clear endorsement of the position
- 'oppose': explicit disagreement or clear rejection of the position
- 'not_addressed': the position is not mentioned, only vaguely referenced, or the stance is ambiguous
"

# one support/oppose/not_addressed field per concept
stance_type <- concepts |>
  with(set_names(map(definition, \(d) type_enum(c("support", "oppose", "not_addressed"), d)), concept_id)) |>
  (\(fields) do.call(type_object, fields))()

# classify one statement on all concepts with one model
classify_stances <- function(statement, model) {
  positions <- paste0("- ", concepts$concept_id, ": ", concepts$definition, collapse = "\n")
  new_chat(model, stance_prompt)$chat_structured(
    paste0("POLICY POSITIONS:\n", positions, "\n\nSTATEMENT:\n", statement),
    type = stance_type
  )
}

coded <- extracted |>
  mutate(stances = map2(statement, model, classify_stances)) |>
  unnest_wider(stances)
coded |>
  select(doc, title, coder, model, name, organization, statement, verbatim, matches("^C[1-5]$")) |>
  write_csv("data/llm-statements.csv")

Comparing codings: stances


C1 Competitiveness
C2 Postpone
C3 Rights
C4 Legal certainty
C5 Own capabilities
O Q D G L M O Q D G L M O Q D G L M O Q D G L M O Q D G L M
Berger + + + + + + + + + + + + − + + + + + +
Dubois − − − − − − − + + + + + + + + + + + +
Lindqvist − − − − − −
Weber + + + + + + + + + + + + + + + +
Commission − + + + + + + + + +
Ferreira − − − + + + + + +
Novak − − − − − − − + + + + + + + + + + + +
O = our coding (the worksheet's reference coding), Q = Qwen3.8 27B, D = DeepSeek V4.1 Flash, G = Gemini 3.8 Flash, L = GPT-6 Luna, M = MiniMax M3.1 Flash, all without thinking (Gemini: low effort, MiniMax: minimal). + agrees, − disagrees; light: one coding differs, darker: the codings split. One run per model; results vary between runs.

Comparing codings: networks

Validation


  • The six networks look broadly similar: promising for a first look at a developing approach, but no substitute for formal validation.
  • What is the gold standard? Human coders disagree too (see the hand-coding exercise), and our coding is one contestable reading (e.g., Weber’s conditional support for C3, or Novak’s “trustworthy AI”).
  • Ensembles combine several models, e.g., by majority vote, or by averaging position estimates (Benoit et al. 2025). They remove the errors of single models, not the ones they share: a majority of our five models matches 13 of the 17 stances and adds only 2, but all five miss the Commission’s implicit rejection of C1.
  • With attributed documents (e.g., consultation responses), only the classification needs validating; with news articles, the extraction too.
  • Extraction is hard to score: exact string matching is too strict. A passage that is almost verbatim, or a little longer or shorter, is a minor error rather than a miss, which overlap-based (fuzzy) matching can grade.
  • How good does the coding need to be for the network to be valid?

Human in the loop: back into DNA


Since version 3.1.0, rDNA can write documents and statements into a DNA database (dna_addDocuments(), dna_addStatement()), which the release notes present as a way to implement AI-informed annotation workflows.

LLM pre-codes → statements imported into DNA → human coders check and correct them in the GUI → export networks as usual

All it needs is a table with the document, the exact passage, and the coded variables: the passage’s position in the text tells DNA where the statement goes. The reference database for today was built this way (handouts/dna-walkthrough/build-reference-dna.R).

Developing Concepts from Data


If we do not have an existing coding scheme, Kubli (2025) suggests an innovative approach to generate and validate concepts from data using LLMs:

‘For example, in analyzing European Parliamentary Debates on AI, the LLM first produces concise summaries of each sentence and then generates potential frame classes such as AI Benefits, AI Risks, AI Impact on Society, or AI Impact on Work. The researchers subsequently refine these automatically proposed classes through an iterative process that combines automated model evaluations and human-in-the-loop validation. By reviewing the generated classes, experts ensure alignment with theoretical constructs and empirical relevance, merging or discarding categories as necessary. This iterative refinement improves accuracy and interpretability, yielding a distilled set of frames that better capture the nuances of the underlying data’ (Kubli 2025).



Angst, Mario, Neitah Noemi Müller, and Viviane Walker. 2025. “Automated Extraction of Discourse Networks from Large Volumes of Media Data.” Network Science 13: e4. https://doi.org/10.1017/nws.2025.4.
Benoit, Kenneth, Scott De Marchi, Conor Laver, Michael Laver, and Jinshuai Ma. 2025. “Using Large Language Models to Analyze Political Texts Through Natural Language Understanding.” American Journal of Political Science, 1–17. https://doi.org/10.1111/ajps.70050.
Cox, Robert. 2004. “The Path-dependency of an Idea: Why Scandinavian Welfare States Remain Distinct.” Social Policy & Administration 38 (2): 204–19. https://doi.org/10.1111/j.1467-9515.2004.00386.x.
Hajer, Maarten A. 1993. “Discourse Coalitions and the Institutionalization of Practice: The Case of Acid Rain in Great Britain.” In The Argumentative Turn in Policy Analysis and Planning, edited by Frank Fischer and John Forester, 43–76. Durham, N.C.: Duke University Press.
Hall, Peter A. 2003. “Aligning Ontology and Methodology in Comparative Research.” In Comparative Historical Analysis in the Social Sciences, edited by James Mahoney and Dietrich Rueschemeyer, 373–404. Cambridge: Cambridge University Press.
Haunss, Sebastian, Jonas Kuhn, Sebastian Padó, Andre Blessing, Nico Blokker, Erenay Dayanik, and Gabriella Lapesa. 2020. “Integrating Manual and Automatic Annotation for the Creation of Discourse Network Data Sets.” Politics and Governance 8 (2): 326–39. https://doi.org/10.17645/pag.v8i2.2591.
Hertzum, Morten. 2022. “How Do Journalists Seek Information from Sources? A Systematic Review.” Information Processing & Management 59 (6): 103087. https://doi.org/10.1016/j.ipm.2022.103087.
Jenkins-Smith, Hank C., Daniel Nohrstedt, Christopher M. Weible, and Paul A. Sabatier. 2017. “The Advocacy Coalition Framework: Foundations, Evolution, and Ongoing Research.” In Theories of the Policy Process, edited by Christopher M. Weible and Paul A. Sabatier, 4th ed., 183–223. Milton: Routledge.
Kubli, Mael D. 2025. “Concept Navigation and Classification via Open-Source Large Language Model Processing.” https://www.arxiv.org/abs/2502.04756.
Lazer, David. 2011. “Networks in Political Science: Back to the Future.” PS: Political Science & Politics 44 (1): 61–68. https://doi.org/10.1017/S1049096510001873.
Leifeld, Philip. 2017. “Discourse Network Analysis: Policy Debates as Dynamic Networks.” In The Oxford Handbook of Political Networks, edited by Jennifer Nicoll Victor, Alexander H. Montgomery, and Mark Lubell, 301–25. New York, NY: Oxford University Press.
———. 2020. “Policy Debates and Discourse Network Analysis: A Research Agenda.” Politics and Governance 8 (2): 180–83. https://doi.org/10.17645/pag.v8i2.3249.
Leifeld, Philip, and Laurence Brandenberger. 2019. “Endogenous Coalition Formation in Policy Debates.” March 25, 2019. https://doi.org/10.48550/arXiv.1904.05327.
Leifeld, Philip, and Kristijan Garic. 2026. “Measuring Frame Evolution: Smoothed Temporal Framing Trajectories in Complex Policy Debates.” JCMS: Journal of Common Market Studies 64 (5): jcms.70119. https://doi.org/10.1111/jcms.70119.
Schaub, Simon, and Florence Metz. 2020. “Comparing Discourse and Policy Network Approaches: Evidence from Water Policy on Micropollutants.” Politics and Governance 8 (2): 184–99. https://doi.org/10.17645/pag.v8i2.2597.
Schmitz, Luuk, and Timo Seidl. 2023. “As Open as Possible, as Autonomous as Necessary: Understanding the Rise of Open Strategic Autonomy in EU Trade Policy.” JCMS: Journal of Common Market Studies 61 (3): 834–52. https://doi.org/10.1111/jcms.13428.
Schoch, David, and Termeh Shafie. 2025. “R for Social Network Analysis.” 2025. https://schochastics.github.io/R4SNA/intro.html.
Scott, John. 2012. What Is Social Network Analysis? Bloomsbury Academic.
Shim, Junseop, Chisung Park, and Mark Wilding. 2015. “Identifying Policy Frames Through Semantic Network Analysis: An Examination of Nuclear Energy Policy Across Six Countries.” Policy Sciences 48 (1): 51–83. https://doi.org/10.1007/s11077-015-9211-3.
Ward, Michael D., Katherine Stovel, and Audrey Sacks. 2011. “Network Analysis and Political Science.” Annual Review of Political Science 14 (1): 245–64. https://doi.org/10.1146/annurev.polisci.12.040907.115949.
Wasserman, Stanley, and Katherine Faust. 1994. Social Network Analysis: Methods and Applications. Structural Analysis in the Social Sciences 8. Cambridge ; New York: Cambridge University Press.
Zhang, Yuanchi, and Yang Liu. 2021. “DirectQuote: A Dataset for Direct Quotation Extraction and Attribution in News Articles.” October 14, 2021. http://arxiv.org/abs/2110.07827.