This additional analysis guide builds on the guide for constructing the Trump-Russia network provided here: https://devincr.github.io/trumpnetwork/abramson-network-guide. Please also visit the main page for more information on the project: https://devincr.github.io/trumpnetwork.
If you find this guide useful or use the data or analysis in your work, please cite it as:
The additional analysis below shows the key role that Felix Sater plays as a connector between the group of Trump and Putin affiliates in the mid 2000s. This can be seen visually on the dynamic plot, and is further clarified by examining certain network statistics for Donald Trump, Vladimir Putin, and Felix Sater over time. While both Trump and Putin gain connections in the years leading up to the mid-2000s, it isn’t until Felix Sater connects the Trump and Putin clusters in 2002 (and more meaningfully in 2006) that Sater’s betweenness score jumps (his role as a “bridge” between nodes) and the centralities of Trump and Putin also start to take off. The years 2002 and 2006 correspond to the years that Sater approaches Trump with a business deal (what becomes Trump Soho), and the year that Sater arranges for Ivanka Trump to sit in Putin’s office chair, indicating the extent of his direct reach in the Kremlin (which likely far predates 2006).
Examining the degree centralities for the full set of nodes shows that the network is “hierarchical” and the Trump and Putin nodes exhibit “preferential attachment” behavior. The hierarchical nature of the network is indicated by the expotential decay in node degrees when plotting the ordered degrees of the full network. That is: Trump has by far the greatest number of connections, and for each subsequent highest node, their number of connections drops of precipitously. That Trump and Putin exhibit “preferential attachment” behavior is indicated by the fact that their number of connections over time grows expotentially; the number of their connections in time t+1 appears to be a function of the number of connections they had in time t. That is: having a certain number of connections early on has the effect of attracting additional nodes due to feedback effects (due to having so many connections/being structurally important to the network).
The sections below walk through and demonstrate these findings, with code for replication provided (click ‘code’).
Using our edge list and node list, we can not only construct plots of the network connections but learn more about the network structure by assessing certain network statistics. Like other descriptive statistics—e.g. a mean of a sample—network statistics provide information describing the structure of the network. These can include centrality, betweenness, average path length, number of triangles (connections among groups of 3), etc.
Two main network statistics of particular interest in understanding the Trump-Russia network are degree centrality and betweenness centrality:
The degree centrality (degree) describes the number of connections a given node (actor) has in the network. A higher degree, the more connected the node is.
The betweenness centrality (betweenness) describes the number of shortest path lengths that pass through a given node. This effectively captures the extent to which a node serves as a “bridge” between other nodes. The higher the number, the more nodes for which that node is serving as a bridge.
Both of these network statistics provide an indication of who are the most important actors in the network, either because of the number of connections they have or their role as a connecting point for many other nodes.
An ordered list of degree centrality for the full network plot shows who are the most highly connected nodes in the network. The degree indicates how many connections that node has.
# load packages
library(ggplot2)
library(dplyr)
library(reshape2)
library(rio)
library(igraph)
# import data
edges <- import("links-stat+years.csv")
newnet <- subset(edges, select = c(from, to, year))
newnet$year <- as.numeric(newnet$year)
newnet$period <- as.numeric(as.factor(newnet$year))
net <- subset(newnet, select = c(from, to))
net <- as.matrix(net)
net <- graph_from_edgelist(net)
top.degrees <- sort(igraph::degree(net, mode = "all"), decreasing = T)
head(top.degrees)
## Donald Trump George Papadopoulos Vladimir Putin
## 105 43 38
## Michael Flynn Trump Campaign Carter Page
## 34 32 29
Plotting the degree centralities for the full network (all nodes as of 2019) shows that the network is “hierarchical”. The hierarchical nature of the network is indicated by the expotential decay in node degrees when plotting the ordered degrees of the full network. That is: Trump has by far the greatest number of connections, and for each subsequent highest node, their number of connections drops of precipitously.
# plot degrees of full network to show it's hierarchical
degs.full <- sort(igraph::degree(net), decreasing = T)
degs.full <- as.data.frame(degs.full)
colnames(degs.full) <- c("degrees")
degs.full$id <- rep(1:length(degs.full$degrees))
ggplot(degs.full, aes(x = id, y = degrees)) +
geom_point(shape = 1) +
theme_classic() +
labs(x = "Node", y = "Degree", main = "Sorted degrees for full network")
Given that we have network data over time, we might be interested to know how degrees change over time for particular actors. A time series plot of Donald Trump, Vladimir Putin, and Felix Sater shows the growth of their connections over time. That Trump and Putin exhibit “preferential attachment” behavior is indicated by the fact that their number of connections over time grows expotentially; the number of their connections in time t+1 appears to be a function of the number of connections they had in time t. That is: having a certain number of connections early on has the effect of attracting additional nodes due to feedback effects (due to having so many connections/being structurally important to the network).
# use for loop to create list of network objects
net <- list()
for (i in 1:max(newnet$period)){
net[[i]] <- subset(newnet, period <= i, select = c(from, to))
net[[i]] <- as.matrix(net[[i]])
net[[i]] <- graph_from_edgelist(net[[i]])
}
# use lapply to extract degrees for particular people for each network in the list
people <- c("Donald Trump", "Vladimir Putin", "Felix Sater")
deg <- do.call("cbind", lapply(1:max(newnet$period), function(i) igraph::degree(net[[i]])[people]))
# add row for year and melt dataframe to plot
deg <- rbind(deg, sort(unique(newnet$year)))
rownames(deg) <- c(people, "year")
deg <- t(deg)
deg <- as.data.frame(deg)
deg <- melt(deg, id = "year")
# plot the degrees over time
ggplot(deg, aes(x = year, y = value, color = variable, group = variable)) +
geom_line() +
scale_x_continuous(breaks = seq(1973, 2019, by = 1)) +
scale_y_continuous(breaks = seq(0,120, by = 10)) +
scale_color_manual(values = c("royalblue4",
"tomato4",
"yellowgreen")) +
labs(x = "Year", y = "Degree", title = "Degree over time") +
theme_classic() +
theme(axis.text.x = element_text(angle = 90, hjust = 1),
legend.title = element_blank())
We can extract the same information and plot for the betweenness scores.
net <- subset(newnet, select = c(from, to))
net <- as.matrix(net)
net <- graph_from_edgelist(net)
top.degrees <- sort(igraph::betweenness(net), decreasing = T)
head(top.degrees)
## Donald Trump George Papadopoulos Trump Campaign
## 25153.813 9710.954 7735.337
## Michael Flynn Vladimir Putin Carter Page
## 7254.204 5394.098 4099.034
We can similarly plot betweenness scores for certain actors over time. Betweenness effectively captures the extent to which a node serves as a “bridge” between other nodes. The higher the number, the more nodes for which that node is serving as a bridge.
# use for loop to create list of network objects
net <- list()
for (i in 1:max(newnet$period)){
net[[i]] <- subset(newnet, period <= i, select = c(from, to))
net[[i]] <- as.matrix(net[[i]])
net[[i]] <- graph_from_edgelist(net[[i]])
}
# use lapply to extract degrees for particular people for each network in the list
people <- c("Donald Trump", "Vladimir Putin", "Felix Sater")
bet <- do.call("cbind", lapply(1:max(newnet$period), function(i) igraph::betweenness(net[[i]])[people]))
# add row for year and melt dataframe to plot
bet <- rbind(bet, sort(unique(newnet$year)))
rownames(bet) <- c(people, "year")
bet <- t(bet)
bet <- as.data.frame(bet)
bet <- melt(bet, id = "year")
# plot
ggplot(bet, aes(x = year, y = value, color = variable, group = variable)) +
geom_line() +
scale_x_continuous(breaks = seq(1973, 2019, by = 1)) +
scale_y_continuous(breaks = seq(0,26000, by = 1000)) +
scale_color_manual(values = c("royalblue4",
"tomato4",
"yellowgreen")) +
labs(x = "Year", y = "Betweenness", title = "Betweenness over time") +
theme_classic() +
theme(axis.text.x = element_text(angle = 90, hjust = 1),
legend.title = element_blank())
Up until 2012, Sater, Putin, and Trump all seem to serve in this capacity to a similar extent. After 2012 and in the lead up to the 2016 election, however, Trump starts to become more important as a “bridge”. This may be the result of the fact that in 2013, Trump and Aras Agalarov (a Russian construction tycoon who as of 2012 had also worked with Putin on a deal) announced their plan to build Trump Tower Moscow. This deal brought many interested parties into the fold, as did Trump’s announcement to run for president in 2015. The US sanctions on Russia after Russia’s 2014 annexation of Crimea puts construction on Trump Tower Moscow on hold and is cited in the book as one potential motivating factor from Trump to pursue the presidency.
tbl_df(subset(edges, from == "Aras Agalarov" & to == "Donald Trump", select = c(from, to, year, title)))
## # A tibble: 1 x 4
## from to year title
## <chr> <chr> <int> <chr>
## 1 Aras Agal… Donald Tr… 2013 "ARAS AGALAROV >> DONALD TRUMP: Donald Trump…
The plots above provide some indication that Felix Sater is an important figure in the network, as his betweenness and degree scores track along with Trump and Putin up to a certain point when both of their trajectories start to take off in an expotential direction. To further explore the importance of Sater in connecting the Trump and Russia clusters, we can look at what the betweenness and degree scores would look like for Trump and Putin (the same plots as above) but without Sater in the network at all.
The plots below recreate the plots of Trump and Putin’s betweenness and degree scores over time with and without Sater in the network. The ligher colored lines show these values for Trump and Putin for the network without Sater. These plots help demonstrate that while both Trump and Putin gain connections in the years leading up to the mid-2000s, it isn’t until Felix Sater connects the Trump and Putin clusters in 2002 (and more meaningfully in 2006) that Sater’s betweenness score jumps (his role as a “bridge” between nodes) and the centralities of Trump and Putin also start to take off. Putin’s betweenness in the scenario without Sater remains close to 0 up through 2008, whereas in the scenario with Sater it jumps up in 2005, further showing that Sater is a key connecting link between the two clusters.
The years 2002 and 2006 correspond to the years that Sater approaches Trump with a business deal (what becomes Trump Soho), and the year that Sater arranges for Ivanka Trump to sit in Putin’s office chair, indicating the extent of his direct reach in the Kremlin (which likely far predates 2006).
# create new edge list without Felix Sater
newnet.no.sater <- subset(newnet, from != "Felix Sater" & to != "Felix Sater")
newnet.no.sater$period <- as.numeric(as.factor(newnet.no.sater$year))
# create new list of networks without Felix Sater
net.no.sater <- list()
for (i in 1:max(newnet.no.sater$period)){
net.no.sater[[i]] <- subset(newnet.no.sater, period <= i, select = c(from, to))
net.no.sater[[i]] <- as.matrix(net.no.sater[[i]])
net.no.sater[[i]] <- graph_from_edgelist(net.no.sater[[i]])
}
# get betweenness scores without Felix Sater (Sater will be NA)
people <- c("Donald Trump", "Vladimir Putin", "Felix Sater")
bet.no.sater <- do.call("cbind", lapply(1:max(newnet.no.sater$period), function(i) igraph::betweenness(net.no.sater[[i]])[people]))
bet.no.sater <- rbind(bet.no.sater, sort(unique(newnet.no.sater$year)))
rownames(bet.no.sater) <- c(people, "year")
bet.no.sater <- t(bet.no.sater)
bet.no.sater <- as.data.frame(bet.no.sater)
colnames(bet.no.sater) <- c("Donald Trump (w/o Sater)", "Vladimir Putin (w/o Sater", "Felix Sater (null)", "year")
# join previous betweenness scores (that included Sater in network) with new ones
bet.w.sater <- dcast(bet, year ~ variable)
bet.new <- left_join(bet.w.sater, bet.no.sater,by = "year")
bet.new <- melt(bet.new, id = "year")
# subset dataframe for better visibility of Sater's role in early 2000s
bet.plot <- subset(bet.new, year > 1990 & year < 2016)
# plot
ggplot(bet.plot, aes(x = year, y = value, color = variable, group = variable)) +
geom_line() +
scale_x_continuous(breaks = seq(1973, 2015, by = 1)) +
scale_y_continuous(breaks = seq(0,3000, by = 200)) +
scale_color_manual(values = c("royalblue4",
"tomato4",
"yellowgreen",
"royalblue1",
"tomato1",
"grey")) +
labs(x = "Year", y = "Betweenness", title = "Betweenness over time") +
theme_classic() +
theme(axis.text.x = element_text(angle = 90, hjust = 1),
legend.title = element_blank())
# get degree without Felix Sater (Sater will be NA)
deg.no.sater <- do.call("cbind", lapply(1:max(newnet.no.sater$period), function(i) igraph::degree(net.no.sater[[i]])[people]))
deg.no.sater <- rbind(deg.no.sater, sort(unique(newnet.no.sater$year)))
rownames(deg.no.sater) <- c(people, "year")
deg.no.sater <- t(deg.no.sater)
deg.no.sater <- as.data.frame(deg.no.sater)
colnames(deg.no.sater) <- c("Donald Trump (w/o Sater)", "Vladimir Putin (w/o Sater", "Felix Sater (null)", "year")
# join previous betweenness scores (that included Sater in network) with new ones
deg.w.sater <- dcast(deg, year ~ variable)
deg.new <- left_join(deg.w.sater, deg.no.sater,by = "year")
deg.new <- melt(deg.new, id = "year")
# subset for visibility and plot
deg.plot <- subset(deg.new, year > 1990 & year < 2016)
ggplot(deg.plot, aes(x = year, y = value, color = variable, group = variable)) +
geom_line() +
scale_x_continuous(breaks = seq(1973, 2015, by = 1)) +
scale_y_continuous(breaks = seq(0,70, by = 10)) +
scale_color_manual(values = c("royalblue4",
"tomato4",
"yellowgreen",
"royalblue1",
"tomato1",
"grey")) +
labs(x = "Year", y = "Degree centrality", title = "Degree centrality over time") +
theme_classic() +
theme(axis.text.x = element_text(angle = 90, hjust = 1),
legend.title = element_blank())