Game of Thrones Political Network

Quinta & Unzila

2026-10-27

Title

Game of Thrones Political Network

This presentation demonstrates how network analysis can be used to visualize character interactions as a political system.

The analysis uses an interaction dataset from A Song of Ice and Fire Book 1 and turns it into an interactive node-link graph.

What is Game of Thrones?

Game of Thrones is a fantasy series by George R.R. Martin set in the fictional continents of Westeros and Essos.

Why Game of Thrones? Scientific Justification

Fictional universes with large casts and political structures can serve as controlled simulations of social systems.

Why This Analysis Matters

Network analysis helps us study systems through relationships rather than isolated units.

Instead of asking only who the important actors are, it asks:

Data and Tools

This analysis uses two main R packages:

The dataset is loaded from character interactions in Book 1.

library(igraph)
library(visNetwork)

url <- "https://raw.githubusercontent.com/mathbeveridge/asoiaf/master/data/asoiaf-book1-edges.csv"
got_data <- read.csv(url)
head(got_data)
##                            Source             Target       Type weight book
## 1                  Addam-Marbrand    Jaime-Lannister Undirected      3    1
## 2                  Addam-Marbrand    Tywin-Lannister Undirected      6    1
## 3               Aegon-I-Targaryen Daenerys-Targaryen Undirected      5    1
## 4               Aegon-I-Targaryen       Eddard-Stark Undirected      4    1
## 5 Aemon-Targaryen-(Maester-Aemon)     Alliser-Thorne Undirected      4    1
## 6 Aemon-Targaryen-(Maester-Aemon)        Bowen-Marsh Undirected      4    1

Node and Edge Criteria

Node criterion: Each node represents a character from Book 1. A character is included if their name appears in the text.

Edge criterion: An edge is created between two characters whenever their names appear within 15 words of one another. Edge weight corresponds to the number of co-occurrences.

Filtering criterion: We filtered to keep only characters with degree ≥ 3 (interacting with at least 3 others) to keep the visualization readable.

Building the Graph

The edge list is converted into an undirected graph and filtered for core characters.

g <- graph_from_data_frame(got_data, directed = FALSE)
g_filtered <- induced_subgraph(g, v = V(g)[degree(g) >= 3])

cat("Full graph — Nodes:", vcount(g), " Edges:", ecount(g), "\n")
## Full graph — Nodes: 187  Edges: 684
cat("Filtered graph — Nodes:", vcount(g_filtered), " Edges:", ecount(g_filtered), "\n")
## Filtered graph — Nodes: 113  Edges: 587

General Network Statistics

cat("Number of nodes:", vcount(g_filtered), "\n")
## Number of nodes: 113
cat("Number of edges:", ecount(g_filtered), "\n")
## Number of edges: 587
cat("Average degree:", round(mean(degree(g_filtered)), 2), "\n")
## Average degree: 10.39
cat("Network density:", round(edge_density(g_filtered), 4), "\n")
## Network density: 0.0928
cat("Average clustering coefficient:", round(transitivity(g_filtered, type = "average"), 4), "\n")
## Average clustering coefficient: 0.7002
cat("Average path length:", round(average.path.length(g_filtered), 2), "\n")
## Average path length: 12.07
cat("Diameter:", diameter(g_filtered), "\n")
## Diameter: 29

Degree Distribution

Real social networks tend to follow a power law - a few central actors have many connections, while most have few (Barabási & Albert, 1999).

par(mfrow = c(1, 2))

hist(degree(g_filtered), 
     main = "Degree Distribution",
     xlab = "Degree", ylab = "Frequency",
     col = "steelblue", border = "white")

dd <- degree_distribution(g_filtered)
plot(log(1:length(dd)), log(dd),
     main = "Degree Distribution (Log-Log)",
     xlab = "Log(Degree)", ylab = "Log(Frequency)",
     pch = 19, col = "steelblue")

Community Detection (Louvain)

To reveal political or social clusters, we group the graph into communities.

communities <- cluster_louvain(g_filtered)
cat("Number of communities detected:", length(unique(membership(communities))), "\n")
## Number of communities detected: 6

Louvain Resolution Parameter

We test different resolution values to check if communities are stable.

communities <- cluster_louvain(g_filtered)
communities_default <- communities
communities_high <- cluster_louvain(g_filtered, resolution = 1.5)
communities_low <- cluster_louvain(g_filtered, resolution = 0.5)

cat("Default resolution — Communities:", length(unique(membership(communities_default))), "\n")
## Default resolution — Communities: 6
cat("High resolution (1.5) — Communities:", length(unique(membership(communities_high))), "\n")
## High resolution (1.5) — Communities: 7
cat("Low resolution (0.5) — Communities:", length(unique(membership(communities_low))), "\n")
## Low resolution (0.5) — Communities: 3

Verification — Do Communities Match Houses?

We check whether the detected communities correspond to known houses.

for (i in 1:length(unique(membership(communities_default)))) {
  chars <- names(membership(communities_default)[membership(communities_default) == i])
  cat("\nCommunity", i, ":\n")
  cat(paste(head(chars, 10), collapse = ", "), "... \n")
}
## 
## Community 1 :
## Aemon-Targaryen-(Maester-Aemon), Albett, Alliser-Thorne, Benjen-Stark, Bowen-Marsh, Chett, Gared, Grenn, Halder, Jaremy-Rykker ... 
## 
## Community 2 :
## Aerys-II-Targaryen, Alyn, Barristan-Selmy, Brandon-Stark, Cayn, Cersei-Lannister, Eddard-Stark, Gerold-Hightower, Harwin, Janos-Slynt ... 
## 
## Community 3 :
## Aggo, Cohollo, Daenerys-Targaryen, Doreah, Drogo, Haggo, Illyrio-Mopatis, Irri, Jhiqui, Jhogo ... 
## 
## Community 4 :
## Arya-Stark, Beric-Dondarrion, Boros-Blount, Gregor-Clegane, Ilyn-Payne, Jeyne-Poole, Joffrey-Baratheon, Loras-Tyrell, Meryn-Trant, Mordane ... 
## 
## Community 5 :
## Bran-Stark, Galbart-Glover, Hallis-Mollen, Hodor, Hullen, Jon-Umber-(Greatjon), Luwin, Nan, Osha, Rickard-Karstark ... 
## 
## Community 6 :
## Bronn, Brynden-Tully, Catelyn-Stark, Chiggen, Conn, Danwell-Frey, Edmure-Tully, Eon-Hunter, Hoster-Tully, Jaime-Lannister ...

Node Design

We define the visual properties of our characters (nodes).

nodes <- data.frame(
  id = V(g_filtered)$name,
  label = V(g_filtered)$name,
  value = degree(g_filtered), # Size based on degree
  group = membership(communities_default), # Color based on community
  title = paste0("<p><b>", V(g_filtered)$name, "</b><br>Connections: ", degree(g_filtered), "</p>")
)

head(nodes)
##                                                              id
## Aemon-Targaryen-(Maester-Aemon) Aemon-Targaryen-(Maester-Aemon)
## Aerys-II-Targaryen                           Aerys-II-Targaryen
## Aggo                                                       Aggo
## Albett                                                   Albett
## Alliser-Thorne                                   Alliser-Thorne
## Alyn                                                       Alyn
##                                                           label value group
## Aemon-Targaryen-(Maester-Aemon) Aemon-Targaryen-(Maester-Aemon)     6     1
## Aerys-II-Targaryen                           Aerys-II-Targaryen     6     2
## Aggo                                                       Aggo     6     3
## Albett                                                   Albett     3     1
## Alliser-Thorne                                   Alliser-Thorne    10     1
## Alyn                                                       Alyn     5     2
##                                                                                           title
## Aemon-Targaryen-(Maester-Aemon) <p><b>Aemon-Targaryen-(Maester-Aemon)</b><br>Connections: 6</p>
## Aerys-II-Targaryen                           <p><b>Aerys-II-Targaryen</b><br>Connections: 6</p>
## Aggo                                                       <p><b>Aggo</b><br>Connections: 6</p>
## Albett                                                   <p><b>Albett</b><br>Connections: 3</p>
## Alliser-Thorne                                  <p><b>Alliser-Thorne</b><br>Connections: 10</p>
## Alyn                                                       <p><b>Alyn</b><br>Connections: 5</p>

Edge Design

We define the visual properties of character interactions.

edges <- data.frame(
  from = as_edgelist(g_filtered)[,1],
  to = as_edgelist(g_filtered)[,2],
  width = E(g_filtered)$weight / 5,
  color = list(color = "rgba(150, 150, 150, 0.5)")
)

head(edges)
##                              from             to width                    color
## 1 Aemon-Targaryen-(Maester-Aemon) Alliser-Thorne   0.8 rgba(150, 150, 150, 0.5)
## 2 Aemon-Targaryen-(Maester-Aemon)    Bowen-Marsh   0.8 rgba(150, 150, 150, 0.5)
## 3 Aemon-Targaryen-(Maester-Aemon)          Chett   1.8 rgba(150, 150, 150, 0.5)
## 4 Aemon-Targaryen-(Maester-Aemon)   Jeor-Mormont   2.6 rgba(150, 150, 150, 0.5)
## 5 Aemon-Targaryen-(Maester-Aemon)       Jon-Snow   6.8 rgba(150, 150, 150, 0.5)
## 6 Aemon-Targaryen-(Maester-Aemon)  Samwell-Tarly   1.0 rgba(150, 150, 150, 0.5)

Final Network Plot

visNetwork(nodes, edges, main = "Game of Thrones: Political Network") %>%
  visIgraphLayout(layout = "layout_with_fr") %>%
  visOptions(
    highlightNearest = TRUE,
    nodesIdSelection = TRUE
  ) %>%
  visInteraction(navigationButtons = TRUE)

Comparison to Real Networks

Real sociopolitical networks have well-documented structural properties (Newman, 2003).

Property Real Social Networks Game of Thrones
Community structure Clear clusters Matches houses/factions
Clustering coefficient Typically 0.1-0.6 0.7
Small-world property Short path lengths 12.07 steps

Is Game of Thrones Realistic?

The network exhibits properties consistent with real sociopolitical networks:

Summary

What did we find? The fictional political system in Game of Thrones mirrors the structure of real social systems.

Conclusion: Even in a fictional universe, the patterns of human political interaction follow recognizable rules of connectivity and influence.

References