Ego Networks
Cassy Dorff and Shahryar Minhas
2026-10-21
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. ICEWS records events from each sender to each target, so we sum both directions to get one undirected tie per country pair and year:
data(icews)
# one row per country pair and year, summing both directions
icews_undirected <- icews |>
group_by(actor_a = pmin(i, j), actor_b = pmax(i, j), year) |>
summarize(
across(c(verbCoop, matlCoop, verbConf), sum),
.groups = "drop"
)
# one row per country and year
nodal_data <- icews |>
distinct(i, year, i_polity2, i_log_gdp, i_region)
# verbal cooperation between countries
netlet <- netify(
icews_undirected,
actor1 = "actor_a", actor2 = "actor_b",
time = "year",
weight = "verbCoop",
dyad_vars = c("matlCoop", "verbConf")
)
netlet <- add_node_vars(netlet, nodal_data, actor = "i", time = "year")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 31 0.8580645 399
#> 2 2003 29 0.9211823 374
#> 3 2004 49 0.7891156 928
#> 4 2005 45 0.8919192 883
#> 5 2006 40 0.8705128 679
#> 6 2007 39 0.9163293 679key 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 31 0.8580645 399 0
#> 2 2003 Pakistan 29 0.9211823 374 0
#> 3 2004 Pakistan 49 0.7891156 928 0
#> 4 2005 Pakistan 45 0.8919192 883 0
#> 5 2006 Pakistan 40 0.8705128 679 0
#> 6 2007 Pakistan 39 0.9163293 679 0
#> mean_edge_weight sd_edge_weight median_edge_weight min_edge_weight
#> 1 447.7093 1130.1598 97.0 1
#> 2 577.3797 1389.1304 100.5 1
#> 3 252.4741 802.3923 45.0 1
#> 4 292.1699 933.7845 48.0 1
#> 5 334.5773 1064.4979 54.0 1
#> 6 397.9087 1116.5289 74.0 1
#> max_edge_weight competition sd_of_actor_means transitivity
#> 1 11763 0.08302184 489.8849 0.8868895
#> 2 9768 0.08263100 639.6115 0.9336033
#> 3 9804 0.05972350 279.3920 0.8390159
#> 4 11759 0.06377164 360.3542 0.9118179
#> 5 14169 0.07270890 407.4732 0.9020027
#> 6 13823 0.06602829 463.5857 0.9310701Pakistan’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 26-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 30 1.0000000 20757 691.9000
#> 2 Afghanistan Pakistan 2002 29 0.9666667 16772 578.3448
#> 3 Australia Pakistan 2002 27 0.9000000 5427 201.0000
#> 4 Azerbaijan Pakistan 2002 23 0.7666667 4863 211.4348
#> 5 Bangladesh Pakistan 2002 24 0.8000000 3029 126.2083
#> 6 Canada Pakistan 2002 28 0.9333333 3469 123.8929
#> strength_std strength_median network_share closeness betweenness eigen_vector
#> 1 1286.4883 199.5 0.058098592 787.4105 0.00000000 0.38000799
#> 2 834.6176 208.0 0.046944625 732.1774 0.06666667 0.28510565
#> 3 409.1559 50.0 0.015190107 606.4470 0.00000000 0.11900674
#> 4 455.2181 26.0 0.013611478 596.3886 0.00000000 0.08960396
#> 5 208.1527 27.5 0.008478134 394.8759 0.00000000 0.04610106
#> 6 232.8033 40.5 0.009709689 495.3738 0.00000000 0.07397748Beyond degree (number of connections), look at eigenvector centrality—it captures importance based on connections to other important actors. The US has the highest eigenvector centrality in Pakistan’s neighborhood every year, reflecting not just bilateral ties with Pakistan but its connections to other key players in the region.
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)
)
Both powers are tied to every other actor in Pakistan’s neighborhood in every year, so the differences lie in intensity. The US consistently leads, with roughly twice China’s cooperation volume (strength), while China’s eigenvector centrality rose through the mid-2000s and then fluctuated at lower levels.
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')
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 and China show the lowest density, suggesting partners drawn from many regions that interact less with each other. Russia sits in between and stays fairly stable (roughly 0.70-0.78) across the 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.5 |
| Strong only (>50) | 29.6 |
8. different relationship types
Networks of cooperation and conflict often follow different logics:
conflict_net <- netify(
icews,
actor1 = "i", actor2 = "j",
time = "year",
sum_dyads = TRUE,
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.500 | 22.000 |
| Average density | 0.909 | 0.756 |
Pakistan’s conflict network is smaller but still substantial. The lower average density in conflict (about 0.76 vs 0.91) 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 | 148 | 247 | 253 | 0.506 |
A stability ratio of 0.506 indicates moderate turnover – about 49% of the 2010 relationships don’t persist into 2011.
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")
us_summary <- summary(us_ego)
china_summary <- summary(china_ego)
# average yearly statistics for each ego network
avg_props <- bind_rows(
"US" = us_summary,
"China" = china_summary,
.id = "Network"
) |>
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 | 109 | 0.586 | 0.710 |
| US | 120 | 0.572 | 0.698 |
Averaged across years, the US maintains a larger network than China, but the two are structured similarly: density and transitivity are close, with China’s only slightly higher. The difference between the two powers lies more in the reach of their neighborhoods than in how interconnected their partners are.
Let’s look at the temporal patterns:
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 | 1 |
| Jaccard similarity | 1 |
| Unique to US | 1183 |
| Unique to China | 433 |
| Shared | 3111 |
Similarity measures are computed on the actors that appear in both ego networks. Because both ego networks are drawn from the same ICEWS data, ties among shared partners are identical, so the correlation and Jaccard similarity are 1. The difference lies in who each ego network includes: the US ego network holds 1,183 ties that China’s lacks, China’s holds 433 that the US network lacks, and 3,111 ties appear in both.
Finally, who’s in these networks?
# compare node composition in 2012
node_comp <- compare_networks(
list(
"US" = subset(us_ego, time = "2012"),
"China" = subset(china_ego, time = "2012")
),
what = "nodes"
)
knitr::kable(
node_comp$summary,
col.names = c(
"Comparison", "US actors", "China actors", "Shared actors",
"Jaccard similarity", "Unique to China", "Unique to US"
),
digits = 2
)| Comparison | US actors | China actors | Shared actors | Jaccard similarity | Unique to China | Unique to US |
|---|---|---|---|---|---|---|
| US vs China | 122 | 109 | 97 | 0.72 | 12 | 25 |
The two neighborhoods overlap heavily (Jaccard similarity of 0.72), reflecting shared major partners, but the US network includes 25 actors absent from China’s, while China’s includes only 12 absent from the US network, consistent with broader US global reach.
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