Measuring Who Connects with Whom: Homophily & Dyadic Analysis
Cassy Dorff and Shahryar Minhas
2025-06-30
Source:vignettes/attribute_analysis.Rmd
attribute_analysis.Rmd
Vignette Summary
This vignette demonstrates how to play with netify to explore relationships between international alliance patterns and country characteristics using data from the Correlates of War (COW) project and Alliance Treaty Obligations and Provisions (ATOP) data. We’ll toy around with some simple international relations ideas:
- Democratic Peace: Do democracies cooperate more with each other than with non-democracies?
- Economic Interdependence: Do countries with similar economic development levels cooperate more?
- Geographic Proximity: Does geographic distance affect cooperation patterns?
- Regional Clustering: Do countries primarily cooperate within their own regions?
We’ll focus on how to do some exploratory statistical analysis with netify:
-
homophily()
: Do Birds of a Feather Flock Together?- Tests whether similar countries tend to form alliances with each other. For example, do democracies primarily ally with other democracies? Do rich countries mainly partner with other rich countries?
- Calculates correlations between attribute similarity and network tie presence using multiple similarity metrics. Performs permutation-based significance testing to determine if observed homophily patterns exceed random chance.
- Function notes:
- Flexible similarity metrics: Correlation, euclidean, categorical, cosine and other methods
- Statistical rigor: Permutation tests with confidence intervals
- Smart missing data handling: Preserves maximum sample sizes
- Multi-network ready: Works across time periods and layers
-
mixing_matrix()
: Who Actually Partners With Whom?- Creates detailed “who allies with whom” tables. Shows not just that democracies prefer democracies, but exactly how much they interact with autocracies, hybrid regimes, etc. Think of it as a cross-tabulation on steroids.
- Constructs mixing matrices showing tie distributions across attribute combinations. Calculates assortativity coefficients, modularity scores, and entropy measures to quantify mixing patterns with optional normalization schemes.
- Function notes:
- Cross-dimensional analysis: How regime types mix across regions (unique feature!)
-
Rich summary statistics: Assortativity, modularity,
entropy, diagonal proportions
- Flexible normalization: Raw counts, proportions, or row-normalized
- Weighted network support: Incorporates alliance strength, not just presence
-
dyad_correlation()
: What Relationship Factors Drive Alliances?- Tests how characteristics of country pairs (like geographic distance, trade volume, or cultural similarity) predict whether they’ll form alliances. Answers questions like “Do nearby countries ally more often?”
- Correlates dyadic (pairwise) variables with network ties using multiple correlation methods. Supports partial correlation analysis to control for confounding dyadic factors while handling missing data through pairwise deletion.
- Function notes:
- Partial correlation support: Isolate specific effects while controlling for others
- Multiple correlation methods: Pearson, Spearman, Kendall with significance testing
- Binary network options: Analyze tie presence vs. strength separately
- Comprehensive diagnostics: Descriptive stats for all variables
-
attribute_report()
: The Complete Picture in One Function- A kind of Swiss Army knife for understanding how country characteristics relate to alliance patterns. Automates running relevant analyses and tells you what makes countries influential, who allies with whom, and what drives partnership formation.
- Comprehensive wrapper combining homophily analysis, mixing matrices, dyadic correlations, and centrality-attribute relationships. Tries to automatically determine appropriate methods based on variable types.
Data Preparation
We’ll use the Correlates of War data via the
peacesciencer
package to build a network of international
alliances. This data includes measures of democracy, economic
development, military capabilities, geographic relationships between
countries, and alliance commitments from the ATOP dataset.
COW data
# Download peacesciencer external data if needed
peacesciencer::download_extdata()
# Create dyadic dataset for a recent 5-year period
cow_dyads <- create_dyadyears(subset_years = c(2010:2014)) |>
# Add conflict data (we'll use inverse for cooperation)
add_cow_mids() |>
# Add capital distance
add_capital_distance() |>
# Add democracy scores (V-Dem polyarchy)
add_democracy() |>
# Add GDP data
add_sdp_gdp() |>
# Add material capabilities
add_nmc() |>
# Add ATOP alliance data
add_atop_alliance()
# Create alliance cooperation measure based on ATOP alliance types
cow_dyads <- cow_dyads |>
mutate(
# Create alliance intensity score (0-5 based on number of pledge types)
alliance_score = atop_defense + atop_offense + atop_neutral + atop_nonagg + atop_consul,
# Normalize to 0-1 scale
alliance_norm = alliance_score / 5,
# Create cooperation score: alliance intensity without conflict
cooperation = alliance_norm,
# cooperation = alliance_norm * (1 - cowmidonset),
# Add region information
region1 = countrycode(ccode1, "cown", "region"),
region2 = countrycode(ccode2, "cown", "region"),
# Log transform some variables
log_gdp1 = log(wbgdp2011est1 + 1),
log_gdp2 = log(wbgdp2011est2 + 1),
log_capdist = log(capdist + 1),
# Renaming to make stuff easier down the road
alliance_intensity = alliance_norm,
defense_alliance = atop_defense
)
# Filter to 2012 for cross-sectional analysis
cow_2012 <- cow_dyads |>
filter(year == 2012)
# Create alliance network
alliance_net <- netify(
cow_2012,
actor1 = 'ccode1', actor2 = 'ccode2',
symmetric = TRUE,
weight = 'cooperation'
)
# Print object
print(alliance_net)
# Prepare nodal data with country attributes
nodal_data <- cow_2012 |>
select(
ccode1, region1, v2x_polyarchy1,
log_gdp1, cinc1
) |>
distinct() |>
rename(
actor = ccode1,
region = region1,
democracy = v2x_polyarchy1,
log_gdp = log_gdp1,
mil_capability = cinc1
) |>
mutate(
# Create democracy categories based on V-Dem scores
regime_type = case_when(
democracy >= 0.6 ~ "Democracy",
democracy >= 0.4 ~ "Hybrid",
democracy < 0.4 ~ "Autocracy",
TRUE ~ "Unknown"
),
# Create development categories
development = case_when(
log_gdp >= quantile(log_gdp, 0.75, na.rm = TRUE) ~ "High",
log_gdp >= quantile(log_gdp, 0.25, na.rm = TRUE) ~ "Medium",
TRUE ~ "Low"
)
)
# Add country names for better interpretation
nodal_data$country_name <- countrycode(nodal_data$actor, "cown", "country.name")
# Add nodal variables to network
alliance_net <- add_node_vars(alliance_net, nodal_data, actor = "actor")
Add dyadic (relationship-level) variables:
# Prepare dyadic data
dyad_data <- cow_2012 |>
select(ccode1, ccode2, log_capdist, alliance_norm, atop_defense) |>
rename(
actor1 = ccode1,
actor2 = ccode2,
geographic_distance = log_capdist,
alliance_intensity = alliance_norm,
defense_alliance = atop_defense
)
# Add dyadic variables to network
alliance_net <- add_dyad_vars(
alliance_net,
dyad_data = dyad_data,
actor1 = "actor1",
actor2 = "actor2",
dyad_vars = c("geographic_distance", "alliance_intensity", "defense_alliance"),
dyad_vars_symmetric = c(TRUE, TRUE, TRUE)
)
1. Testing the Democratic Peace with homophily()
The democratic peace theory posits that democracies rarely engage in conflict with one another, driven by shared liberal norms, institutional constraints on executive power, and transparency in political decision-making. Here we examine whether these same mechanisms that reduce conflict might also promote cooperation, specifically, whether democratic states demonstrate a preference for forming alliances with other democracies.
Here are the edited sections with interpretive output similar to the regional clustering example:
🔍 Using homophily()
for Continuous Variables
The homophily()
function is a tool in
netify that tests whether similar actors tend to
connect more in a network. It can handle both continuous and categorical
attributes.
# Test if countries with similar democracy levels form more alliances
democracy_homophily <- homophily(
alliance_net, # Our network object
attribute = "democracy", # Node attribute to analyze
method = "correlation", # Method for continuous variables
significance_test = TRUE # Perform statistical significance test
)
knitr::kable(democracy_homophily, digits=3, align='c')
net | layer | attribute | method | threshold_value | homophily_correlation | mean_similarity_connected | mean_similarity_unconnected | similarity_difference | p_value | ci_lower | ci_upper | n_connected_pairs | n_unconnected_pairs | n_missing | n_pairs |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | cooperation | democracy | correlation | 0 | 0.14 | -0.243 | -0.312 | 0.069 | 0 | 0.125 | 0.155 | 3558 | 11493 | 21 | 18915 |
# Build summary message
democracy_summary <- paste0(
"**Democracy Homophily Results:**\n\n",
"- Homophily correlation: ", round(democracy_homophily$homophily_correlation, 3), "\n",
"- Avg similarity among allies: ", round(democracy_homophily$mean_similarity_connected, 3), "\n",
"- Avg similarity among non-allies: ", round(democracy_homophily$mean_similarity_unconnected, 3), "\n",
"- P-value: ", round(democracy_homophily$p_value, 3), "\n",
if(democracy_homophily$p_value < 0.05) {
"→ Democracies significantly tend to ally with similarly democratic countries\n"
} else {
"→ No significant democracy-based alliance preferences detected\n"
}
)
Democracy Homophily Results:
- Homophily correlation: 0.14
- Avg similarity among allies: -0.243
- Avg similarity among non-allies: -0.312
- P-value: 0 → Democracies significantly tend to ally with similarly democratic countries
Understanding the Output:
- homophily_correlation: Measures tendency for similar values to connect (0 to 1)
- mean_similarity_connected: Average similarity among connected pairs
- mean_similarity_unconnected: Average similarity among unconnected pairs
- p_value: Statistical significance of the homophily pattern
The democracy homophily analysis reveals a statistically significant pattern of democratic countries preferring to form alliances with other democracies. With a homophily correlation of 0.140 (p < 0.05), there is evidence that similarity in democratic values influences alliance formation. The negative similarity values (-0.243 for allies vs -0.312 for non-allies) reflect the correlation method’s calculation of similarity scores, where higher (less negative) values indicate greater similarity. This 0.069 difference between allied and non-allied pairs demonstrates that countries in alliances tend to have more similar democracy scores than those without alliance ties. The finding extends democratic peace theory beyond conflict avoidance—democracies not only rarely fight each other but also show a moderate tendency to select each other as alliance partners. This pattern across 3,558 alliance pairs likely reflects shared preferences for international institutions, compatible domestic constraints on foreign policy, and the reduced uncertainty that comes from transparency in democratic decision-making processes.
Visualizing Homophily Patterns
We can visualize the homophily pattern to better understand how democracy similarity relates to alliance formation:
# Visualize the democracy homophily pattern
plot_homophily(democracy_homophily, alliance_net,
type = "distribution",
attribute = "democracy",
method = "correlation",
sample_size = 5000) + # Sample for faster plotting with large networks
labs(subtitle = "Allied countries show greater similarity in democracy scores") +
# Expand x-axis limits to show full distribution
xlim(c(-1, 1))
Understanding the Distribution Shape
The distribution plot reveals the empirical density of pairwise
similarity scores computed using the correlation method from
calculate_similarity_matrix()
. The distinctly non-normal
shape arises from the specific calculation procedure:
Details of the Similarity Calculation:
For continuous attributes like democracy scores, when
method = "correlation"
is specified, the homophily function
computes pairwise similarities as:
# For each dyad (i,j), similarity is calculated as:
similarity[i,j] = cor(attr[i], attr[j])
However, since we’re dealing with scalar attribute values (single democracy score per country) rather than vectors, the function actually computes a transformed distance metric that preserves the correlation interpretation. Specifically, it uses:
# Standardize the attribute
z_attr = (attr - mean(attr)) / sd(attr)
# Compute pairwise "correlation-like" similarity
similarity[i,j] = 1 - abs(z_attr[i] - z_attr[j]) / max_possible_distance
This produces similarity scores that:
- Range from -1 to 1 (like correlations)
- Capture relative similarity in standardized attribute space
- Generate the observed multimodal distribution due to the discrete clustering of democracy scores
Interpretation of the Result
The observed homophily correlation of 0.140 indicates that despite these distributional complexities, allied countries do exhibit systematically higher democracy similarity scores than non-allied pairs. The mean difference (-0.243 vs -0.312) is statistically significant even though both distributions exhibit similar non-normal shapes.
However, the extensive overlap between distributions reveals that democracy similarity is just one factor among many driving alliance formation. Many democratic countries ally with non-democracies (left side of blue distribution), while many similar democracies remain unallied (right side of gold distribution). This pattern reflects the reality of international politics: shared democratic values facilitate cooperation, but geographic, security, and economic considerations often prove equally or more influential in shaping alliance networks.
🔍 Using homophily()
for Categorical Variables
Now let’s move onto the categorical regime type variable we made:
## actor region democracy log_gdp mil_capability regime_type
## 1 100 Latin America & Caribbean 0.627 3.337583 6.695519e-03 Democracy
## 2 101 Latin America & Caribbean 0.414 3.332919 4.859170e-03 Hybrid
## 3 110 Latin America & Caribbean 0.590 3.148239 3.355918e-05 Hybrid
## 4 115 Latin America & Caribbean 0.772 3.169770 4.996089e-05 Democracy
## 5 130 Latin America & Caribbean 0.582 3.290452 1.530854e-03 Hybrid
## 6 135 Latin America & Caribbean 0.775 3.315930 3.378733e-03 Democracy
## development country_name
## 1 High Colombia
## 2 High Venezuela
## 3 Low Guyana
## 4 Low Suriname
## 5 Medium Ecuador
## 6 High Peru
# Test using categorical regime types
regime_homophily <- homophily(
alliance_net,
attribute = "regime_type",
method = "categorical",
significance_test = TRUE)
knitr::kable(regime_homophily, digits=3, align='c')
net | layer | attribute | method | threshold_value | homophily_correlation | mean_similarity_connected | mean_similarity_unconnected | similarity_difference | p_value | ci_lower | ci_upper | n_connected_pairs | n_unconnected_pairs | n_missing | n_pairs |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | cooperation | regime_type | categorical | 0 | 0.133 | 0.41 | 0.262 | 0.148 | 0 | 0.119 | 0.148 | 4033 | 14882 | 0 | 18915 |
# Build regime type summary message
regime_summary <- paste0(
"**Regime Type Homophily Results:**\n\n",
"- Homophily score: ", round(regime_homophily$homophily_correlation, 3), "\n",
"- Same-regime alliances: ", round(regime_homophily$mean_similarity_connected * 100, 1), "%\n",
"- Different-regime alliances: ", round((1 - regime_homophily$mean_similarity_connected) * 100, 1), "%\n",
"- Expected if random: ", round(regime_homophily$mean_similarity_unconnected * 100, 1), "%\n",
"- P-value: ", round(regime_homophily$p_value, 3), "\n",
if(regime_homophily$p_value < 0.05 && regime_homophily$homophily_correlation > 0.1) {
"→ Countries strongly prefer allies with similar political systems\n"
} else {
"→ Regime type doesn't significantly influence alliance formation\n"
}
)
Regime Type Homophily Results:
- Homophily score: 0.133
- Same-regime alliances: 41%
- Different-regime alliances: 59%
- Expected if random: 26.2%
- P-value: 0 → Countries strongly prefer allies with similar political systems
The regime type analysis also reveals a pattern of political homophily in alliance formation. With a homophily score of 0.133 (p < 0.05), countries demonstrate a clear preference for forming alliances with similar regime types. The similarity scores show that 41% of allied pairs share the same regime type, compared to only 26.2% of non-allied pairs. This 14.8 percentage point difference suggests that political regime compatibility plays at least some role in international cooperation.
The categorical nature of this analysis provides a clearer interpretation than continuous measures: when countries form alliances, there’s a 41% chance their partner shares the same regime type, compared to only 26.2% for non-allied pairs. This pattern supports the idea that shared political institutions and governance norms facilitate international cooperation, though the effect remains moderate enough to allow for substantial cross-regime alliances driven by strategic necessities – though note that we are not specifically testing the democratic peace idea here specifically as we are amalgamating autocracy-autocracy and democracy-democracy pairs into our same-regime bucket.
Visualizing Categorical Homophily
For categorical variables like regime type, the visualization looks different. Instead of continuous similarity distributions, we see discrete categories:
# Visualize regime type homophily
plot_homophily(regime_homophily, alliance_net,
type = "distribution",
attribute = "regime_type",
method = "categorical",
sample_size = 5000) +
labs(title = "Regime Type Homophily in Alliance Networks",
subtitle = "Do similar political systems form more alliances?")
Unlike the continuous democracy score, regime type similarity is binary: country pairs either share the same regime type (similarity = 1) or they don’t (similarity = 0). The visualization shows two bars comparing the proportion of alliances within each category. A higher blue bar at similarity = 1 indicates that countries with the same regime type are more likely to form alliances than those with different regime types. This categorical approach provides a clearer test of the “democracies ally with democracies” hypothesis, though it loses the nuance of how similar countries are on the democracy spectrum.
2. Economic Interdependence and Development
International relations theory suggests that countries with similar levels of economic development tend to form more alliances. Let’s test this hypothesis:
# Test if countries with similar GDP levels form more alliances
gdp_homophily <- homophily(
alliance_net,
attribute = "log_gdp",
method = "correlation",
significance_test = TRUE)
knitr::kable(gdp_homophily, digits=3, align='c')
net | layer | attribute | method | threshold_value | homophily_correlation | mean_similarity_connected | mean_similarity_unconnected | similarity_difference | p_value | ci_lower | ci_upper | n_connected_pairs | n_unconnected_pairs | n_missing | n_pairs |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | cooperation | log_gdp | correlation | 0 | 0.11 | -0.085 | -0.106 | 0.02 | 0 | 0.098 | 0.123 | 3902 | 13864 | 6 | 18915 |
# Build economic development summary message
economic_summary <- paste0(
"**Economic Development Homophily Results:**\n\n",
"- Homophily correlation: ", round(gdp_homophily$homophily_correlation, 3), "\n",
"- Similarity among allies: ", round(gdp_homophily$mean_similarity_connected, 3), "\n",
"- Similarity among non-allies: ", round(gdp_homophily$mean_similarity_unconnected, 3), "\n",
"- P-value: ", round(gdp_homophily$p_value, 3), "\n",
if(gdp_homophily$p_value < 0.05 && gdp_homophily$homophily_correlation > 0) {
"→ Countries at similar development levels are more likely to form alliances\n"
} else {
"→ Economic development levels don't significantly predict alliance patterns\n"
}
)
Economic Development Homophily Results:
- Homophily correlation: 0.11
- Similarity among allies: -0.085
- Similarity among non-allies: -0.106
- P-value: 0 → Countries at similar development levels are more likely to form alliances
3. Regional Clustering in International Cooperation
Do countries primarily form alliances within their own regions, or are alliances more globally distributed? Regional patterns provide another example of categorical homophily:
# Test regional homophily
region_homophily <- homophily(
alliance_net,
attribute = "region",
method = "categorical",
significance_test = TRUE)
knitr::kable(region_homophily, digits=3, align='c')
net | layer | attribute | method | threshold_value | homophily_correlation | mean_similarity_connected | mean_similarity_unconnected | similarity_difference | p_value | ci_lower | ci_upper | n_connected_pairs | n_unconnected_pairs | n_missing | n_pairs |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | cooperation | region | categorical | 0 | 0.785 | 0.796 | 0.034 | 0.761 | 0 | 0.774 | 0.796 | 4033 | 14882 | 0 | 18915 |
# Build regional clustering summary message
regional_summary <- paste0(
"**Regional Clustering Results:**\n\n",
"- Homophily score: ", round(region_homophily$homophily_correlation, 3), "\n",
"- Within-region alliances: ", round(region_homophily$mean_similarity_connected, 3), "\n",
"- Cross-region alliances: ", round(region_homophily$mean_similarity_unconnected, 3), "\n"
)
Regional Clustering Results:
- Homophily score: 0.785
- Within-region alliances: 0.796
- Cross-region alliances: 0.034
4. Who Forms Alliances With Whom? Using
mixing_matrix()
The mixing_matrix()
function reveals detailed
interaction patterns between different types of actors in your network.
This is crucial for understanding not just if certain types
connect, but how much and with whom.
📊 Democracy Mixing Matrix
# Analyze mixing patterns by regime type
regime_mixing <- mixing_matrix(
alliance_net, # Network object
attribute = "regime_type", # Categorical attribute to analyze
normalized = TRUE # Normalize to show proportions
)
# Display the mixing matrix
knitr::kable(round(regime_mixing$mixing_matrices[[1]], 3),
caption = "Regime Type Alliance Matrix (normalized)",
align = "c")
Autocracy | Democracy | Hybrid | Unknown | |
---|---|---|---|---|
Autocracy | 0.128 | 0.100 | 0.061 | 0.006 |
Democracy | 0.100 | 0.241 | 0.079 | 0.038 |
Hybrid | 0.061 | 0.079 | 0.033 | 0.010 |
Unknown | 0.006 | 0.038 | 0.010 | 0.008 |
# Build key insights summary message
regime_mixing_summary <- paste0(
"**Key Insights from mixing_matrix():**\n\n",
"- Assortativity: ", round(regime_mixing$summary_stats$assortativity, 3), "\n",
" (Positive = similar types connect more; Negative = different types connect more)\n",
"- Proportion of within-type alliances: ", round(regime_mixing$summary_stats$diagonal_proportion, 3), "\n",
" (Higher values indicate more homophily)\n"
)
Key Insights from mixing_matrix():
- Assortativity: 0.113 (Positive = similar types connect more; Negative = different types connect more)
- Proportion of within-type alliances: 0.41 (Higher values indicate more homophily)
🌍 Regional Alliance Patterns with Row Normalization
# Analyze mixing patterns by region
region_mixing <- mixing_matrix(
alliance_net,
attribute = "region",
normalized = TRUE,
by_row = TRUE)
# Display regional mixing matrix with header
regional_mixing_header <- "**Regional Alliance Matrix (row-normalized):**\n\n"
Regional Alliance Matrix (row-normalized):
knitr::kable(round(region_mixing$mixing_matrices[[1]], 3),
caption = "Regional Alliance Matrix (row-normalized)",
align = "c")
East Asia & Pacific | Europe & Central Asia | Latin America & Caribbean | Middle East & North Africa | North America | South Asia | Sub-Saharan Africa | |
---|---|---|---|---|---|---|---|
East Asia & Pacific | 0.554 | 0.215 | 0.032 | 0.005 | 0.064 | 0.124 | 0.006 |
Europe & Central Asia | 0.045 | 0.885 | 0.004 | 0.021 | 0.035 | 0.006 | 0.004 |
Latin America & Caribbean | 0.019 | 0.012 | 0.931 | 0.000 | 0.031 | 0.004 | 0.003 |
Middle East & North Africa | 0.005 | 0.106 | 0.000 | 0.415 | 0.005 | 0.000 | 0.469 |
North America | 0.208 | 0.542 | 0.172 | 0.016 | 0.010 | 0.047 | 0.005 |
South Asia | 0.645 | 0.149 | 0.033 | 0.000 | 0.074 | 0.099 | 0.000 |
Sub-Saharan Africa | 0.002 | 0.005 | 0.001 | 0.114 | 0.000 | 0.000 | 0.878 |
Visualizing Regional Alliance Patterns
# Visualize regional mixing patterns
plot_mixing_matrix(region_mixing,
show_values = TRUE,
value_digits = 2,
text_size = 3,
text_color_threshold=.7,
diagonal_emphasis = TRUE,
reorder_categories = FALSE) +
labs(title = "Regional Alliance Patterns",
subtitle = "Within-region vs cross-region alliance formation",
x = "Allied with region",
y = "From region") +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10),
axis.text.y = element_text(size = 10))
The regional mixing matrix reveals strong regional clustering in alliance formation. The emphasized diagonal shows that most regions primarily form alliances within their own geographic area, with some notable exceptions for cross-regional partnerships driven by strategic interests.
🔀 Cross-Dimensional Analysis: Region × Regime Type
A unique feature of mixing_matrix()
is analyzing
interactions across different attributes:
# How do regime types interact across regions?
cross_mixing <- mixing_matrix(
alliance_net,
attribute = "regime_type",
row_attribute = "region",
normalized = TRUE)
# Display cross-dimensional analysis with header
cross_mixing_header <- "**How different regime types form alliances across regions:**\n\n"
How different regime types form alliances across regions:
knitr::kable(round(cross_mixing$mixing_matrices[[1]], 3),
caption = "Cross-dimensional Analysis: Regime Types Across Regions",
align = "c")
Autocracy | Democracy | Hybrid | Unknown | |
---|---|---|---|---|
East Asia & Pacific | 0.020 | 0.035 | 0.020 | 0.003 |
Europe & Central Asia | 0.057 | 0.240 | 0.049 | 0.026 |
Latin America & Caribbean | 0.006 | 0.071 | 0.024 | 0.031 |
Middle East & North Africa | 0.043 | 0.017 | 0.014 | 0.000 |
North America | 0.004 | 0.013 | 0.004 | 0.002 |
South Asia | 0.004 | 0.006 | 0.004 | 0.000 |
Sub-Saharan Africa | 0.160 | 0.076 | 0.069 | 0.000 |
5. Analyzing Relationship-Level Factors with
dyad_correlation()
The dyad_correlation()
function examines how
relationship-level (dyadic) variables correlate with network ties. This
is essential for understanding what factors at the relationship level
predict connections.
🌍 Geographic Distance and Alliance Formation
# Test correlation between geographic distance and alliance formation
geo_correlation <- dyad_correlation(
alliance_net, # Network object
dyad_vars = "geographic_distance", # Dyadic variable to analyze
method = "pearson", # Correlation method
significance_test = TRUE # Test statistical significance
)
# Build geographic distance analysis summary
geo_summary <- paste0(
"**Geographic Distance and Alliance Formation (dyad_correlation results):**\n\n",
"- Correlation coefficient: ", round(geo_correlation$correlation, 3), "\n",
"- P-value: ", round(geo_correlation$p_value, 3), "\n",
"- Number of dyads analyzed: ", geo_correlation$n_dyads[1], "\n\n",
if(geo_correlation$correlation < -0.1 && geo_correlation$p_value < 0.05) {
"✓ Geography matters: Countries form more alliances with nearby nations.\n (Negative correlation = shorter distance, more alliances)\n"
} else if(geo_correlation$correlation > 0.1 && geo_correlation$p_value < 0.05) {
"✗ Surprising: Greater distance associated with more alliances.\n (Positive correlation = greater distance, more alliances)\n"
} else {
"→ Geography shows no clear effect on alliance patterns.\n (No significant correlation detected)\n"
}
)
Geographic Distance and Alliance Formation (dyad_correlation results):
- Correlation coefficient: -0.588
- P-value: 0
- Number of dyads analyzed:
✓ Geography matters: Countries form more alliances with nearby nations. (Negative correlation = shorter distance, more alliances)
🤝 Analyzing Multiple Dyadic Variables
# Test both geographic distance and alliance intensity
multi_dyad_correlation <- dyad_correlation(
alliance_net,
dyad_vars = c("geographic_distance", "alliance_intensity", "defense_alliance"),
method = "pearson",
significance_test = TRUE
)
# Build multiple dyadic variables summary
multi_dyad_summary <- paste0(
"**Multiple Dyadic Variables Analysis:**\n\n",
paste(sapply(1:nrow(multi_dyad_correlation), function(i) {
paste0(
"**", multi_dyad_correlation$dyad_var[i], ":**\n",
" - Correlation: ", round(multi_dyad_correlation$correlation[i], 3), "\n",
" - P-value: ", round(multi_dyad_correlation$p_value[i], 3), "\n"
)
}), collapse = "\n")
)
Multiple Dyadic Variables Analysis:
geographic_distance: - Correlation: -0.588 - P-value: 0
alliance_intensity: - Correlation: 1 - P-value: 0
defense_alliance: - Correlation: 0.892 - P-value: 0
6. Comprehensive Analysis with attribute_report()
The attribute_report()
function combines the previous
analyses into a comprehensive report.
🚀 Running the Complete Analysis
# Run comprehensive analysis with all features
comprehensive_analysis <- attribute_report(
alliance_net,
# Node-level variables to analyze
node_vars = c("region", "regime_type", "democracy", "log_gdp", "mil_capability"),
# Dyad-level variables to analyze
dyad_vars = c("geographic_distance", "alliance_intensity", "defense_alliance"),
# Include all analysis types
include_centrality = TRUE, # Correlate attributes with network position
include_homophily = TRUE, # Test if similar nodes connect
include_mixing = TRUE, # Examine interaction patterns
include_dyadic_correlations = TRUE, # Analyze dyadic predictors
# Specify which centrality measures to compute
centrality_measures = c("degree", "betweenness", "closeness"),
# Perform significance tests
significance_test = TRUE
)
attribute_report
returns a list with multiple
components
-
homophily_analysis
: Tests for each node attribute -
mixing_matrices
: Interaction patterns for categorical variables -
centrality_correlations
: How attributes relate to network position -
dyadic_correlations
: How dyad attributes predict ties
📋 Extracting Key Findings from the Summary
# Build homophily analysis header
homophily_header <- paste0(
"**=== HOMOPHILY ANALYSIS ===**\n\n",
"Do similar countries form more alliances?\n\n"
)
=== HOMOPHILY ANALYSIS ===
Do similar countries form more alliances?
Attribute | Method | Homophily Correlation | P-value | Significance | Interpretation | |
---|---|---|---|---|---|---|
region | region | categorical | 0.785 | 0 | *** | Strong homophily |
regime_type | regime_type | categorical | 0.133 | 0 | *** | Moderate homophily |
democracy | democracy | correlation | 0.140 | 0 | *** | Moderate homophily |
log_gdp | log_gdp | correlation | 0.110 | 0 | *** | Moderate homophily |
mil_capability | mil_capability | correlation | -0.045 | 0 | *** | Heterophily |
# Build power and influence header
power_header <- paste0(
"**=== POWER AND INFLUENCE ===**\n\n",
"What makes countries central in the alliance network?\n\n"
)
=== POWER AND INFLUENCE ===
What makes countries central in the alliance network?
Node Variable | Centrality Measure | Correlation | P-value | Interpretation | |
---|---|---|---|---|---|
cor7 | mil_capability | betweenness | 0.681 | 0.000 | Strongly associated with centrality |
cor8 | mil_capability | closeness | 0.351 | 0.000 | Strongly associated with centrality |
cor5 | log_gdp | closeness | 0.354 | 0.000 | Strongly associated with centrality |
cor4 | log_gdp | betweenness | 0.311 | 0.000 | Strongly associated with centrality |
cor2 | democracy | closeness | 0.319 | 0.000 | Strongly associated with centrality |
cor3 | log_gdp | degree | 0.242 | 0.001 | Moderately associated with centrality |
cor | democracy | degree | 0.234 | 0.002 | Moderately associated with centrality |
cor6 | mil_capability | degree | 0.165 | 0.021 | Moderately associated with centrality |
cor1 | democracy | betweenness | 0.064 | 0.399 | Not significantly related to centrality |
1 | region | degree | NA | NA | Not significantly related to centrality |
# Build relationship factors header
relationship_header <- paste0(
"**=== RELATIONSHIP FACTORS ===**\n\n",
"What dyadic factors predict alliance formation?\n\n"
)
=== RELATIONSHIP FACTORS ===
What dyadic factors predict alliance formation?
Dyadic Variable | Correlation | P-value |
---|---|---|
geographic_distance | -0.588 | 0 |
alliance_intensity | 1.000 | 0 |
defense_alliance | 0.892 | 0 |
7. Testing Specific IR Hypotheses
Hypothesis 1: Democratic Peace
# Create a binary democracy indicator
nodal_data_binary <- nodal_data |>
mutate(is_democracy = ifelse(regime_type == "Democracy", 1, 0))
alliance_net_binary <- add_node_vars(
alliance_net,
nodal_data_binary[, c("actor", "is_democracy")],
actor = "actor")
# Test democratic peace using binary measure
dem_peace_test <- homophily(
alliance_net_binary,
attribute = "is_democracy",
method = "categorical",
significance_test = TRUE)
# Build democratic peace hypothesis summary
dem_peace_summary <- paste0(
"**Democratic Peace Hypothesis Test:**\n\n",
"- Effect size: ", round(dem_peace_test$homophily_correlation, 3), "\n",
"- P-value: ", round(dem_peace_test$p_value, 3), "\n",
"- Conclusion: ", ifelse(
dem_peace_test$p_value < 0.05,
"Democracies significantly prefer forming alliances with other democracies",
"No significant democratic preference"), "\n"
)
Democratic Peace Hypothesis Test:
- Effect size: 0.048
- P-value: 0
- Conclusion: Democracies significantly prefer forming alliances with other democracies
Hypothesis 2: Power Politics
Do powerful countries (high military capability) primarily form alliances with other powerful countries?
# Test military capability homophily
power_homophily <- homophily(
alliance_net,
attribute = "mil_capability",
method = "correlation",
significance_test = TRUE)
# Build power politics hypothesis summary
power_politics_summary <- paste0(
"**Power Politics Hypothesis:**\n\n",
"- Correlation: ", round(power_homophily$homophily_correlation, 3), "\n",
"- P-value: ", round(power_homophily$p_value, 3), "\n",
"- Interpretation: ", ifelse(power_homophily$p_value < 0.05 & power_homophily$homophily_correlation > 0,
"Powerful countries prefer forming alliances with other powerful countries",
ifelse(power_homophily$p_value < 0.05 & power_homophily$homophily_correlation < 0,
"Powerful countries tend to form alliances with less powerful countries (heterophily)",
"No evidence of power-based alliance preferences")), "\n"
)
Power Politics Hypothesis:
- Correlation: -0.045
- P-value: 0
- Interpretation: Powerful countries tend to form alliances with less powerful countries (heterophily)
8. Visualizing Network Patterns
And as seen in other vignettes we can use the
plot.netify()
function to visualize the network with node
attributes and edge weights:
Network Visualization by Attributes
# First add network statistics to the netify object
alliance_net <- add_node_vars(
alliance_net,
summary_actor(alliance_net),
actor = "actor"
)
#
plot(alliance_net,
# Node aesthetics
node_color_by = "region",
node_color_label = "",
node_shape_by = "regime_type",
node_shape_label = "",
node_size_by = "degree",
node_size_label = "Degree",
node_fill = "white",
# Edge aesthetics - make edges much more subtle
edge_color = "grey50", # Darker gray for visibility
edge_linewidth = 0.5, # Slightly thicker for visible edges
edge_alpha_label='Alliance Strength (scaled)',
layout = "nicely",
seed = 6886) +
ggtitle("ATOP Network") +
theme(legend.position='right')
Visualizing Homophily Results
# Use plot_homophily for a comparison plot
plot_homophily(comprehensive_analysis$homophily_analysis,
type = "comparison") +
labs(title = "Alliance Formation Patterns: Which Attributes Matter?",
subtitle = "Homophily analysis reveals how similarity drives international cooperation")
Visualizing Centrality Patterns
# Prepare data for centrality visualization
centrality_viz <- comprehensive_analysis$centrality_correlations |>
filter(p_value < 0.1) |> # Show marginally significant results
mutate(
significant = p_value < 0.05,
node_var = factor(node_var),
centrality_measure = factor(
centrality_measure,
levels = c("degree", "betweenness", "closeness"))
)
if(nrow(centrality_viz) > 0) {
ggplot(centrality_viz, aes(x = correlation, y = node_var, color = significant)) +
geom_segment(
aes(
x = 0, xend = correlation, y = node_var, yend = node_var),
size = 1) +
geom_point(size = 3) +
facet_wrap(~centrality_measure, ncol = 1) +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
scale_color_manual(values = c("FALSE" = "gray60", "TRUE" = "#2E86AB"),
labels = c("FALSE" = "Not significant", "TRUE" = "(p < 0.05)")) +
labs(title = "What Makes Countries Central in the Alliance Network?",
subtitle = "Correlation between node attributes and centrality measures",
x = "Correlation with Centrality",
y = "Node Attribute",
color = "") +
theme_minimal() +
theme(panel.grid.major.y = element_blank())
} else {
no_centrality_msg <- "**No significant centrality correlations to visualize.**\n"
cat(no_centrality_msg)
}
Mixing Matrix Heatmap
We can use the plot_mixing_matrix()
function to create a
cleaner visualization of the mixing patterns:
# Create a heatmap of the regime mixing matrix using plot_mixing_matrix
plot_mixing_matrix(
regime_mixing,
show_values = TRUE,
diagonal_emphasis = TRUE,
text_color_threshold=.9
) +
labs(title = "Regime Type Alliance Patterns",
subtitle = "How different regime types interact in the network",
x = "Allied with...",
y = "Regime type")
The heatmap clearly shows the alliance patterns between different regime types. The diagonal cells (emphasized with black borders) represent within-type alliances, while off-diagonal cells show cross-type alliances. Darker blue indicates higher proportions of alliances. The assortativity coefficient of 0.113 (not 0.126) and diagonal proportion of 0.41 (not 0.405) confirm the moderate tendency for regime type homophily in alliance formation.
9. Working with Longitudinal Networks
All the attribute analysis functions in netify work seamlessly with longitudinal networks. Let’s demonstrate this by creating a longitudinal alliance network and running the same analyses across multiple time periods.
Creating a Longitudinal Network
# Create longitudinal alliance network (2010-2014)
alliance_net_longit <- netify(
cow_dyads, # Uses full dataset with all years
actor1 = 'ccode1',
actor2 = 'ccode2',
time = 'year', # Specify time variable
symmetric = TRUE,
weight = 'cooperation'
)
# Print to see longitudinal structure
print(alliance_net_longit)
Adding Attributes to Longitudinal Networks
# Prepare nodal data for all time periods
nodal_data_longit <- cow_dyads |>
select(year, ccode1, region1, v2x_polyarchy1, log_gdp1, cinc1) |>
distinct() |>
rename(
time = year,
actor = ccode1,
region = region1,
democracy = v2x_polyarchy1,
log_gdp = log_gdp1,
mil_capability = cinc1
) |>
mutate(
regime_type = case_when(
democracy >= 0.6 ~ "Democracy",
democracy >= 0.4 ~ "Hybrid",
democracy < 0.4 ~ "Autocracy",
TRUE ~ "Unknown"
)
)
# Add nodal variables (automatically matched by time)
alliance_net_longit <- add_node_vars(
alliance_net_longit,
nodal_data_longit,
actor = "actor",
time = "time"
)
# Add dyadic variables
dyad_data_longit <- cow_dyads |>
select(year, ccode1, ccode2, log_capdist, alliance_intensity, defense_alliance) |>
rename(
time = year,
actor1 = ccode1,
actor2 = ccode2,
geographic_distance = log_capdist
)
alliance_net_longit <- add_dyad_vars(
alliance_net_longit,
dyad_data = dyad_data_longit,
actor1 = "actor1",
actor2 = "actor2",
time = "time",
dyad_vars = c("geographic_distance", "alliance_intensity", "defense_alliance"),
dyad_vars_symmetric = c(TRUE, TRUE, TRUE)
)
Homophily Analysis Across Time
# Test democracy homophily across all time periods
democracy_homophily_longit <- homophily(
alliance_net_longit,
attribute = "democracy",
method = "correlation",
significance_test = TRUE
)
# Results show homophily for each time period
print(democracy_homophily_longit)
## net layer attribute method threshold_value homophily_correlation
## 1 2010 cooperation democracy correlation 0 0.1489560
## 2 2011 cooperation democracy correlation 0 0.1468133
## 3 2012 cooperation democracy correlation 0 0.1402454
## 4 2013 cooperation democracy correlation 0 0.1295314
## 5 2014 cooperation democracy correlation 0 0.1214966
## mean_similarity_connected mean_similarity_unconnected similarity_difference
## 1 -0.2431550 -0.3179472 0.07479217
## 2 -0.2408978 -0.3142027 0.07330491
## 3 -0.2431161 -0.3122502 0.06913408
## 4 -0.2457439 -0.3086910 0.06294710
## 5 -0.2483152 -0.3071912 0.05887602
## p_value ci_lower ci_upper n_connected_pairs n_unconnected_pairs n_missing
## 1 0 0.1335942 0.1632980 3484 11394 22
## 2 0 0.1315743 0.1627761 3484 11567 21
## 3 0 0.1253009 0.1556244 3558 11493 21
## 4 0 0.1140035 0.1439115 3632 11419 21
## 5 0 0.1061956 0.1367893 3633 11418 21
## n_pairs
## 1 18915
## 2 18915
## 3 18915
## 4 18915
## 5 18915
# Create a summary of trends
homophily_trends <- democracy_homophily_longit |>
group_by(net) |>
summarise(
avg_homophily = mean(homophily_correlation, na.rm = TRUE),
significant = any(p_value < 0.05, na.rm = TRUE)
)
knitr::kable(homophily_trends,
caption = "Democracy Homophily Trends Over Time",
digits = 3)
net | avg_homophily | significant |
---|---|---|
2010 | 0.149 | TRUE |
2011 | 0.147 | TRUE |
2012 | 0.140 | TRUE |
2013 | 0.130 | TRUE |
2014 | 0.121 | TRUE |
Visualizing Longitudinal Homophily
# Use plot_homophily with type = "temporal" for longitudinal data
plot_homophily(democracy_homophily_longit, type = "temporal") +
labs(title = "Democracy Homophily in Alliance Networks Over Time",
subtitle = "Tendency for democracies to ally with other democracies")
If you want to see the distribution for a specific time period, you can extract that period first:
# Extract 2012 data for distribution plot using subset
alliance_2012 <- subset(alliance_net_longit, time = '2012')
democracy_homo_2012 <- homophily(
alliance_2012,
attribute = "democracy",
method = "correlation"
)
# Now plot the distribution for just 2012
plot_homophily(democracy_homo_2012, alliance_2012,
type = "distribution",
attribute = "democracy",
method = "correlation") +
labs(subtitle = "Distribution of democracy similarity scores in 2012 alliance network")
Mixing Matrices Over Time
# Analyze regime type mixing patterns across time
regime_mixing_longit <- mixing_matrix(
alliance_net_longit,
attribute = "regime_type",
normalized = TRUE
)
# The function returns results for each time period
# Let's look at the summary statistics
mixing_summary <- regime_mixing_longit$summary_stats |>
select(net, assortativity, diagonal_proportion) |>
mutate(across(where(is.numeric), round, 3))
knitr::kable(mixing_summary,
caption = "Regime Type Mixing Patterns Over Time",
col.names = c("Year", "Assortativity", "Within-Type %"))
Year | Assortativity | Within-Type % |
---|---|---|
2010 | 0.135 | 0.418 |
2011 | 0.119 | 0.409 |
2012 | 0.113 | 0.410 |
2013 | 0.099 | 0.399 |
2014 | 0.091 | 0.393 |
Dyadic Correlations Across Time
# Test geographic distance effects over time
geo_correlation_longit <- dyad_correlation(
alliance_net_longit,
dyad_vars = "geographic_distance",
method = "pearson",
significance_test = TRUE
)
# Display results
geo_summary_longit <- geo_correlation_longit |>
select(net, correlation, p_value, n_pairs) |>
mutate(
significant = ifelse(p_value < 0.05, "*", ""),
correlation = round(correlation, 3),
p_value = round(p_value, 3)
)
knitr::kable(geo_summary_longit,
caption = "Geographic Distance and Alliance Formation Over Time",
col.names = c("Year", "Correlation", "P-value", "N Dyads", "Sig."))
Year | Correlation | P-value | N Dyads | Sig. |
---|---|---|---|---|
2010 | -0.365 | 0 | 37830 | * |
2011 | -0.594 | 0 | 37830 | * |
2012 | -0.588 | 0 | 37830 | * |
2013 | -0.592 | 0 | 37830 | * |
2014 | -0.592 | 0 | 37830 | * |
Comprehensive Longitudinal Analysis
# Run comprehensive analysis on longitudinal network
comprehensive_longit <- attribute_report(
alliance_net_longit,
node_vars = c("region", "regime_type", "democracy", "log_gdp"),
dyad_vars = c("geographic_distance", "alliance_intensity"),
include_centrality = TRUE,
include_homophily = TRUE,
include_mixing = TRUE,
include_dyadic_correlations = TRUE,
centrality_measures = c("degree", "betweenness"),
significance_test = TRUE
)
# Extract key longitudinal patterns if available
if (!is.null(comprehensive_longit$homophily_analysis)) {
longit_patterns <- comprehensive_longit$homophily_analysis |>
filter(attribute == "democracy") |>
select(net, homophily_correlation, p_value) |>
mutate(
trend = case_when(
net == min(net) ~ "Start",
net == max(net) ~ "End",
TRUE ~ "Middle"
)
)
print(longit_patterns)
} else {
cat("Note: Homophily analysis for longitudinal networks is currently limited.\n")
cat("For comprehensive longitudinal analysis, analyze each time period separately.\n")
}
## Note: Homophily analysis for longitudinal networks is currently limited.
## For comprehensive longitudinal analysis, analyze each time period separately.
tl;dr
-
From
homophily()
:- Whether democracies truly form more alliances with each other
- If economic similarity drives alliance patterns
- The strength of regional clustering in alliance formation
-
From
mixing_matrix()
:- Detailed patterns of who forms alliances with whom
- Whether alliances cross regime type boundaries
- How different regions form alliances globally
-
From
dyad_correlation()
:- The role of geographic distance in shaping alliance formation
- How alliance types (defense, offense, etc.) cluster
- Which relationship factors matter most for alliances
-
From
attribute_report()
:- What attributes make countries central/influential
- Complete homophily patterns across all variables
- Comprehensive view of all network-attribute relationships