bibnets is an R package for importing, constructing, and exporting
bibliometric networks. It reads scholarly export formats from Scopus, Web of
Science, OpenAlex, BibTeX, RIS, Lens.org, Dimensions, and Crossref; converts
multi-valued fields such as authors, references, keywords, countries, and
affiliations into sparse incidence matrices; returns edge lists for
co-authorship, co-citation, bibliographic coupling, keyword co-occurrence,
direct citation, historiograph, and custom co-occurrence analyses; and exports
to igraph, tidygraph, cograph, Gephi CSV, GraphML, and sparse matrix formats.
Beyond construction, bibnets supports similarity and dissimilarity
normalizations (association strength, cosine, Jaccard, inclusion, equivalence,
and raw counts); counting methods including fractional, paper, and
position-aware authorship credit (harmonic, arithmetic, geometric, adaptive
geometric, golden-ratio, first-author, last-author, first-last, custom);
attention-style position weights (lead, last, proximity, circular) for
author, keyword, country, and institution networks; temporal network
construction with fixed, sliding, and cumulative time windows; disparity-filter
backbone extraction for multiscale weighted networks; Garfield-style
historiograph construction over the most locally cited documents; local
citation scoring; and threshold and top-n pruning utilities.
author_network(), document_network(), reference_network(),
keyword_network(), source_network(), institution_network(),
country_network(), historiograph(), and conetwork().attention = "lead", "last",
"proximity", or "circular" for author, keyword, country, and institution
networks.authors, keywords, references, journal, countries,
affiliations) plus sep, references_sep, and strip_quotes, so a plain
data frame with its own column names and delimiter works in one call — no
reader and no pre-splitting required.backbone(), prune(), filter_top(),
to_gephi(), to_graphml(), to_igraph(), to_tbl_graph(),
to_matrix(), and to_cograph().temporal_network(),
local_citations(), and historiograph().parse_names() reorders and splits author names
(Last, First, SURNAME Initials, or First Last) into first / last /
particle / suffix components — useful for normalizing labels before
building author networks.bibnets_network edge
list with from, to, weight, and count columns.From CRAN:
install.packages("bibnets")
Development version from GitHub:
# install.packages("remotes")
# remotes::install_github("mohsaqr/bibnets")
install.packages("bibnets", repos = c('https://mohsaqr.r-universe.dev', 'https://cloud.r-project.org'))
library(bibnets)
# Build straight from your own data frame — name the column and delimiter:
authors <- author_network(my_papers, authors = "Author Names", sep = ",")
# Or read a scholarly export (format auto-detected), then build:
data <- read_biblio("scopus.csv")
authors <- author_network(data, type = "collaboration")
# Standard edge list: from, to, weight, count
head(authors)
summary(authors)
The edge list separates two quantities:
count: the raw binary co-occurrence count for the pair.weight: the analysis weight after counting and optional similarity
normalization.With similarity = "none" and counting = "full", weight and count are
usually the same. Once fractional counting or similarity normalization is used,
they intentionally diverge.
Use read_biblio() when you have a file, a vector of files, or a directory:
data <- read_biblio("scopus_export.csv")
data <- read_biblio(c("wos_1.txt", "wos_2.txt"))
data <- read_biblio("exports/")
Use a format-specific reader when the source is already known:
scopus <- read_scopus("scopus.csv")
wos <- read_wos("savedrecs.txt")
oa <- read_openalex_csv("openalex_works.csv")
dim <- read_dimensions("dimensions.csv")
lens <- read_lens("lens.csv")
For custom CSV files, map each source column onto a standard field by name
(authors, keywords, references, countries, affiliations, journal):
data <- read_biblio(
"my_data.csv",
id = "paper_id",
authors = "Authors",
keywords = "Keywords",
sep = ";"
)
All readers try to return the same core columns: id, title, year,
journal, doi, cited_by_count, abstract, type, authors,
references, and keywords. Source-specific extras such as countries,
affiliations, index_keywords, and keywords_plus are preserved when
available.
When data is already a plain data frame or CSV with its own column names and
delimiter, you do not have to coerce it into the standard schema first. Every
network builder accepts a column argument named after the entity it builds
(authors, keywords, references, journal, countries, affiliations)
plus a sep to split a delimited character column. The builder splits,
normalizes, and projects in a single call:
papers <- data.frame(
`Author Names`= c("Smith J, Doe A, Lee K", "Smith J, Lee K",
"Doe A, Lee K", "Smith J, Doe A"),
Tags = c("ml, ai", "ml, nlp", "ai, nlp", "ml, ai"),
check.names = FALSE
)
# Point the builder at the column and give it the delimiter — no renaming.
author_network(papers, authors = "Author Names", sep = ",")
keyword_network(papers, keywords = "Tags", sep = ",")
There is no document-identifier column in that example, and none is needed: the
builders use an existing id column when present and otherwise fall back to row
numbers (each row is treated as one document). To use a differently-named
identifier column, name it with id:
papers$paper_id <- c("A1", "A2", "A3", "A4")
author_network(papers, authors = "Author Names", sep = ",", id = "paper_id")
sep is any literal delimiter, so BibTeX-style " and " or pipe-delimited
exports work too:
author_network(bib, authors = "creators", sep = " and ")
country_network(data, countries = "Country List", sep = "|")
The defaults match the standard schema, so calls on data already in that schema need no extra arguments:
| Builder | Column argument (default) |
|---|---|
author_network() |
authors = "authors" |
keyword_network() |
keywords = "keywords" |
reference_network() |
references = "references" |
document_network() |
references = "references" |
source_network() |
journal = "journal" |
country_network() |
countries = "countries" |
institution_network() |
affiliations = "affiliations" |
A few related controls:
id — the document-identifier column (the rows of the works × entities
matrix). id = NULL (default) uses an existing id column if present and
otherwise numbers the rows; id = "paper_id" points at any column. Two
entities are linked when they share the same id (the same document).references_sep — coupling builders (author_network, source_network,
country_network, institution_network) split the references column with its
own delimiter (default ";"), independent of sep. Reference strings often
contain internal commas ("Smith J, 2020, Journal"), so the two delimiters
are kept separate.strip_quotes (default TRUE) — surrounding quote characters ("Alice",
or the CSV doubled form ""Alice"") are removed so a quoted label and its
bare form collapse to one node. Internal apostrophes (O'Brien) are left
alone. Set strip_quotes = FALSE to keep the quotes.sep does not actually split the column
and the values still contain a structural delimiter (";", "|", or a tab),
the builder warns instead of silently treating each whole cell as one entity.
The check is quiet for commas and " and ", which appear inside valid single
labels.The
field =argument ofkeyword_network()is deprecated in favor ofkeywords =; the old argument still works (with a warning).
See the "Custom columns and separators" section of
vignette("reading-data", package = "bibnets") for the full treatment.
edges <- author_network(data, type = "collaboration")
Two authors are linked when they appear on the same paper. Use counting to
change how each paper contributes:
author_network(data, type = "collaboration", counting = "full")
author_network(data, type = "collaboration", counting = "fractional")
author_network(data, type = "collaboration", counting = "harmonic")
author_network(data, type = "collaboration", counting = "first_last")
data can be a reader result or a plain data frame — point the builder
straight at your own columns, no reader needed:
author_network(my_df, authors = "Author Names", id = "paper_id", sep = ",")
Use attention instead of counting when the goal is position-based
weighting independent of the standard counting families. The same option is
available on keyword_network(), country_network(), and
institution_network():
author_network(data, attention = "lead") # first author dominant
author_network(data, attention = "last") # last author dominant
author_network(data, attention = "proximity") # middle authors weighted most
author_network(data, attention = "circular") # first and last upweighted
attention and counting are mutually exclusive: when attention is
non-NULL, the network is built directly from positional weights and the
type/counting arguments are ignored.
refs <- reference_network(data, type = "co_citation", min_occur = 2)
Two references are linked when they are cited together by the same paper. This
is a column-mode projection of the papers x references matrix.
coupling <- document_network(data, type = "coupling", similarity = "cosine")
direct <- document_network(data, type = "citation")
Coupling links two papers when they cite the same references. Direct citation returns directed within-corpus citation edges from citing paper to cited paper.
keyword_network(data, keywords = "keywords")
source_network(data, type = "coupling", min_occur = 2)
country_network(data, type = "collaboration", counting = "fractional")
institution_network(data, type = "collaboration", counting = "fractional")
Entity labels are trimmed and uppercased before matrix construction so that minor casing differences do not create separate nodes.
conetwork() is useful when a dedicated wrapper is not needed:
conetwork(data, "keywords")
conetwork(data, "authors", by = "keywords")
conetwork(data, "journal", by = "references", similarity = "cosine")
With one field, entities are linked when they co-occur in the same paper. With
by, entities are linked through shared values of another field.
Counting controls how each paper contributes to edge weights. Similarity normalization controls how pair-level totals are rescaled after projection.
| Method | Main use | Interpretation |
|---|---|---|
"full" |
all networks | Each observed co-occurrence contributes 1. |
"fractional" |
all networks | Contribution is scaled by list size so large teams or long reference lists do not dominate. |
"paper" |
co-occurrence networks | Each paper contributes a fixed total amount spread across pairs. |
"strength" |
coupling networks | Uses reference-frequency weighting and row-list-size normalization for coupling-strength weighting. |
"harmonic" |
author collaboration | Authorship credit decreases by harmonic rank and sums to 1 per paper. |
"arithmetic" |
author collaboration | Authorship credit decreases linearly by position. |
"geometric" |
author collaboration | Authorship credit decays geometrically by position. |
"adaptive_geometric" |
author collaboration | Geometric decay adapts to the number of authors. |
"golden" |
author collaboration | Geometric decay based on the golden ratio. |
"first" / "last"
|
author collaboration | Only the first or last author receives credit. |
"first_last" |
author collaboration | First and last authors are upweighted. |
"position_weighted" |
author collaboration | User-supplied position weights. |
Similarity options:
keyword_network(data, similarity = "association")
keyword_network(data, similarity = "cosine")
keyword_network(data, similarity = "jaccard")
keyword_network(data, similarity = "inclusion")
keyword_network(data, similarity = "equivalence")
Association strength is often useful for co-occurrence data because it compares the observed co-occurrence against the product of the two marginal frequencies. Cosine normalization is often easier to interpret for coupling because it scales shared references by the geometric mean of the two reference-list lengths.
edges <- author_network(data, "collaboration")
strong_edges <- prune(edges, threshold = 3)
local_top <- prune(edges, top_n = 5)
top_nodes <- filter_top(edges, n = 50)
backbone_net <- backbone(edges, alpha = 0.05)
Use:
prune(threshold = x) for an absolute edge-weight cutoff.prune(top_n = k) to retain locally strong edges for each node.filter_top(n = k) to keep only the most connected nodes.backbone(alpha = x) to apply the disparity filter for multiscale
weighted networks.temporal_network(data, keyword_network, window = 3)
temporal_network(data, author_network, "collaboration",
window = 2, strategy = "sliding")
lcs <- local_citations(data)
h <- historiograph(data, n = 30)
temporal_network() supports fixed, sliding, and cumulative windows. If a
window cannot be built, it warns with the window label instead of silently
dismissing the failure.
local_citations() counts within-corpus citations. historiograph() then
builds a directed network among the top locally cited documents.
to_matrix(edges)
to_gephi(edges)
to_graphml(edges, file = "network.graphml")
if (requireNamespace("igraph", quietly = TRUE)) {
g <- to_igraph(edges)
}
Optional converters are guarded by requireNamespace(), so packages such as
igraph, tidygraph, and cograph are not required unless their output
formats are requested.
The same author can appear under different spellings across sources
("Saqr, Mohammed", "SAQR M", "Mohammed Saqr"). parse_names() reorders
and splits names so they can be normalized to a single label before building
a network — node identity is fixed at build time, so merging spellings
afterward is not possible.
parse_names(c("Saqr, Mohammed", "WANG Y", "Mohammed Saqr"))
# Normalize an authors list-column before author_network()
data$authors <- lapply(data$authors, parse_names, format = "last_initials")
It recognizes three conventions — Last, First (comma), SURNAME Initials
(Scopus/bibnets), and First Last — and returns the parsed components in a
"parts" attribute. It is a standalone utility: no reader or builder calls it.
Three vignettes ship with the package:
vignette("bibnets") — end-to-end workflow: builders, counting and
similarity options, attention weighting, network reduction, temporal
windows, historiograph, and exports.vignette("reading-data", package = "bibnets") — readers for each
supported source (Scopus, Web of Science, OpenAlex JSON and flat CSV,
BibTeX, RIS, Lens, Dimensions, Crossref), the standard schema, generic
CSV input, custom columns and separators, and multi-source merging.vignette("parsing-author-names", package = "bibnets") — parse_names():
name conventions, output styles, the "parts" attribute, and the
normalize-before-building workflow.MIT