Supplementary Material

Replication data and code for stakeholder mapping and Net-Map health governance analysis

Authors
Affiliations

Bianca-Elena Mihăilă

Department of Sociology, University of Bucharest, Romania

Center for Innovation in Medicine, Bucharest, Romania

Marian-Gabriel Hâncean

Department of Sociology, University of Bucharest, Romania

Center for Innovation in Medicine, Bucharest, Romania

Marius Geantă

Center for Innovation in Medicine, Bucharest, Romania

Cosmina Cioroboiu

Center for Innovation in Medicine, Bucharest, Romania

Jürgen Lerner

Department of Computer and Information Science, University of Konstanz, Germany

José Luis Molina

GRAFO, Department of Social and Cultural Anthropology, Universitat Autònoma de Barcelona, Spain

Antonin Tron-Lozai

Department of Sociology, University of Bucharest, Romania

Center for Innovation in Medicine, Bucharest, Romania

Bogdan-Adrian Vidrașcu

Department of Sociology, University of Bucharest, Romania

Center for Innovation in Medicine, Bucharest, Romania

Iulian Oană

Department of Sociology, University of Bucharest, Romania

Center for Innovation in Medicine, Bucharest, Romania

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

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)

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.3 Model 3: Add authority relationships

cat("Model 3: Authority relationships\n")
model3 <- ergm(money_network ~ edges + edgecov(same_level) + edgecov(authority_matrix), 
               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")
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 = "")
Figure 1: Goodness-of-fit diagnostics for Model 4
Figure 2: Goodness-of-fit diagnostics for Model 4

6.1.5 Clustering assessment (edgewise shared partners)

If observed ESP values consistently exceed the simulated means, the model underestimates clustering (transitivity). Check the ESP panel in the GOF diagnostic plot above for visual assessment.

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 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.

Coefficient comparison: Models 4–7
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] <- 0

When 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.1.1 Cross-level ties, authority only

Influence is excluded due to perfect prediction.

model_c1 <- ergm(
  cross_net ~ edges +
    edgecov(auth_cross),
  control = paper_control
)
                Term   OR CI_lo CI_hi       pvalue
1              edges 0.04  0.03  0.04 0.000000e+00
2 edgecov.auth_cross 6.72  4.27 10.59 2.133036e-16

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

Robustness comparison across network subsets
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.

6.7 Multicollinearity between authority and influence

If authority and influence covariates strongly overlap, their ERGM coefficients could be unstable or mutually confounded. We assess this concern using three complementary approaches: QAP permutation tests for pairwise correlations between all three relational networks (authority, influence, funding), Jaccard similarity for the binary overlap between authority and influence ties, and coefficient stability analysis comparing estimates from models with and without each covariate. Together, these diagnostics establish that authority and influence capture empirically distinct relational dimensions.

6.7.1 QAP correlation

auth_mat <- as.matrix(authority_matrix)
inf_mat  <- as.matrix(influence_matrix)

# Authority–Influence
dat <- array(dim = c(2, n, n))
dat[1,,] <- auth_mat
dat[2,,] <- inf_mat

gcor_val <- sna::gcor(dat, g1 = 1, g2 = 2)
cat("Authority–Influence Pearson correlation:", round(gcor_val, 4), "\n\n")
Authority–Influence Pearson correlation: 0.0763 
qap_test <- sna::qaptest(
  dat,
  FUN = sna::gcor, g1 = 1, g2 = 2,
  reps = 2000
)
cat("Observed correlation:", round(qap_test$testval, 4), "\n")
Observed correlation: 0.0763 
cat("p-value (right tail):", round(qap_test$pgreq, 4), "\n\n")
p-value (right tail): 0 
# Influence–Funding
dat_if <- array(dim = c(2, n, n))
dat_if[1,,] <- inf_mat
dat_if[2,,] <- as.matrix(money_network, type = "adjacency")

gcor_if <- sna::gcor(dat_if, g1 = 1, g2 = 2)
cat("Influence–Funding Pearson correlation:", round(gcor_if, 4), "\n\n")
Influence–Funding Pearson correlation: 0.6858 
qap_if <- sna::qaptest(
  dat_if,
  FUN = sna::gcor, g1 = 1, g2 = 2,
  reps = 2000
)
cat("Observed correlation:", round(qap_if$testval, 4), "\n")
Observed correlation: 0.6858 
cat("p-value (right tail):", round(qap_if$pgreq, 4), "\n\n")
p-value (right tail): 0 
# Authority–Funding
dat_af <- array(dim = c(2, n, n))
dat_af[1,,] <- auth_mat
dat_af[2,,] <- as.matrix(money_network, type = "adjacency")

gcor_af <- sna::gcor(dat_af, g1 = 1, g2 = 2)
cat("Authority–Funding Pearson correlation:", round(gcor_af, 4), "\n\n")
Authority–Funding Pearson correlation: 0.1028 
qap_af <- sna::qaptest(
  dat_af,
  FUN = sna::gcor, g1 = 1, g2 = 2,
  reps = 2000
)
cat("Observed correlation:", round(qap_af$testval, 4), "\n")
Observed correlation: 0.1028 
cat("p-value (right tail):", round(qap_af$pgreq, 4), "\n\n")
p-value (right tail): 0 

6.7.2 Jaccard overlap

auth_ties  <- sum(auth_mat > 0)
inf_ties   <- sum(inf_mat > 0)
both_ties  <- sum((auth_mat > 0) & (inf_mat > 0))
either_ties <- sum((auth_mat > 0) | (inf_mat > 0))
jaccard    <- both_ties / either_ties

cat("Authority ties:", auth_ties, "\n")
Authority ties: 150 
cat("Influence ties:", inf_ties, "\n")
Influence ties: 1012 
cat("Overlapping ties (present in both):", both_ties, "\n")
Overlapping ties (present in both): 38 
cat("Jaccard similarity:", round(jaccard, 3), "\n")
Jaccard similarity: 0.034 
cat("Overlap as % of authority:", round(100 * both_ties / auth_ties, 1), "%\n")
Overlap as % of authority: 25.3 %
cat("Overlap as % of influence:", round(100 * both_ties / inf_ties, 1), "%\n")
Overlap as % of influence: 3.8 %

6.7.3 Coefficient stability (VIF-like diagnostic)

model_no_inf <- ergm(
  money_network ~ edges +
    edgecov(same_level) +
    edgecov(authority_matrix),
  control = paper_control
)

model_no_auth <- ergm(
  money_network ~ edges +
    edgecov(same_level) +
    edgecov(influence_matrix),
  control = paper_control
)
Authority coefficient:
  Without influence: 1.989  (OR = 7.31 )
  With influence:    1.751  (OR = 5.76 )
  % change: -11.9 %
Influence coefficient:
  Without authority: 5.137  (OR = 170.13 )
  With authority:    5.127  (OR = 168.54 )
  % change: -0.2 %