Ego Networks
Cassy Dorff and Shahryar Minhas
2026-06-05
Source:vignettes/ego_networks.Rmd
ego_networks.RmdThis vignette shows how netify handles ego networks—the
network surrounding a specific actor and their immediate
connections.
what are ego networks?
An ego network focuses on one actor (the “ego”) and includes:
- The ego: Your focal actor
- The alters: Actors directly connected to the
ego
- Alter-alter ties: Connections between the ego’s neighbors
Think of it as zooming in on one node and asking: “Who are their connections, and how are those connections related to each other?”
examples
Pakistan’s diplomatic neighborhood includes not just major powers (US, China) but regional rivals (India) and neighbors (Afghanistan). The connections between these alters matter—does Pakistan broker between disconnected partners or navigate a densely connected region?
A senator’s ego network reveals their coalition partners, but also whether those partners work together or represent distinct constituencies the senator must balance.
An activist organization’s ego network shows both allies and the broader movement structure—are they bridging disconnected groups or embedded in a tight cluster?
why ego networks matter
Ego networks let you apply network thinking without needing complete network data. You can study:
- How actors manage competing relationships
- Whether similar actors cluster together (homophily)
- How an actor’s position changes across relationships
- Whether an actor brokers between otherwise disconnected groups
getting started: extract an ego network
We’ll use data from the Integrated Crisis Early Warning System (ICEWS) to demonstrate:
data(icews)
# verbal cooperation between countries
netlet <- netify(
icews,
actor1 = "i", actor2 = "j",
time = "year",
weight = "verbCoop",
nodal_vars = c("i_polity2", "i_log_gdp", 'i_region'),
dyad_vars = c("matlCoop", "verbConf")
)extract your first ego network
The ego_netify() function makes extraction
straightforward:
pakistan_ego_net <- ego_netify(
netlet,
ego = "Pakistan"
)
print(pakistan_ego_net)
head(summary(pakistan_ego_net)[, c("net", "num_actors", "density", "num_edges")])
#> net num_actors density num_edges
#> 1 2002 33 0.8049242 425
#> 2 2003 28 0.9312169 352
#> 3 2004 49 0.7891156 928
#> 4 2005 44 0.8985201 850
#> 5 2006 40 0.8846154 690
#> 6 2007 39 0.9082321 673key features
1. network statistics
ngbd_summ <- summary(pakistan_ego_net)
head(ngbd_summ)
#> net layer num_actors density num_edges prop_edges_missing
#> 1 2002 Pakistan 33 0.8049242 425 0
#> 2 2003 Pakistan 28 0.9312169 352 0
#> 3 2004 Pakistan 49 0.7891156 928 0
#> 4 2005 Pakistan 44 0.8985201 850 0
#> 5 2006 Pakistan 40 0.8846154 690 0
#> 6 2007 Pakistan 39 0.9082321 673 0
#> mean_edge_weight sd_edge_weight median_edge_weight min_edge_weight
#> 1 227.2941 595.2732 35 1
#> 2 306.7926 740.5199 51 1
#> 3 128.4300 412.2462 22 1
#> 4 154.1388 499.4925 25 1
#> 5 169.6478 561.8834 25 1
#> 6 201.4740 578.2115 35 1
#> max_edge_weight competition sd_of_actor_means transitivity
#> 1 5760 0.08270328 244.3143 0.8513921
#> 2 5937 0.08521930 342.5283 0.9417385
#> 3 5141 0.06014910 142.8899 0.8390159
#> 4 6561 0.06605279 193.4328 0.9174501
#> 5 7579 0.07536095 215.7135 0.9116163
#> 6 6698 0.06860271 239.9547 0.9247124Pakistan’s network shows high density (0.79-0.97)—most countries that interact with Pakistan also interact with each other. The network size fluctuates between 24-49 actors across years, but that high density persists. This isn’t a hub-and-spoke pattern where Pakistan connects otherwise isolated actors; it’s a dense web where multilateral dynamics likely matter.
plot_graph_stats(ngbd_summ) +
scale_x_discrete(
breaks = seq(2002, 2014, by = 4)
) +
theme(
axis.text.x=element_text(angle=45, hjust=1)
)
Notice the consistently high transitivity (~0.84-0.97). Pakistan’s partners tend to cooperate with each other, creating a tightly clustered neighborhood rather than structural holes Pakistan could exploit.
2. actor-level analysis
ngbd_actor_summ <- summary_actor(pakistan_ego_net)
head(ngbd_actor_summ)
#> actor layer time degree prop_ties strength_sum strength_avg
#> 1 Pakistan Pakistan 2002 32 1.0000 10766 336.43750
#> 2 Afghanistan Pakistan 2002 30 0.9375 8864 295.46667
#> 3 Azerbaijan Pakistan 2002 24 0.7500 2435 101.45833
#> 4 Bangladesh Pakistan 2002 24 0.7500 1457 60.70833
#> 5 Canada Pakistan 2002 28 0.8750 1587 56.67857
#> 6 China Pakistan 2002 32 1.0000 16311 509.71875
#> strength_std strength_median network_share closeness betweenness eigen_vector
#> 1 671.72383 102.5 0.055724638 267.8291 0.0625000 0.37512591
#> 2 443.71921 73.5 0.045879917 253.9336 0.0625000 0.28240824
#> 3 219.03682 16.0 0.012603520 215.1176 0.0000000 0.08126347
#> 4 98.21869 13.5 0.007541408 157.0965 0.0000000 0.04026397
#> 5 104.11996 15.5 0.008214286 180.4695 0.0000000 0.06184088
#> 6 971.41673 223.5 0.084425466 273.2608 0.1229839 0.62166587Beyond degree (number of connections), look at eigenvector centrality—it captures importance based on connections to other important actors. China’s high and growing eigenvector centrality reflects not just bilateral ties with Pakistan but its connections to other key players in Pakistan’s neighborhood.
plot_actor_stats(ngbd_actor_summ,
across_actor=FALSE,
specific_actors=c('United States', 'China')
) +
scale_x_discrete(
breaks = seq(2002, 2014, by = 4)
) +
theme(
axis.text.x=element_text(angle=45, hjust=1)
)
The diverging trajectories are striking. China’s rising centrality measures show growing connectedness in Pakistan’s neighborhood, while the US shows more variable engagement despite maintaining presence.
3. visualization
By default, plot() highlights the ego:
plot(pakistan_ego_net)
#> Warning: Removed 1525 rows containing missing values or values outside the scale range
#> (`geom_point()`).
4. advanced visualization
For clearer visualization of alter-alter relationships, remove the ego’s edges—we already know everyone connects to Pakistan:
# attach actor stats so we can map them to node aesthetics
pakistan_ego_net <- add_node_vars(
pakistan_ego_net,
summary_actor(pakistan_ego_net),
"actor", "time"
)
# strip ties incident to ego so the alter-alter structure stands on its own
pakistan_no_ego_edges <- remove_ego_edges(pakistan_ego_net)
plot(pakistan_no_ego_edges,
layout = "hierarchical",
node_size_by = "i_log_gdp",
node_size_label = "Log(GDP)",
highlight = c("Pakistan", "United States", "China"),
highlight_color = c(
"Pakistan" = '#01411cff',
"United States" = "#0A3161",
"China" = "#EE1C25",
"Other" = 'grey40'),
edge_linewidth = 0.05,
mutate_weight = log1p,
edge_alpha_label = 'Log(Verb.\n Coop.)',
time_filter = as.character(seq(2002, 2014, 4))
) +
labs(
title = "Pakistan's Diplomatic Neighborhood",
subtitle = "Node size = GDP; Edges show cooperation between Pakistan's partners") +
theme(legend.position = 'right')
#> Warning: Removed 87 rows containing missing values or values outside the scale range
#> (`geom_point()`).
The visualization reveals how Pakistan’s partners form a dense web of relationships among themselves, with major powers like the US and China occupying central positions.
comparing ego networks
5. compare across actors
One strength of ego network analysis is systematic comparison:
powers <- c("United States", "China", "Russian Federation", "India", "Pakistan")
ego_networks <- lapply(powers, function(country) {
ego_netify(netlet, ego = country)
})
names(ego_networks) <- powers
summaries <- lapply(ego_networks, summary)
comparison_df <- bind_rows(summaries, .id = "country")
ggplot(comparison_df,
aes(x = net, y = density, color = country, group = country)) +
geom_line(linewidth = 1.2) +
labs(title = "Ego Network Density Over Time",
subtitle = "How interconnected are different countries' diplomatic neighborhoods?",
x = "Year", y = "Density") +
theme_bw() +
theme(
panel.border = element_blank(),
axis.ticks = element_blank(),
legend.position = "top"
)
Pakistan and India maintain the highest density—their partners are highly interconnected. The US shows lower density, suggesting more of a hub-and-spoke pattern. Russia’s volatile pattern may reflect shifting alliances during this period.
6. test for homophily
Do birds of a feather flock together? Let’s test if similar regime types cluster:
pakistan_homophily <- homophily(
pakistan_ego_net,
attribute = "i_polity2",
method = "correlation"
)
plot_homophily(pakistan_homophily, type = "temporal") +
labs(title = "Do Democracies Cluster in Pakistan's Network?",
subtitle = "Testing regime type homophily over time")
The results show weak and fluctuating homophily: regime type is not consistently associated with cooperation patterns in Pakistan’s neighborhood. Geopolitics appears more central than ideological alignment in this example.
7. control neighborhood boundaries
Sometimes you want to focus on only the strongest relationships:
# default includes all connections
pakistan_all <- ego_netify(netlet, ego = "Pakistan")
# only strong connections (threshold = 50 cooperation events)
pakistan_strong <- ego_netify(netlet, ego = "Pakistan", threshold = 50)
all_size <- mean(summary(pakistan_all)$num_actors)
strong_size <- mean(summary(pakistan_strong)$num_actors)
tibble(
Network = c("All connections", "Strong only (>50)"),
`Average Size` = round(c(all_size, strong_size), 1)
) |>
knitr::kable()| Network | Average Size |
|---|---|
| All connections | 34.7 |
| Strong only (>50) | 20.1 |
8. different relationship types
Networks of cooperation and conflict often follow different logics:
conflict_net <- netify(
icews,
actor1 = "i", actor2 = "j",
time = "year",
weight = "verbConf"
)
# cooperation vs conflict ego networks for pakistan
pak_coop_ego <- ego_netify(netlet, ego = "Pakistan")
pak_conf_ego <- ego_netify(conflict_net, ego = "Pakistan")
coop_stats <- summary(pak_coop_ego)
conf_stats <- summary(pak_conf_ego)
comparison <- tibble(
Metric = c("Average actors", "Average density"),
Cooperation = c(
round(mean(coop_stats$num_actors), 1),
round(mean(coop_stats$density), 3)
),
Conflict = c(
round(mean(conf_stats$num_actors), 1),
round(mean(conf_stats$density), 3)
)
)
knitr::kable(comparison)| Metric | Cooperation | Conflict |
|---|---|---|
| Average actors | 34.700 | 26.800 |
| Average density | 0.904 | 0.646 |
Pakistan’s conflict network is smaller but still substantial. The lower average density in conflict (about 0.65 vs 0.90) suggests conflicts are more bilateral while cooperation tends to be multilateral.
9. track network evolution
How stable are relationships over time?
pakistan_comparison <- compare_networks(pakistan_ego_net, what = "edges")
# zoom in on one transition
changes_2010_2011 <- pakistan_comparison$edge_changes$`2010_vs_2011`
stability_ratio <- round(
changes_2010_2011$maintained /
(changes_2010_2011$maintained + changes_2010_2011$removed), 3)
tibble(
Transition = "2010 to 2011",
Added = changes_2010_2011$added,
Removed = changes_2010_2011$removed,
Maintained = changes_2010_2011$maintained,
`Stability Ratio` = stability_ratio
) |>
knitr::kable()| Transition | Added | Removed | Maintained | Stability Ratio |
|---|---|---|---|---|
| 2010 to 2011 | 444 | 720 | 512 | 0.416 |
A stability ratio of 0.416 indicates moderate turnover – about 58% of relationships don’t persist year-to-year.
10. compare network structures: rising vs established powers
How do the US and China structure their diplomatic neighborhoods differently?
us_ego <- ego_netify(netlet, ego = "United States")
china_ego <- ego_netify(netlet, ego = "China")
structural_comp <- compare_networks(
list("US" = us_ego, "China" = china_ego),
what = 'structure'
)
#> Warning: Network US appears to be longitudinal. Using first time period for structural
#> comparison.
#> Warning: Network China appears to be longitudinal. Using first time period for
#> structural comparison.
comp_summary <- structural_comp$summary
avg_props <- comp_summary |>
group_by(network) |>
summarise(
`Avg. Nodes` = round(mean(num_actors), 0),
`Avg. Density` = round(mean(density), 3),
`Avg. Transitivity` = round(mean(transitivity), 3),
.groups = 'drop'
)
knitr::kable(avg_props)| network | Avg. Nodes | Avg. Density | Avg. Transitivity |
|---|---|---|---|
| China | 104 | 0.568 | 0.698 |
| US | 120 | 0.533 | 0.682 |
The US maintains a larger but less dense network—more of a hub-and-spoke pattern. China’s denser network suggests its partners are more interconnected, potentially reflecting regional concentration.
Let’s look at the temporal patterns:
us_summary <- summary(us_ego)
china_summary <- summary(china_ego)
temporal_comparison <- bind_rows(
us_summary |> mutate(network = "US"),
china_summary |> mutate(network = "China")
)
temporal_comparison$year <- as.integer(temporal_comparison$net)
ggplot(temporal_comparison, aes(x = year, y = density, color = network)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2) +
scale_color_manual(values = c("US" = "#377eb8", "China" = "#e41a1c")) +
labs(title = "Ego Network Density: US vs China",
subtitle = "China's increasing density reflects growing multilateral engagement",
x = "Year", y = "Density") +
theme_bw() +
theme(
panel.border = element_blank(),
axis.ticks = element_blank(),
legend.position = "top"
)
Now let’s examine how similar their cooperation patterns are:
comp_2012 <- compare_networks(
list(
"US_2012" = subset(us_ego, time = "2012"),
"China_2012" = subset(china_ego, time = "2012")
),
what = "edges",
method = "all"
)
edge_stats <- comp_2012$summary
edge_changes <- comp_2012$edge_changes[[1]]
tibble(
Metric = c("Edge correlation", "Jaccard similarity",
"Unique to US", "Unique to China", "Shared"),
Value = c(
round(edge_stats$correlation, 3),
round(edge_stats$jaccard, 3),
edge_changes$removed,
edge_changes$added,
edge_changes$maintained
)
) |>
knitr::kable()| Metric | Value |
|---|---|
| Edge correlation | 0.991 |
| Jaccard similarity | 0.645 |
| Unique to US | 2222.000 |
| Unique to China | 1104.000 |
| Shared | 6030.000 |
The very high correlation (0.991) but moderate Jaccard similarity (0.645) tells an interesting story: when both countries engage with a partner, they do so in similar ways, but the US maintains many more unique relationships (2,222 vs 1,104).
Finally, who’s in these networks?
# compare node composition
node_comp <- compare_networks(
list("US" = us_ego, "China" = china_ego),
what = "nodes"
)
# the 75% overlap reflects shared major partners
# but the us's 24 unique partners vs china's 8 shows its broader global reach
knitr::kable(node_comp$summary)| comparison | nodes_net1 | nodes_net2 | common_nodes | jaccard_similarity | nodes_added | nodes_removed |
|---|---|---|---|---|---|---|
| US vs China | 120 | 104 | 96 | 0.75 | 8 | 24 |
tl;dr
# extract ego network
ego_net <- ego_netify(netlet, ego = "Actor Name")
# basic analysis
summary(ego_net) # network-level stats
summary_actor(ego_net) # actor-level stats
plot(ego_net) # visualize
plot(remove_ego_edges(ego_net)) # focus on alter-alter ties
# advanced analysis
homophily(ego_net, attribute = "democracy") # test homophily
compare_networks(ego_net, what = "edges") # temporal comparison
compare_networks(list(ego1, ego2), what = "edges") # cross-sectional comparison