Exploration of project citations

Author

Romain Frelat

Published

August 13, 2026

The objective is to explore the associations among FRB-MTE-OFB projects based on shared references. Shared references can reveal common conceptual frameworks, methodological approaches, or research topics across projects.

Based on references, we:

The analysis is based on a bipartite network linking projects and references, which is subsequently projected into project and reference networks. This document is mostly an exploration of (bipartite) network analysis methodology.

General overview

Code
devtools::load_all()
library(igraph) # network analysis
library(visNetwork) # interactive network
library(plotly) # interactive plot

# load reference list
ref <- read.csv(
  here::here("data", "derived-data", "mte_references_completed.csv")
)
ref$Project <- firstup(ref$Project)
# remove references with no DOI
ref <- ref[!is.na(ref$DOI), ]

ref$Relation <- factor(
  ref$Relation,
  levels = c("Proposal", "Top5", "Output"),
  ordered = TRUE
)
ref <- ref[order(ref$Project, ref$Relation, ref$DOI, decreasing = TRUE), ]
# remove duplicates # sum(duplicated(ref))
ref <- ref[!duplicated(ref[, c("Project", "DOI")]), ]

# load informations on projects
meta <- readxl::read_xlsx(
  here::here("data", "raw-data", "mte", "Projets_FRB-MTECT-OFB.xlsx")
)

ndoi <- table(table(ref$DOI))

# complete the references with author names
load(
  here::here("data", "derived-data", "mte_references_oa_shinycomplete.RData")
)
# M <- readRDS(here::here("data", "derived-data", "mte_bibliometrix.rds"))
# table(ref$DOI %in% M$DI)
ref$Short <- M$SR[match(ref$DOI, M$DI)]
ref$FAut <- M$AU_CORR[match(ref$DOI, M$DI)]
Code
# table(ref$Year, ref$Project)
table(ref$Project, ref$Relation)
             
              Proposal Top5 Output
  Acoucene          23   48      4
  Beyonds           51   57      0
  Carapat           18    0      0
  Comepi            21    4      0
  Desybel           12   10      1
  Discar            15   53      7
  Dragon             8   44      1
  Ebenn              8    5      0
  Elan              17    4      0
  Fellow            20   51      1
  Fragshifts        13    9      2
  Funbiodiv         23   61      0
  Funindic          33    4      3
  Impacts           28   51      4
  Indicators        26    9      0
  Interface          6    9      4
  Landbio           25    6      0
  Landworm          50   36      4
  Motiver           17   53      0
  Pppirec           13   12      0
  Rodexpo           20    7      0
  Solaire-pb        12    9      2
  Spatman           29   49      0
  Tres-pratic        6    4      0
Code
# between 11 and 116 references per project
# summary(as.numeric(table(ref$Project)))
n_ref <- data.frame(table(ref$Project))
n_ref$Type <- meta$Type[match(n_ref$Var1, meta$Acronyme)]
tapply(n_ref$Freq, n_ref$Type, summary)
$`REVUE SYSTEMATIQUE`
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  10.00   12.25   18.00   17.25   23.00   23.00 

$SYNERGIE
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  18.00   21.75   25.00   26.50   30.00   40.00 

$SYNTHESE
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  53.00   72.75   76.50   78.80   83.75  108.00 

Each project cited between 10 and 108 references in their proposal. These references come from the main text (Proposal), the top articles per member (Top5) or listed as Output in the final report. There are strong differences in the proposal format depending on the project type. Synthese projects have the most complete proposal compare to Synergie and Revue systematique.

In total, there are 1122 references listed by the 24 projects. Most publications are cited by a single project (N=965), but 72 publications are cited by at least two projects.

Bipartite network

We can build a bi-partite network in which references connect projects.

Code
netBI <- graph_from_data_frame(
  ref[, c("DOI", "Project")]
)
V(netBI)$type <- V(netBI)$name %in% ref$Project
# V(netBI)$size <- 1
# modBI <- cluster_walktrap(netBI)

The bipartite network has 1061 nodes (project or reference), 1122 edges, and a connectance of 0.001.

Code
V(netBI)$color <- ifelse(V(netBI)$type, "red", "blue")
V(netBI)$shape <- ifelse(V(netBI)$type, "square", "dot")

V(netBI)$label <- ifelse(V(netBI)$type, V(netBI)$name, "")
# V(netBI)$size <- degree(netBI)
extra <- ifelse(
  V(netBI)$type,
  paste(
    meta$Type[match(V(netBI)$name, meta$Acronyme)],
    meta$Year[match(V(netBI)$name, meta$Acronyme)],
    sep = "<br>"
  ),
  ref$Short[match(V(netBI)$name, ref$DOI)]
)

V(netBI)$title <- paste(
  V(netBI)$name,
  extra,
  sep = "<br>"
)

showBI <- delete_vertices(netBI, degree(netBI) == 1)

# plot(netBI, layout = layout_as_bipartite(netBI))
visNetwork::visIgraph(showBI, idToLabel = FALSE, smooth = TRUE) |>
  visLegend(
    addNodes = list(
      list(
        label = "Project",
        shape = "square",
        color = "red"
      ),
      list(
        label = "Reference",
        shape = "dot",
        color = "blue"
      )
    ),
    useGroups = FALSE
  ) |>
  visOptions(
    highlightNearest = TRUE,
  )

To facilitate interpretation, the bipartite network is projected into two complementary networks:

  • a project network to explore similarities among projects
  • a reference network to identify the most central references
Code
splitBI <- bipartite_projection(netBI)
netREF <- splitBI$proj1
netPR <- splitBI$proj2

Project network

Based on the number of shared references, we can build a network of projects.

Code
# manually count references
# group_mem <- table(mem$group, mem$name)
# member_overlap <- tcrossprod(group_mem)
# diag(member_overlap) <- 0
# netPR <- igraph::graph_from_adjacency_matrix(
#   member_overlap,
#   mode = "undirected",
#   weighted = TRUE
# )

# weigthed by number of references
# sim <- similarity(netPR, method = "jaccard")
# diag(sim) <- 0
# netPR <- igraph::graph_from_adjacency_matrix(
#   sim,
#   mode = "undirected",
#   weighted = TRUE
# )

modPR <- cluster_walktrap(netPR)
cluPR <- membership(modPR)
nclu <- table(cluPR)
cclu <- colorspace::qualitative_hcl(n = length(nclu), palette = "Dark 3")
cclu[nclu == 1] <- "#808080" # grey
colPR <- cclu[cluPR]
simclu <- ifelse(cluPR <= (sum(nclu > 1)), cluPR, "isolated")

The network has 24 nodes (one node = one project) and 53 edges (one edge = shared reference between two projects). The connectance is 0.192 and the modularity 0.38

Code
V(netPR)$type <- as.factor(meta$Type[match(V(netPR)$name, meta$Acronyme)])
V(netPR)$shape <- c("triangle", "square", "dot")[V(netPR)$type]
V(netPR)$year <- meta$Year[match(V(netPR)$name, meta$Acronyme)]
V(netPR)$group <- simclu
V(netPR)$color <- colPR
E(netPR)$width <- E(netPR)$weight
E(netPR)$title <- E(netPR)$weight
V(netPR)$title <- paste(
  V(netPR)$name,
  V(netPR)$type,
  V(netPR)$year,
  sep = "<br>"
)
visNetwork::visIgraph(
  netPR,
  randomSeed = 54,
  layout = "layout_nicely",
  smooth = TRUE,
  type = "full"
) |>
  visOptions(
    highlightNearest = TRUE,
    selectedBy = list(variable = "group", multiple = T)
  ) |>
  visLegend(
    addNodes = list(
      list(
        label = "Revue",
        shape = "triangle",
        color = "grey"
      ),
      list(
        label = "Synergie",
        shape = "square",
        color = "grey"
      ),
      list(
        label = "Synthese",
        shape = "dot",
        color = "grey"
      )
    ),
    useGroups = FALSE
  ) |>
  visPhysics(stabilization = TRUE)
Code
# identify cliques
for (i in sort(unique(simclu))) {
  print(paste(
    i,
    paste(V(netPR)$name[simclu == i], collapse = ", "),
    sep = ": "
  ))
}
[1] "1: Solaire-pb, Ebenn, Desybel"
[1] "2: Motiver, Landbio, Funbiodiv, Fellow, Beyonds, Acoucene"
[1] "3: Spatman, Interface, Elan, Dragon"
[1] "4: Tres-pratic, Pppirec, Landworm, Impacts, Funindic"
[1] "5: Indicators, Comepi"
[1] "isolated: Rodexpo, Fragshifts, Discar, Carapat"

Reference network

The reference network links publications that are cited by the same projects. Central references are therefore publications that contribute to connecting different parts of the project landscape.

We examine three centrality indices:

  • Betweenness: number of shortest paths going through a reference
  • Degree: number of connections to other references;
  • Google PageRank: influence based on the number and the importance of connected references.
Code
# show the weight
E(netREF)$width <- E(netREF)$weight

V(netREF)$size <- log(degree(netREF))

V(netREF)$label <- ref$Short[match(V(netREF)$name, ref$DOI)]
V(netREF)$year <- ref$Year[match(V(netREF)$name, ref$DOI)]
V(netREF)$faut <- ref$FAut[match(V(netREF)$name, ref$DOI)]
V(netREF)$fulltitle <- ref$Title[match(V(netREF)$name, ref$DOI)]
V(netREF)$shorttitle <- substr(V(netREF)$fulltitle, 1, 25)

V(netREF)$title <- paste(
  V(netREF)$name,
  V(netREF)$year,
  V(netREF)$faut,
  V(netREF)$shorttitle,
  sep = "<br>"
)

# plot(netREF)
# visNetwork::visIgraph(netREF, randomSeed = 54, layout = "layout_nicely") |>
#   visOptions(
#     highlightNearest = TRUE,
#   )
Code
centR <- data.frame(
  "betweenness" = betweenness(netREF),
  "degree" = degree(netREF),
  "pagerank" = page_rank(netREF)$vector,
  "lab" = V(netREF)$title
)

plot_ly(centR) |>
  add_markers(
    x = ~betweenness,
    y = ~pagerank,
    size = ~degree,
    text = ~lab,
    hoverinfo = "text"
  ) |>
  layout(title = "Reference centrality") |>
  config(
    modeBarButtons = list(list("toImage")),
    displaylogo = FALSE
  )
Code
info <- apply(centR[, -4], 2, round, 5)
score <- apply(scale(sqrt(info)), 1, mean)
info <- as.data.frame(info)
info$first_aut <- V(netREF)$faut
info$year <- V(netREF)$year
info$short_title <- V(netREF)$shorttitle
DT::datatable(info[order(score, decreasing = TRUE)[1:50], ])

Conclusion

We identified 5 sub-groups of projects based on shared literature and a handful of the most influential references among the FRB-MTE-OFB projects.

Yet, these results are drawn only from the raw reference list, ignoring the relation between the articles, which could be explored by a co-citation network. To further estimate the breadth and interdisciplinarity of research projects, we need integrate the citations among a larger pool of articles.