required_packages <- c("igraph", "ergm", "network", "intergraph", "readxl", "sna", "knitr")
for(pkg in required_packages) {
if(!require(pkg, character.only = TRUE, quietly = TRUE)) {
install.packages(pkg)
library(pkg, character.only = TRUE)
}
}
# Set seed for reproducibility
set.seed(12345)Supplementary Material
Replication data and code for stakeholder mapping and Net-Map health governance analysis
0.1 Overview
This supplementary material, titled Replication data and code for stakeholder mapping and Net-Map health governance analysis, provides the complete replication code for the paper From stakeholder mapping to statistical modeling: An end-to-end Net-Map methodology for health governance analysis. All results reported in the main paper are fully reproducible using this code. The data files are part of this supplementary material and included in the same data repository.
0.2 Data Availability
The original Net-Map data files — _Netmap_attributes_final.xls and _Netmap_edgelist_final.xls — are needed to execute this replication code. The data files come together with this code.
Note: This supplementary material provides complete computational reproducibility for all statistical results reported in the main paper. The replication demonstrates the correspondence between published findings and analytical code output.
1 Setup and data preparation
This section loads the required R packages, imports the Net-Map data files (attribute and edgelist spreadsheets), and constructs the network objects and covariate matrices used throughout the analysis. The funding network, authority network, and influence network are created as directed graphs from the digitized Net-Map edgelists. A same-level binary matrix is constructed to identify dyads where both organizations operate at the same governance level (EU, national, or local). All objects created here are used in the descriptive analysis (Section 2), ERGM estimation (Section 3), and additional robustness analyses (Section 6).
1.1 Package Loading
1.2 Data import and network creation
# Load data
attributes <- readxl::read_excel("_Netmap_attributes_final.xls", sheet = 1)
edgelist <- readxl::read_excel("_Netmap_edgelist_final.xls", sheet = 1)
# Create vertex data
vertices <- data.frame(
name = attributes$id,
level = attributes$level
)
# Create edge lists using paper's binary coding approach
authority_edges <- edgelist[edgelist$authority == 1 & !is.na(edgelist$authority), c("id", "id2")]
money_edges <- edgelist[edgelist$money == 1 & !is.na(edgelist$money), c("id", "id2")]
influence_edges <- edgelist[edgelist$influence == 1 & !is.na(edgelist$influence), c("id", "id2")]
# Create igraph networks
money_net <- igraph::graph_from_data_frame(money_edges, directed = TRUE, vertices = vertices)
authority_net <- igraph::graph_from_data_frame(authority_edges, directed = TRUE, vertices = vertices)
influence_net <- igraph::graph_from_data_frame(influence_edges, directed = TRUE, vertices = vertices)
# Convert to network object for ERGM
money_network <- intergraph::asNetwork(money_net)
# Create covariate matrices
authority_matrix <- as.matrix(igraph::as_adjacency_matrix(authority_net))
influence_matrix <- as.matrix(igraph::as_adjacency_matrix(influence_net))1.3 Same-level matrix construction
n_nodes <- igraph::vcount(money_net)
same_level <- matrix(0, nrow = n_nodes, ncol = n_nodes)
for(i in 1:n_nodes) {
for(j in 1:n_nodes) {
if(i != j && igraph::V(money_net)$level[i] == igraph::V(money_net)$level[j]) {
same_level[i, j] <- 1
}
}
}2 Descriptive network analysis
This section replicates the descriptive statistics reported in Table 2 of the main paper. We report the network composition by governance level, basic structural statistics (density, reciprocity), and the distribution of funding ties within and across governance levels. We also examine directional funding flows between EU, national, and local actors, and quantify the overlap between governance ties (authority and influence) and funding ties. These descriptive patterns motivate the ERGM specifications in Section 3.
2.1 Network composition
cat("Total organizations:", igraph::vcount(money_net), "\n\n")Total organizations: 128
level_counts <- table(vertices$level)
total_orgs <- sum(level_counts)
cat("European level:", level_counts["european"],
"(", round(level_counts["european"]/total_orgs*100, 1), "%)\n")European level: 25 ( 19.5 %)
cat("National level:", level_counts["national"],
"(", round(level_counts["national"]/total_orgs*100, 1), "%)\n")National level: 63 ( 49.2 %)
cat("Local level:", level_counts["local"],
"(", round(level_counts["local"]/total_orgs*100, 1), "%)\n")Local level: 40 ( 31.2 %)
2.2 Network structure statistics
cat("Total funding ties:", igraph::ecount(money_net), "\n")Total funding ties: 836
cat("Possible dyads:", n_nodes * (n_nodes - 1), "\n")Possible dyads: 16256
cat("Network density:", round(igraph::edge_density(money_net), 4),
"(", round(igraph::edge_density(money_net)*100, 2), "%)\n")Network density: 0.0514 ( 5.14 %)
cat("Reciprocity:", round(igraph::reciprocity(money_net), 2),
"(", round(igraph::reciprocity(money_net)*100, 1), "%)\n\n")Reciprocity: 0.01 ( 1 %)
cat("Authority ties:", igraph::ecount(authority_net), "\n")Authority ties: 150
cat("Authority density:", round(igraph::edge_density(authority_net), 4),
"(", round(igraph::edge_density(authority_net)*100, 2), "%)\n")Authority density: 0.0092 ( 0.92 %)
cat("Influence ties:", igraph::ecount(influence_net), "\n")Influence ties: 1012
cat("Influence density:", round(igraph::edge_density(influence_net), 4),
"(", round(igraph::edge_density(influence_net)*100, 2), "%)\n")Influence density: 0.0623 ( 6.23 %)
2.3 Cross-level funding analysis
money_adj <- as.matrix(igraph::as_adjacency_matrix(money_net))
within_level_ties <- 0
cross_level_ties <- 0
for(i in 1:n_nodes) {
for(j in 1:n_nodes) {
if(i != j && money_adj[i, j] > 0) {
if(igraph::V(money_net)$level[i] == igraph::V(money_net)$level[j]) {
within_level_ties <- within_level_ties + 1
} else {
cross_level_ties <- cross_level_ties + 1
}
}
}
}
total_ties <- within_level_ties + cross_level_ties
cat("Within-level ties:", within_level_ties,
"(", round(within_level_ties/total_ties*100, 1), "%)\n")Within-level ties: 239 ( 28.6 %)
cat("Cross-level ties:", cross_level_ties,
"(", round(cross_level_ties/total_ties*100, 1), "%)\n")Cross-level ties: 597 ( 71.4 %)
2.4 Directional funding flows
eu_nodes <- which(igraph::V(money_net)$level == "european")
nat_nodes <- which(igraph::V(money_net)$level == "national")
local_nodes <- which(igraph::V(money_net)$level == "local")
# European -> National
eu_to_nat <- 0
for(i in eu_nodes) {
for(j in nat_nodes) {
if(money_adj[i, j] > 0) eu_to_nat <- eu_to_nat + 1
}
}
eu_to_nat_density <- eu_to_nat / (length(eu_nodes) * length(nat_nodes))
# European -> Local
eu_to_local <- 0
for(i in eu_nodes) {
for(j in local_nodes) {
if(money_adj[i, j] > 0) eu_to_local <- eu_to_local + 1
}
}
eu_to_local_density <- eu_to_local / (length(eu_nodes) * length(local_nodes))
# National -> Local
nat_to_local <- 0
for(i in nat_nodes) {
for(j in local_nodes) {
if(money_adj[i, j] > 0) nat_to_local <- nat_to_local + 1
}
}
nat_to_local_density <- nat_to_local / (length(nat_nodes) * length(local_nodes))
cat("European → National:", eu_to_nat,
"(", round(eu_to_nat_density*100, 1), "% density)\n")European → National: 280 ( 17.8 % density)
cat("European → Local:", eu_to_local,
"(", round(eu_to_local_density*100, 1), "% density)\n")European → Local: 161 ( 16.1 % density)
cat("National → Local:", nat_to_local,
"(", round(nat_to_local_density*100, 1), "% density)\n")National → Local: 155 ( 6.2 % density)
2.5 Relationship overlap analysis
auth_fund_overlap <- sum((authority_matrix > 0) & (money_adj > 0))
inf_fund_overlap <- sum((influence_matrix > 0) & (money_adj > 0))
total_auth_ties <- sum(authority_matrix > 0)
total_inf_ties <- sum(influence_matrix > 0)
cat("Authority-funding overlap:", auth_fund_overlap, "/", total_auth_ties,
"(", round(auth_fund_overlap/total_auth_ties*100, 1), "%)\n")Authority-funding overlap: 43 / 150 ( 28.7 %)
cat("Influence-funding overlap:", inf_fund_overlap, "/", total_inf_ties,
"(", round(inf_fund_overlap/total_inf_ties*100, 1), "%)\n")Influence-funding overlap: 647 / 1012 ( 63.9 %)
3 ERGM analysis
This section replicates the core ERGM analysis from Table 3 of the main paper. We estimate four nested dyadic-independence models for the directed funding network: a baseline model (edges only), a governance-level model (adding same-level effects), an authority model, and the full model including influence relationships. All models are estimated via maximum pseudolikelihood (MPLE). Because the model terms are exclusively dyadic-independent (edges and edgecov covariates), MPLE yields maximum likelihood estimates, and the MCMC parameters in the control object do not affect point estimates or standard errors (see Section 3.1 for details).
3.1 Model estimation setup
paper_control <- control.ergm(
MCMC.samplesize = 2000,
MCMC.burnin = 2000,
seed = 12345
)We specified a control object (paper_control) to govern the behavior of the ergm() estimation routine. The three parameters set within control.ergm() are the MCMC sample size (2,000 draws), the burn-in period (2,000 iterations discarded before sampling begins), and a fixed random seed (12345) to ensure full reproducibility of results. It is important to note that our four nested models rely exclusively on dyadic-independent terms (edges and edgecov() covariates), which means that the ergm package estimates them via maximum pseudolikelihood (MPLE). MPLE recasts the network likelihood as a standard logistic regression over all dyads and does not require Markov chain Monte Carlo (MCMC) sampling during parameter estimation. Consequently, the MCMC-related settings in the control object have no effect on the point estimates or standard errors of Models 1–4. These settings do, however, become operative in downstream procedures that require network simulation from the fitted model, specifically goodness-of-fit assessment (gof()), the triad census comparison, and the parameter stability analysis, all of which draw networks from the MCMC chain parameterized by the estimated coefficients. For these simulation-based diagnostics, a sample size of 2,000 with a burn-in of 2,000 provides a reasonable balance between computational efficiency and sampling accuracy for our 128-node, sparse (density ≈ 5%) directed network.
3.2 Progressive model fitting
3.2.1 Model 1: Baseline
cat("Model 1: Baseline\n")
model1 <- ergm(money_network ~ edges, control = paper_control)3.2.2 Model 2: Governance level effects
cat("Model 2: Governance level effects\n")
model2 <- ergm(money_network ~ edges + edgecov(same_level), control = paper_control)3.2.4 Model 4: Full model with influence
cat("Model 4: Full model\n")
model4 <- ergm(money_network ~ edges + edgecov(same_level) + edgecov(authority_matrix) +
edgecov(influence_matrix), control = paper_control)3.3 Results (Table 3 replication)
models <- list(model1, model2, model3, model4)
model_names <- c("Baseline", "Cross-level", "Authority", "Full Model")
results_table <- data.frame(
Parameter = c("Edges (baseline)", "(SE)", "Same governance level", "(SE)", "[OR]",
"Authority relationship", "(SE)", "[OR]", "Influence relationship", "(SE)", "[OR]",
"Log-likelihood", "AIC", "BIC", "Deviance", "n (dyads)", "n (edges)", "n (nodes)"),
stringsAsFactors = FALSE
)
for(i in 1:length(models)) {
model <- models[[i]]
coefs <- coef(model)
ses <- tryCatch(sqrt(diag(vcov(model))), error = function(e) rep(NA, length(coefs)))
col_values <- rep("—", 18)
if("edges" %in% names(coefs)) {
col_values[1] <- sprintf("%.3f***", coefs["edges"])
col_values[2] <- sprintf("(%.3f)", ses[1])
}
if("edgecov.same_level" %in% names(coefs)) {
idx <- which(names(coefs) == "edgecov.same_level")
col_values[3] <- sprintf("%.3f***", coefs["edgecov.same_level"])
col_values[4] <- sprintf("(%.3f)", ses[idx])
col_values[5] <- sprintf("%.2f", exp(coefs["edgecov.same_level"]))
}
if("edgecov.authority_matrix" %in% names(coefs)) {
idx <- which(names(coefs) == "edgecov.authority_matrix")
col_values[6] <- sprintf("%.3f***", coefs["edgecov.authority_matrix"])
col_values[7] <- sprintf("(%.3f)", ses[idx])
col_values[8] <- sprintf("%.2f", exp(coefs["edgecov.authority_matrix"]))
}
if("edgecov.influence_matrix" %in% names(coefs)) {
idx <- which(names(coefs) == "edgecov.influence_matrix")
col_values[9] <- sprintf("%.3f***", coefs["edgecov.influence_matrix"])
col_values[10] <- sprintf("(%.3f)", ses[idx])
col_values[11] <- sprintf("%.2f", exp(coefs["edgecov.influence_matrix"]))
}
col_values[12] <- sprintf("%.1f", as.numeric(logLik(model)))
col_values[13] <- sprintf("%.1f", AIC(model))
col_values[14] <- sprintf("%.1f", BIC(model))
col_values[15] <- sprintf("%.1f", -2 * as.numeric(logLik(model)))
col_values[16] <- "16,256"
col_values[17] <- "836"
col_values[18] <- "128"
results_table[[model_names[i]]] <- col_values
}
knitr::kable(results_table, caption = "ERGM Results: Predictors of Funding Ties")| Parameter | Baseline | Cross-level | Authority | Full Model |
|---|---|---|---|---|
| Edges (baseline) | -2.915*** | -2.777*** | -2.831*** | -4.140*** |
| (SE) | (0.036) | (0.042) | (0.043) | (0.077) |
| Same governance level | — | -0.417*** | -0.387*** | -1.040*** |
| (SE) | — | (0.078) | (0.079) | (0.112) |
| [OR] | — | 0.66 | 0.68 | 0.35 |
| Authority relationship | — | — | 1.989*** | 1.751*** |
| (SE) | — | — | (0.185) | (0.305) |
| [OR] | — | — | 7.31 | 5.76 |
| Influence relationship | — | — | — | 5.127*** |
| (SE) | — | — | — | (0.106) |
| [OR] | — | — | — | 168.54 |
| Log-likelihood | -3295.0 | -3280.1 | -3238.2 | -1615.6 |
| AIC | 6592.1 | 6564.3 | 6482.4 | 3239.3 |
| BIC | 6599.8 | 6579.7 | 6505.5 | 3270.1 |
| Deviance | 6590.1 | 6560.3 | 6476.4 | 3231.3 |
| n (dyads) | 16,256 | 16,256 | 16,256 | 16,256 |
| n (edges) | 836 | 836 | 836 | 836 |
| n (nodes) | 128 | 128 | 128 | 128 |
4 Model diagnostics
This section assesses the stability and specification of Model 4 (the full dyadic-independence model). Parameter stability is tested across multiple random seeds to confirm that MPLE estimates are deterministic (as expected for dyadic-independent terms). Model specification tests compare AIC values when authority or influence terms are individually removed, quantifying each predictor’s contribution to model fit.
4.1 Parameter stability testing
stability_seeds <- c(12345, 54321, 99999)
stability_results <- list()
cat("Parameter stability analysis\n\n")
for(i in 1:length(stability_seeds)) {
cat("Testing seed", stability_seeds[i], "... ")
tryCatch({
test_control <- control.ergm(MCMC.samplesize = 2000, MCMC.burnin = 2000,
seed = stability_seeds[i])
temp_model <- ergm(money_network ~ edges + edgecov(same_level) +
edgecov(authority_matrix) + edgecov(influence_matrix),
control = test_control)
stability_results[[i]] <- coef(temp_model)
cat("Success\n")
}, error = function(e) {
cat("Failed\n")
stability_results[[i]] <- NA
})
}
if(length(stability_results[!is.na(stability_results)]) >= 2) {
valid_results <- stability_results[!is.na(stability_results)]
param_names <- names(coef(model4))
cat("\nParameter stability results:\n\n")
for(param in param_names) {
values <- sapply(valid_results, function(x) x[param])
cv <- sd(values) / abs(mean(values))
status <- ifelse(cv < 0.1, "Stable", "Check")
cat(" ", param, ": CV =", round(cv, 3), "(", status, ")\n")
}
}Parameter stability results:
edges : CV = 0 ( Stable )
edgecov.same_level : CV = 0 ( Stable )
edgecov.authority_matrix : CV = 0 ( Stable )
edgecov.influence_matrix : CV = 0 ( Stable )
4.2 Model specification tests
model_no_auth <- ergm(money_network ~ edges + edgecov(same_level) + edgecov(influence_matrix),
control = paper_control)
auth_delta_aic <- AIC(model_no_auth) - AIC(model4)
model_no_inf <- ergm(money_network ~ edges + edgecov(same_level) + edgecov(authority_matrix),
control = paper_control)
inf_delta_aic <- AIC(model_no_inf) - AIC(model4)Removing authority: ΔAIC = 27.7
Removing influence: ΔAIC = 3243.2
Influence relationships are critical for model performance
Authority relationships contribute meaningfully to model fit
4.3 Goodness-of-fit assessment
A comprehensive goodness-of-fit assessment using simulation-based diagnostics (in-degree, out-degree, edgewise shared partners, geodesic distance, triad census, and mixing matrices) is presented in Section 6.1 of the Additional Analyses. In brief, the dyadic-independence Model 4 adequately reproduces the model statistics it was designed to capture (p-values for edges, same-level, authority, and influence all non-significant), but — as expected for a model without endogenous structural terms — it does not fully reproduce higher-order features such as the observed degree distribution and clustering patterns. These discrepancies motivate the structural extensions in Section 6.2.
5 Key findings summary
This section summarizes the core results from the ERGM analysis. Influence relationships emerge as the strongest correlate of funding ties (OR = 168.54), followed by authority relationships (OR = 5.76). Same-level funding is significantly less likely than cross-level funding (OR = 0.35). All effects are highly significant (p < 0.001). These results are further examined and stress-tested in the additional analyses that follow.
coefs <- coef(model4)
ses <- sqrt(diag(vcov(model4)))
auth_or <- exp(coefs["edgecov.authority_matrix"])
inf_or <- exp(coefs["edgecov.influence_matrix"])
same_or <- exp(coefs["edgecov.same_level"])
inf_vs_auth_ratio <- inf_or / auth_or
cat("Model performance:\n")Model performance:
cat("Full ERGM provided the best fit (AIC =", round(AIC(model4), 1), ")\n")Full ERGM provided the best fit (AIC = 3239.3 )
cat("Improvement over baseline: ΔAIC =", round(AIC(model1) - AIC(model4), 1), "\n\n")Improvement over baseline: ΔAIC = 3352.8
cat("Substantive results:\n")Substantive results:
cat("Influence relationships: OR =", round(inf_or, 2), "(strongest predictor)\n")Influence relationships: OR = 168.54 (strongest predictor)
cat("Authority relationships: OR =", round(auth_or, 2), "(significant but smaller)\n")Authority relationships: OR = 5.76 (significant but smaller)
cat("Same-level funding preference: OR =", round(same_or, 2), "(cross-level preferred)\n\n")Same-level funding preference: OR = 0.35 (cross-level preferred)
cat("Effect Size Comparison:\n")Effect Size Comparison:
cat("Influence is", round(inf_vs_auth_ratio, 1), "times more predictive than authority\n\n")Influence is 29.2 times more predictive than authority
cat("Statistical significance:\n")Statistical significance:
z_auth <- coefs["edgecov.authority_matrix"] / ses[3]
z_inf <- coefs["edgecov.influence_matrix"] / ses[4]
z_same <- coefs["edgecov.same_level"] / ses[2]
cat("Authority: z =", round(z_auth, 2), ", p < 0.001\n")Authority: z = 5.74 , p < 0.001
cat("Influence: z =", round(z_inf, 2), ", p < 0.001\n")Influence: z = 48.5 , p < 0.001
cat("Same-level: z =", round(z_same, 2), ", p < 0.001\n")Same-level: z = -9.33 , p < 0.001
6 Additional analyses
This section presents a comprehensive battery of robustness checks, diagnostic extensions, and sensitivity analyses conducted in response to reviewer feedback. We address three categories of concerns: (a) goodness-of-fit diagnostics assessing how well the dyadic-independence ERGM reproduces observed network structure (Section 6.1); (b) structural model extensions incorporating endogenous network terms — clustering (gwesp) and degree heterogeneity (gwidegree, gwodegree) — to evaluate whether the substantive coefficients are confounded with unmodeled network structure (Sections 6.2–6.4); (c) subset robustness tests, sensitivity checks, and multicollinearity diagnostics confirming that results are not driven by specific actor subgroups or by overlap between the authority and influence covariates (Sections 6.5–6.7).
6.1 Full ERGM goodness-of-fit diagnostics
We assess goodness-of-fit for Model 4 by simulating 200 networks from the fitted model and comparing their structural properties to the observed network. Diagnostics include in-degree and out-degree distributions, edgewise shared partner (ESP) counts, geodesic distances, the triad census, and cross-level mixing matrices. Because Model 4 uses only dyadic-independent terms, it is not expected to reproduce higher-order structural features (clustering, degree heterogeneity) — the GOF diagnostics quantify these discrepancies and motivate the structural extensions in Section 6.2.
6.1.1 Simulate networks and compute GOF
gof_result <- gof(
model4,
GOF = ~ idegree + odegree + espartners + distance,
control = control.gof.ergm(nsim = 200, seed = 12345)
)6.1.2 GOF summary statistics
print(gof_result)
Goodness-of-fit for in-degree
obs min mean max MC p-value
idegree0 2 0 1.295 5 0.77
idegree1 0 0 4.080 11 0.01
idegree2 0 1 7.165 14 0.00
idegree3 6 3 8.270 16 0.56
idegree4 45 3 9.670 17 0.00
idegree5 14 6 13.325 28 0.91
idegree6 3 8 17.175 28 0.00
idegree7 15 10 19.075 32 0.36
idegree8 19 10 17.450 28 0.78
idegree9 5 5 12.965 21 0.01
idegree10 1 1 8.280 16 0.01
idegree11 5 0 4.690 11 0.99
idegree12 4 0 2.355 7 0.44
idegree13 1 0 1.245 6 1.00
idegree14 3 0 0.595 3 0.06
idegree15 2 0 0.235 2 0.08
idegree16 1 0 0.095 2 0.18
idegree17 2 0 0.035 1 0.00
Goodness-of-fit for out-degree
obs min mean max MC p-value
odegree0 80 9 19.535 30 0.00
odegree1 15 23 33.640 48 0.00
odegree2 3 18 28.480 40 0.00
odegree3 4 9 16.160 25 0.00
odegree4 4 1 7.480 14 0.22
odegree5 2 0 3.400 8 0.55
odegree6 1 0 1.820 7 0.87
odegree7 3 0 1.105 4 0.13
odegree8 3 0 0.695 3 0.03
odegree9 0 0 0.410 2 1.00
odegree10 1 0 0.135 2 0.25
odegree11 0 0 0.125 1 1.00
odegree12 0 0 0.060 1 1.00
odegree13 0 0 0.075 2 1.00
odegree14 0 0 0.085 1 1.00
odegree15 1 0 0.145 2 0.28
odegree16 2 0 0.260 3 0.05
odegree17 2 0 0.325 2 0.06
odegree18 0 0 0.375 2 1.00
odegree19 0 0 0.495 3 1.00
odegree20 0 0 0.335 3 1.00
odegree21 0 0 0.350 2 1.00
odegree22 0 0 0.265 2 1.00
odegree23 0 0 0.240 2 1.00
odegree24 0 0 0.240 2 1.00
odegree25 0 0 0.340 2 1.00
odegree26 0 0 0.340 3 1.00
odegree27 0 0 0.585 3 1.00
odegree28 0 0 0.600 3 1.00
odegree29 0 0 0.735 4 0.93
odegree30 0 0 0.880 4 0.70
odegree31 0 0 0.900 5 0.72
odegree32 0 0 0.860 5 0.78
odegree33 0 0 0.700 4 0.95
odegree34 0 0 0.515 2 1.00
odegree35 0 0 0.480 3 1.00
odegree36 0 0 0.310 3 1.00
odegree37 0 0 0.150 2 1.00
odegree38 0 0 0.150 2 1.00
odegree39 0 0 0.080 1 1.00
odegree40 0 0 0.055 1 1.00
odegree41 0 0 0.040 1 1.00
odegree42 0 0 0.030 1 1.00
odegree44 0 0 0.010 1 1.00
odegree45 2 0 0.005 1 0.00
odegree50 1 0 0.000 0 0.00
odegree59 0 0 0.010 1 1.00
odegree62 0 0 0.020 1 1.00
odegree63 0 0 0.005 1 1.00
odegree64 0 0 0.005 1 1.00
odegree65 0 0 0.020 1 1.00
odegree66 0 0 0.035 1 1.00
odegree67 0 0 0.045 1 1.00
odegree68 0 0 0.070 1 1.00
odegree69 0 0 0.070 2 1.00
odegree70 0 0 0.105 1 1.00
odegree71 0 0 0.160 2 1.00
odegree72 0 0 0.250 2 1.00
odegree73 0 0 0.275 2 1.00
odegree74 0 0 0.340 3 1.00
odegree75 0 0 0.270 2 1.00
odegree76 0 0 0.330 2 1.00
odegree77 0 0 0.265 2 1.00
odegree78 0 0 0.210 2 1.00
odegree79 0 0 0.185 2 1.00
odegree80 0 0 0.115 2 1.00
odegree81 0 0 0.100 2 1.00
odegree82 0 0 0.100 2 1.00
odegree83 0 0 0.060 1 1.00
odegree84 0 0 0.085 1 1.00
odegree85 0 0 0.055 1 1.00
odegree86 0 0 0.095 2 1.00
odegree87 0 0 0.080 2 1.00
odegree88 0 0 0.095 1 1.00
odegree89 0 0 0.080 1 1.00
odegree90 0 0 0.075 1 1.00
odegree91 0 0 0.115 1 1.00
odegree92 0 0 0.040 1 1.00
odegree93 0 0 0.070 1 1.00
odegree94 0 0 0.045 1 1.00
odegree95 0 0 0.020 1 1.00
odegree96 0 0 0.035 1 1.00
odegree97 0 0 0.020 1 1.00
odegree98 0 0 0.005 1 1.00
odegree99 0 0 0.005 1 1.00
odegree100 0 0 0.005 1 1.00
odegree101 0 0 0.010 1 1.00
odegree102 0 0 0.005 1 1.00
odegree105 0 0 0.005 1 1.00
odegree109 0 0 0.005 1 1.00
odegree111 0 0 0.005 1 1.00
odegree120 1 0 0.000 0 0.00
odegree125 3 0 0.000 0 0.00
Goodness-of-fit for edgewise shared partner
obs min mean max MC p-value
esp.OTP0 196 300 357.115 410 0.00
esp.OTP1 113 134 165.125 200 0.00
esp.OTP2 169 62 101.550 146 0.00
esp.OTP3 96 52 75.530 106 0.05
esp.OTP4 21 32 53.830 81 0.00
esp.OTP5 51 20 35.590 54 0.07
esp.OTP6 74 8 22.190 40 0.00
esp.OTP7 33 3 12.435 37 0.01
esp.OTP8 10 0 6.250 14 0.33
esp.OTP9 18 0 2.670 10 0.00
esp.OTP10 17 0 1.045 4 0.00
esp.OTP11 5 0 0.585 6 0.01
esp.OTP12 11 0 0.185 2 0.00
esp.OTP13 8 0 0.070 2 0.00
esp.OTP14 5 0 0.020 1 0.00
esp.OTP15 9 0 0.000 0 0.00
Goodness-of-fit for minimum geodesic distance
obs min mean max MC p-value
1 836 780 834.190 903 0.91
2 173 1376 2020.110 2801 0.00
3 32 1733 3029.750 4211 0.00
4 1 1480 2962.200 3860 0.00
5 0 1055 1964.735 2625 0.00
6 0 292 1005.155 1680 0.00
7 0 23 421.105 993 0.00
8 0 0 163.545 662 0.03
9 0 0 68.170 587 0.22
10 0 0 28.260 473 0.77
11 0 0 13.275 496 1.00
12 0 0 5.165 217 1.00
13 0 0 1.860 157 1.00
14 0 0 0.705 102 1.00
15 0 0 0.030 4 1.00
Inf 15214 1770 3737.745 6795 0.00
Goodness-of-fit for model statistics
obs min mean max MC p-value
edges 836 780 834.190 903 0.91
edgecov.same_level 239 174 238.105 272 0.98
edgecov.authority_matrix 43 33 43.000 52 1.00
edgecov.influence_matrix 647 605 645.005 687 0.95
6.1.3 Extract p-values across all GOF statistics
extract_gof_pvals <- function(gof_obj) {
pvals <- c()
stat_names <- c()
for (nm in names(gof_obj)) {
mat <- gof_obj[[nm]]
if (is.matrix(mat) || is.data.frame(mat)) {
# GOF objects use "MC p-value" (with space); handle alternatives too
pcol <- NULL
if ("MC p-value" %in% colnames(mat)) pcol <- "MC p-value"
else if ("pvalue" %in% colnames(mat)) pcol <- "pvalue"
else {
pcol_idx <- grep("p-value|pvalue", colnames(mat), ignore.case = TRUE)
if (length(pcol_idx) > 0) pcol <- colnames(mat)[pcol_idx[1]]
}
if (!is.null(pcol)) {
pv <- as.numeric(mat[, pcol])
pvals <- c(pvals, pv)
stat_names <- c(stat_names, paste0(nm, ":", rownames(mat)))
}
}
}
data.frame(Statistic = stat_names, pvalue = pvals, row.names = NULL)
}
pval_df <- extract_gof_pvals(gof_result)
n_tests <- nrow(pval_df)
if (n_tests > 0) {
n_sig <- sum(pval_df$pvalue < 0.05, na.rm = TRUE)
pct_sig <- round(100 * n_sig / n_tests, 1)
cat("---- GOF p-value summary ----\n")
cat("Total GOF statistics tested:", n_tests, "\n")
cat("Significant deviations (p < 0.05):", n_sig, "(", pct_sig, "%)\n")
cat("Non-significant (adequate fit):", n_tests - n_sig,
"(", 100 - pct_sig, "%)\n")
} else {
cat("Note: Automated p-value extraction returned 0 tests.\n")
cat("Please consult the printed GOF summary above for p-values.\n")
}---- GOF p-value summary ----
Total GOF statistics tested: 1030
Significant deviations (p < 0.05): 74 ( 7.2 %)
Non-significant (adequate fit): 956 ( 92.8 %)
6.1.4 GOF diagnostic plots
par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
plot(gof_result, main = "")6.1.6 Triad census comparison
obs_triads <- sna::triad.census(as.matrix(money_network))
nsim_triads <- 200
sim_nets <- simulate(model4, nsim = nsim_triads, seed = 12345,
control = control.simulate.ergm(MCMC.burnin = 5000))
sim_triads_list <- list()
for (i in 1:nsim_triads) {
sim_triads_list[[i]] <- sna::triad.census(as.matrix(sim_nets[[i]]))
}
sim_triads_mat <- do.call(rbind, sim_triads_list)
triad_labels <- c("003", "012", "102", "021D", "021U", "021C",
"111D", "111U", "030T", "030C", "201", "120D",
"120U", "120C", "210", "300")
triad_summary <- data.frame(
Triad = triad_labels,
Observed = as.numeric(obs_triads),
Sim_Mean = round(colMeans(sim_triads_mat, na.rm = TRUE), 1),
Sim_SD = round(apply(sim_triads_mat, 2, sd, na.rm = TRUE), 2),
stringsAsFactors = FALSE
)
triad_summary$pvalue <- sapply(1:ncol(sim_triads_mat), function(j) {
obs_val <- obs_triads[j]
sim_vals <- sim_triads_mat[, j]
sim_mean <- mean(sim_vals)
mean(abs(sim_vals - sim_mean) >= abs(obs_val - sim_mean))
})
knitr::kable(triad_summary, digits = 3,
caption = "Triad census: observed vs. simulated from Model 4")| Triad | Observed | Sim_Mean | Sim_SD | pvalue | |
|---|---|---|---|---|---|
| 003 | 003 | 273897 | 259161.9 | 2221.82 | 0.000 |
| 012 | 012 | 32300 | 60746.4 | 1933.30 | 0.000 |
| 102 | 102 | 104 | 625.9 | 221.79 | 0.020 |
| 021D | 021D | 31824 | 15375.3 | 623.02 | 0.000 |
| 021U | 021U | 734 | 1469.9 | 117.95 | 0.000 |
| 021C | 021C | 220 | 2159.4 | 231.95 | 0.000 |
| 111D | 111D | 14 | 54.7 | 20.72 | 0.055 |
| 111U | 111U | 5 | 459.8 | 181.87 | 0.005 |
| 030T | 030T | 1899 | 1248.0 | 111.31 | 0.000 |
| 030C | 030C | 0 | 20.5 | 7.32 | 0.010 |
| 201 | 201 | 0 | 3.8 | 3.38 | 0.230 |
| 120D | 120D | 9 | 12.0 | 5.65 | 0.600 |
| 120U | 120U | 369 | 17.8 | 17.49 | 0.000 |
| 120C | 120C | 0 | 19.8 | 9.82 | 0.040 |
| 210 | 210 | 0 | 0.6 | 0.83 | 0.705 |
| 300 | 300 | 1 | 0.0 | 0.00 | 0.000 |
6.1.7 Cross-level mixing matrix comparison
obs_mix <- mixingmatrix(money_network, "level")
cat("Observed mixing matrix:\n")Observed mixing matrix:
print(obs_mix) To
From european local national Sum
european 112 161 280 553
local 0 41 0 41
national 1 155 86 242
Sum 113 357 366 836
sim_mix_list <- list()
for (i in 1:min(nsim_triads, length(sim_nets))) {
sim_mix_list[[i]] <- as.matrix(
mixingmatrix(sim_nets[[i]], "level")
)
}
mean_sim_mix <- Reduce("+", sim_mix_list) / length(sim_mix_list)
cat("\nMean simulated mixing matrix:\n")
Mean simulated mixing matrix:
print(round(mean_sim_mix, 1)) To
From european local national Sum
european 29.5 133.2 236.7 399.4
local 16.8 46.0 39.8 102.6
national 25.4 154.6 156.1 336.1
Sum 71.7 333.8 432.6 838.1
6.2 Add structural terms
We extend Model 4 with endogenous structural terms to test whether the substantive coefficients are robust to unmodeled network dependencies. Model 5 adds geometrically-weighted edgewise shared partners (gwesp) to capture transitivity. Model 6 adds geometrically-weighted in-degree and out-degree distributions (gwidegree, gwodegree) to capture degree heterogeneity. Model 7 combines all structural terms. The coefficient comparison table (Section 6.2.4) shows how each substantive estimate changes as structural controls are introduced, providing a transparent assessment of potential confounding between governance covariates and endogenous network structure.
6.2.1 Model 5: + gwesp (transitivity / clustering)
tryCatch({
model5 <- ergm(
money_network ~ edges +
edgecov(same_level) +
edgecov(authority_matrix) +
edgecov(influence_matrix) +
gwesp(0.5, fixed = TRUE),
control = paper_control
)
}, error = function(e) {
cat("Model 5 failed:", conditionMessage(e), "\n")
})--- Model 5: + gwesp ---
Estimate Std. Error MCMC % z value Pr(>|z|)
edges -4.4921595 0.08695819 0 -51.658845 0.000000e+00
edgecov.same_level -1.0788527 0.11442866 0 -9.428168 4.173131e-21
edgecov.authority_matrix 1.8911542 0.29206352 0 6.475147 9.471978e-11
edgecov.influence_matrix 4.6371163 0.11894993 0 38.983765 0.000000e+00
gwesp.OTP.fixed.0.5 0.4544012 0.04944341 0 9.190329 3.916414e-20
AIC: 3125.3
BIC: 3163.8
Delta-AIC vs Model 4: -114 (negative = improvement)
6.2.2 Model 6: + gwidegree + gwodegree (degree heterogeneity)
tryCatch({
model6 <- ergm(
money_network ~ edges +
edgecov(same_level) +
edgecov(authority_matrix) +
edgecov(influence_matrix) +
gwidegree(0.5, fixed = TRUE) +
gwodegree(0.5, fixed = TRUE),
control = paper_control
)
}, error = function(e) {
cat("Model 6 failed:", conditionMessage(e), "\n\n")
})--- Model 6: + gwidegree + gwodegree ---
Estimate Std. Error MCMC % z value Pr(>|z|)
edges -2.836272 0.07662445 0 -37.015233 6.513952e-300
edgecov.same_level -1.010458 0.11778177 0 -8.579073 9.564086e-18
edgecov.authority_matrix 1.753476 0.26704182 0 6.566298 5.158139e-11
edgecov.influence_matrix 3.819709 0.10706953 0 35.675030 9.643905e-279
gwideg.fixed.0.5 1.143203 0.86650789 0 1.319323 1.870613e-01
gwodeg.fixed.0.5 -4.944827 0.28492285 0 -17.354967 1.809040e-67
AIC: 2847
BIC: 2893.2
Delta-AIC vs Model 4: -392.3
6.2.3 Model 7: all structural terms
tryCatch({
model7 <- ergm(
money_network ~ edges +
edgecov(same_level) +
edgecov(authority_matrix) +
edgecov(influence_matrix) +
gwesp(0.5, fixed = TRUE) +
gwidegree(0.5, fixed = TRUE) +
gwodegree(0.5, fixed = TRUE),
control = paper_control
)
}, error = function(e) {
cat("Model 7 failed:", conditionMessage(e), "\n")
})--- Model 7: full structural ---
Estimate Std. Error MCMC % z value Pr(>|z|)
edges -3.0928699 0.09069288 0 -34.102674 6.732672e-255
edgecov.same_level -1.0493750 0.10923407 0 -9.606664 7.493745e-22
edgecov.authority_matrix 1.8662513 0.24925724 0 7.487250 7.033163e-14
edgecov.influence_matrix 3.7255393 0.10737271 0 34.697265 8.663170e-264
gwesp.OTP.fixed.0.5 0.1858227 0.03594406 0 5.169776 2.343753e-07
gwideg.fixed.0.5 1.0367984 0.82463268 0 1.257285 2.086504e-01
gwodeg.fixed.0.5 -4.5218920 0.28842813 0 -15.677708 2.148680e-55
AIC: 2814
BIC: 2867.8
Delta-AIC vs Model 4: -425.3
===> KEY RESULT: Influence effect after controlling for
endogenous clustering and degree heterogeneity:
Estimate: 3.726
OR: 41.49
p: <2e-16
6.2.4 Coefficient comparison across Models 4–7
This table shows how each substantive coefficient changes as endogenous structural terms (clustering, degree heterogeneity) are progressively added. Changes in the influence and authority coefficients indicate the extent to which the original Model 4 estimates absorbed confounding from unmodeled network structure.
| Model | Term | Estimate | SE | OR | CI_lo | CI_hi | AIC |
|---|---|---|---|---|---|---|---|
| Model 4 (dyadic) | edges | -4.140 | 0.077 | 0.02 | 0.01 | 0.02 | 3239.3 |
| Model 4 (dyadic) | edgecov.same_level | -1.040 | 0.112 | 0.35 | 0.28 | 0.44 | 3239.3 |
| Model 4 (dyadic) | edgecov.authority_matrix | 1.751 | 0.305 | 5.76 | 3.17 | 10.48 | 3239.3 |
| Model 4 (dyadic) | edgecov.influence_matrix | 5.127 | 0.106 | 168.54 | 137.00 | 207.34 | 3239.3 |
| Model 5 (+gwesp) | edges | -4.492 | 0.087 | 0.01 | 0.01 | 0.01 | 3125.3 |
| Model 5 (+gwesp) | edgecov.same_level | -1.079 | 0.114 | 0.34 | 0.27 | 0.43 | 3125.3 |
| Model 5 (+gwesp) | edgecov.authority_matrix | 1.891 | 0.292 | 6.63 | 3.74 | 11.75 | 3125.3 |
| Model 5 (+gwesp) | edgecov.influence_matrix | 4.637 | 0.119 | 103.25 | 81.78 | 130.35 | 3125.3 |
| Model 5 (+gwesp) | gwesp.OTP.fixed.0.5 | 0.454 | 0.049 | 1.58 | 1.43 | 1.74 | 3125.3 |
| Model 6 (+degree) | edges | -2.836 | 0.077 | 0.06 | 0.05 | 0.07 | 2847.0 |
| Model 6 (+degree) | edgecov.same_level | -1.010 | 0.118 | 0.36 | 0.29 | 0.46 | 2847.0 |
| Model 6 (+degree) | edgecov.authority_matrix | 1.753 | 0.267 | 5.77 | 3.42 | 9.75 | 2847.0 |
| Model 6 (+degree) | edgecov.influence_matrix | 3.820 | 0.107 | 45.59 | 36.96 | 56.24 | 2847.0 |
| Model 6 (+degree) | gwideg.fixed.0.5 | 1.143 | 0.867 | 3.14 | 0.57 | 17.14 | 2847.0 |
| Model 6 (+degree) | gwodeg.fixed.0.5 | -4.945 | 0.285 | 0.01 | 0.00 | 0.01 | 2847.0 |
| Model 7 (full) | edges | -3.093 | 0.091 | 0.05 | 0.04 | 0.05 | 2814.0 |
| Model 7 (full) | edgecov.same_level | -1.049 | 0.109 | 0.35 | 0.28 | 0.43 | 2814.0 |
| Model 7 (full) | edgecov.authority_matrix | 1.866 | 0.249 | 6.46 | 3.97 | 10.54 | 2814.0 |
| Model 7 (full) | edgecov.influence_matrix | 3.726 | 0.107 | 41.49 | 33.62 | 51.21 | 2814.0 |
| Model 7 (full) | gwesp.OTP.fixed.0.5 | 0.186 | 0.036 | 1.20 | 1.12 | 1.29 | 2814.0 |
| Model 7 (full) | gwideg.fixed.0.5 | 1.037 | 0.825 | 2.82 | 0.56 | 14.20 | 2814.0 |
| Model 7 (full) | gwodeg.fixed.0.5 | -4.522 | 0.288 | 0.01 | 0.01 | 0.02 | 2814.0 |
6.3 GOF of best structural model
We compare AIC values across the structural model candidates and identify the best-fitting specification. This serves as a benchmark for evaluating whether the structural extensions meaningfully improve model fit relative to the original dyadic-independence model.
structural_candidates <- list()
if (exists("model5")) structural_candidates[["Model 5 (gwesp)"]] <- model5
if (exists("model6")) structural_candidates[["Model 6 (degree)"]] <- model6
if (exists("model7")) structural_candidates[["Model 7 (full)"]] <- model7
if (length(structural_candidates) > 0) {
aic_vals <- sapply(structural_candidates, AIC)
cat("Structural model AIC comparison:\n")
print(round(aic_vals, 1))
cat("Original Model 4 AIC:", round(AIC(model4), 1), "\n\n")
best_name <- names(which.min(aic_vals))
best_structural <- structural_candidates[[best_name]]
cat("Best structural model:", best_name, "(AIC =",
round(min(aic_vals), 1), ")\n\n")
} else {
best_structural <- NULL
cat("No structural models converged.\n\n")
}Structural model AIC comparison:
Model 5 (gwesp) Model 6 (degree) Model 7 (full)
3125.3 2847.0 2814.0
Original Model 4 AIC: 3239.3
Best structural model: Model 7 (full) (AIC = 2814 )
6.4 MCMLE alternative
Models 5–7 require MCMC-based estimation (MCMLE) because they include endogenous network terms that violate dyadic independence. We verify that the MCMLE estimates for Model 5 are consistent with the MPLE starting values, confirming that the estimation method does not drive the results.
mcmle_control <- control.ergm(
main.method = "MCMLE",
MCMC.samplesize = 4096,
MCMC.burnin = 20000,
MCMC.interval = 1024,
seed = 12345,
MCMLE.maxit = 40
)
tryCatch({
model_mcmle <- ergm(
money_network ~ edges +
edgecov(same_level) +
edgecov(authority_matrix) +
edgecov(influence_matrix) +
gwesp(0.5, fixed = TRUE),
control = mcmle_control
)
}, error = function(e) {
cat("MCMLE failed:", conditionMessage(e), "\n")
})--- MCMLE structural model ---
Estimate Std. Error MCMC % z value Pr(>|z|)
edges -4.4836007 0.08866244 0 -50.569335 0.000000e+00
edgecov.same_level -1.0710235 0.11056890 0 -9.686480 3.441859e-22
edgecov.authority_matrix 1.8370331 0.31154317 0 5.896560 3.711571e-09
edgecov.influence_matrix 4.6255314 0.12603669 0 36.699878 7.335737e-295
gwesp.OTP.fixed.0.5 0.4502692 0.04995400 0 9.013677 1.992608e-19
AIC: 3127.8
BIC: 3166.3
6.5 Subset robustness tests
We test whether the ERGM results hold across different portions of the network. The cross-level subset restricts analysis to funding ties between organizations at different governance levels. Perfect separation in this subset (all 597 cross-level influence ties coincide with cross-level funding ties) prevents estimation of the influence coefficient; we report the authority-only model instead. The no-EU subset removes the 25 European-level actors and re-estimates the full model on the remaining 103 national and local organizations. A robustness comparison table presents the substantive coefficients side by side across specifications.
6.5.1 Cross-level funding ties only
# Construct cross-level subset
cross_mat <- money_mat
cross_mat[same_level == 1] <- 0
cross_net <- network(cross_mat, directed = TRUE)
for (attr in network::list.vertex.attributes(money_network)) {
if (attr != "na") {
network::set.vertex.attribute(
cross_net, attr,
network::get.vertex.attribute(money_network, attr)
)
}
}
auth_cross <- authority_matrix
auth_cross[same_level == 1] <- 0
inf_cross <- influence_matrix
inf_cross[same_level == 1] <- 0When restricting the network to cross-level funding ties only, estimation of the full model (with both authority and influence covariates) fails due to perfect separation: every cross-level influence tie coincides with a cross-level funding tie, producing infinite coefficient estimates and undefined standard errors. The diagnostic below confirms this complete overlap.
Cross-level subset:
Same-level ties removed: 239
Cross-level ties retained: 597
Perfect separation diagnostic:
Cross-level influence ties: 597
Cross-level funding ties: 597
Cross-level influence ties WITH funding: 597
Cross-level influence ties WITHOUT funding: 0
Because every cross-level influence tie coincides with a cross-level
funding tie, the influence coefficient is not estimable in this subset.
We therefore estimate an authority-only model below.
6.5.2 Remove EU actors
tryCatch({
non_eu <- which(levels_vec != "european")
sub_money <- money_mat[non_eu, non_eu]
sub_auth <- authority_matrix[non_eu, non_eu]
sub_inf <- influence_matrix[non_eu, non_eu]
sub_same_lev <- same_level[non_eu, non_eu]
sub_net <- network(sub_money, directed = TRUE)
network::set.vertex.attribute(sub_net, "level", levels_vec[non_eu])
model_c2 <- ergm(
sub_net ~ edges +
edgecov(sub_same_lev) +
edgecov(sub_auth) +
edgecov(sub_inf),
control = paper_control
)
results_c2 <- extract_results(model_c2, "No EU actors")
}, error = function(e) {
cat("Model failed:", conditionMessage(e), "\n\n")
})EU nodes removed: 25
Remaining nodes: 103
Ties in subgraph: 282
Term OR CI_lo CI_hi pvalue
1 edges 0.01 0.01 0.02 0.000000e+00
2 edgecov.sub_same_lev 0.36 0.27 0.49 1.368290e-11
3 edgecov.sub_auth 8.08 3.75 17.38 9.186551e-08
4 edgecov.sub_inf 66.17 49.29 88.83 2.311459e-171
Note: Since all EU-level actors are at the European governance level and no national or local actors are classified as European, the “Remove EU actors” subgraph is identical to the “National-local only” subgraph (103 nodes, 282 ties). A separate national-local analysis is therefore omitted as it would produce identical results.
6.5.3 Robustness comparison table
| Model | Term | OR | CI_lo | CI_hi | pvalue | AIC |
|---|---|---|---|---|---|---|
| Full (Model 4) | edges | 0.02 | 0.01 | 0.02 | 0e+00 | 3239.3 |
| Full (Model 4) | edgecov.same_level | 0.35 | 0.28 | 0.44 | 0e+00 | 3239.3 |
| Full (Model 4) | edgecov.authority_matrix | 5.76 | 3.17 | 10.48 | 0e+00 | 3239.3 |
| Full (Model 4) | edgecov.influence_matrix | 168.54 | 137.00 | 207.34 | 0e+00 | 3239.3 |
| C1: Cross-level (auth only) | edges | 0.04 | 0.03 | 0.04 | 0e+00 | 5075.1 |
| C1: Cross-level (auth only) | edgecov.auth_cross | 6.72 | 4.27 | 10.59 | 0e+00 | 5075.1 |
| No EU actors | edges | 0.01 | 0.01 | 0.02 | 0e+00 | 1741.8 |
| No EU actors | edgecov.sub_same_lev | 0.36 | 0.27 | 0.49 | 0e+00 | 1741.8 |
| No EU actors | edgecov.sub_auth | 8.08 | 3.75 | 17.38 | 1e-07 | 1741.8 |
| No EU actors | edgecov.sub_inf | 66.17 | 49.29 | 88.83 | 0e+00 | 1741.8 |
6.6 Sensitivity checks (Weighted / Threshold Alternative)
We examine additional structural features of the governance-funding relationship. The governance tie overlap analysis characterizes the proportion of funded dyads backed by zero, one, or two types of governance ties. A contingency table cross-tabulating governance backing with same-level/cross-level status reveals two distinct funding mechanisms: governance-channeled cross-level ties and governance-independent same-level ties. We also check for funding isolates (none exist in this network).
6.6.1 Governance tie overlap
overlap_mat <- (authority_matrix > 0) + (influence_matrix > 0)
cat("Dyad governance overlap:\n")Dyad governance overlap:
cat(" No governance tie:", sum(overlap_mat == 0), "dyads\n") No governance tie: 15260 dyads
cat(" One type only:", sum(overlap_mat == 1), "dyads\n") One type only: 1086 dyads
cat(" Both authority + influence:", sum(overlap_mat == 2), "dyads\n\n") Both authority + influence: 38 dyads
money_adj <- as.matrix(money_network, type = "adjacency")
funded <- which(money_adj > 0, arr.ind = TRUE)
overlap_funded <- overlap_mat[funded]
cat("Among funded dyads (n =", nrow(funded), "):\n")Among funded dyads (n = 836 ):
cat(" No governance tie:", sum(overlap_funded == 0),
"(", round(100 * mean(overlap_funded == 0), 1), "%)\n") No governance tie: 184 ( 22 %)
cat(" One type only:", sum(overlap_funded == 1),
"(", round(100 * mean(overlap_funded == 1), 1), "%)\n") One type only: 614 ( 73.4 %)
cat(" Both types:", sum(overlap_funded == 2),
"(", round(100 * mean(overlap_funded == 2), 1), "%)\n") Both types: 38 ( 4.5 %)
Note: An ERGM using the “high-intensity” indicator (both authority and influence present, n = 38 dyads) as a predictor of funding ties exhibits perfect separation — all 38 high-intensity dyads are funded — producing an infinite coefficient. This confirms the strong governance-funding association but prevents formal statistical testing for this combined indicator.
6.6.2 Funding ties by governance backing
funded_dyads <- which(money_adj > 0, arr.ind = TRUE)
has_gov <- overlap_mat[funded_dyads] > 0
cat("Total funding ties:", nrow(funded_dyads), "\n")Total funding ties: 836
cat("With governance tie:", sum(has_gov),
"(", round(100 * mean(has_gov), 1), "%)\n")With governance tie: 652 ( 78 %)
cat("Without governance tie:", sum(!has_gov),
"(", round(100 * mean(!has_gov), 1), "%)\n\n")Without governance tie: 184 ( 22 %)
same_lev_funded <- same_level[funded_dyads]
cat("Among governed funding ties — same-level:",
round(100 * mean(same_lev_funded[has_gov]), 1), "%\n")Among governed funding ties — same-level: 8.4 %
cat("Among ungoverned funding ties — same-level:",
round(100 * mean(same_lev_funded[!has_gov]), 1), "%\n\n")Among ungoverned funding ties — same-level: 100 %
tab <- table(
Governance = ifelse(has_gov, "Yes", "No"),
SameLevel = ifelse(same_lev_funded == 1, "Same", "Cross")
)
cat("Contingency table:\n")Contingency table:
print(tab) SameLevel
Governance Cross Same
No 0 184
Yes 597 55
cat("\n")print(chisq.test(tab))
Pearson's Chi-squared test with Yates' continuity correction
data: tab
X-squared = 584.85, df = 1, p-value < 2.2e-16
6.6.3 Remove funding isolates
Nodes with >= 1 funding tie: 128 of 128
No isolates found — all 128 nodes have funding ties.