| Title: | Reproducible, Question-Driven, Embedding-Aware Science Mapping |
|---|---|
| Description: | A comprehensive toolkit for bibliometric and scientometric analysis, designed as a complement to the foundational 'bibliometrix' package (Aria & Cuccurullo, 2017, <doi:10.1016/j.joi.2017.08.007>). Provides unified ingestion from major bibliographic sources including 'OpenAlex', 'Crossref', 'Semantic Scholar', 'PubMed', 'arXiv', 'bioRxiv', 'ORCID', 'Overton', 'Unpaywall', 'Scopus', and 'Web of Science' exports; classical science mapping (co-citation, bibliographic coupling, co-authorship, co-word networks); modern citation indicators (CD index, Relative Citation Ratio, field-normalized scores, Uzzi novelty); embedding-based research cluster discovery; structured research-question objects with optional LLM-grounded screening; author trajectory analysis; equity and representation auditing with explicit confidence and method reporting; reproducible-by-construction corpus certificates enabling exact re-derivation of a corpus; retrieval-grounded conversational corpus exploration; and a comprehensive 'shiny' application with publication-ready figure and table export. Output is fully 'tidyverse'-compatible with tibble-based corpora, 'viridis'-themed 'ggplot2' visualizations, and 'tidygraph' network objects. |
| Authors: | R. Heller [aut, cre] (ORCID: <https://orcid.org/0000-0001-8006-9742>) |
| Maintainer: | R. Heller <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.4.0.9000 |
| Built: | 2026-11-04 22:07:13 UTC |
| Source: | https://github.com/CTTIR/scimapR |
Generic function to convert various data types to an sm_corpus object.
as_sm_corpus(x, ...) ## S3 method for class 'sm_corpus' as_sm_corpus(x, ...) ## S3 method for class 'data.frame' as_sm_corpus(x, source_label = "data.frame", ...) ## S3 method for class 'list' as_sm_corpus(x, ...) ## S3 method for class 'bibliometrixDB' as_sm_corpus(x, source_label = "bibliometrix-import", ...)as_sm_corpus(x, ...) ## S3 method for class 'sm_corpus' as_sm_corpus(x, ...) ## S3 method for class 'data.frame' as_sm_corpus(x, source_label = "data.frame", ...) ## S3 method for class 'list' as_sm_corpus(x, ...) ## S3 method for class 'bibliometrixDB' as_sm_corpus(x, source_label = "bibliometrix-import", ...)
x |
An object to coerce. |
... |
Additional arguments passed to methods. |
source_label |
Label for provenance tracking. For a bibliometrix
data frame, rows with |
An sm_corpus object.
as_sm_corpus() reads a data frame made by bibliometrix::convert2df()
or sm_to_bibliometrix() (class bibliometrixDB), and the file readers
with engine = "bibliometrix" use the same conversion:
works from TI, AB (TI_raw and AB_raw where present, which
keep the case of the file), PY, DT, TC, LA, DI and PM;
authors and authorships from AU and AF, with one authorship
row per author and address: the names in the brackets of C1
(C1raw where present) link authors to addresses. An address
without names, as in older records, belongs to every author of the
work; one whose names designate none of the authors belongs to the
author of a single-author work and else to nobody. Authors are told
apart by the full names of AF; an entry whose given names are only
initials, or that has no AF, belongs to the author with that
surname and those initials when there is just one such author with a
full name. RP gives is_corresponding: the authors it names before
"(corresponding author)" or "(reprint author)", in any case, are
TRUE on all their rows and the other authors of the work FALSE.
Namesakes (the same surname and initials) are told apart by the
address that RP gives with the name, and stay NA when it does not
tell them apart; a work without RP, or whose RP designates none
of its authors for certain, gets NA (unknown);
sources from SO, SN, EI, J9 and JI (issn_l, issn,
abbreviation, iso_abbreviation);
references from CR, with the DOI of each item that has one;
concepts from DE (DE_raw where present), ID, SC and WC, in
the vocabularies author-keywords, the indexer vocabulary of the
source, wos-subject-category and wos-category. ID is read as
keywords-plus for Web of Science data (DB = "ISI"), mesh for
PubMed, Dimensions, Lens and Cochrane, author-keywords for BibTeX
and RIS files (where bibliometrix stores the keywords of the file in
ID) and index-keywords otherwise;
provenance with UT as source_id_external.
Other corpus:
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
corpus <- sm_example_corpus() as_sm_corpus(corpus) as_sm_corpus(corpus$works)corpus <- sm_example_corpus() as_sm_corpus(corpus) as_sm_corpus(corpus$works)
Automatically generate a suitable plot for an sm_corpus based on the
requested type.
## S3 method for class 'sm_corpus' autoplot( object, type = c("production", "landscape", "thematic", "collab", "equity", "top", "lotka", "bradford"), ... )## S3 method for class 'sm_corpus' autoplot( object, type = c("production", "landscape", "thematic", "collab", "equity", "top", "lotka", "bradford"), ... )
object |
An |
type |
Plot type. |
... |
Additional arguments passed to the plot function. |
A ggplot object.
Other plots:
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() ggplot2::autoplot(corpus, type = "production")corpus <- sm_example_corpus() ggplot2::autoplot(corpus, type = "production")
Test if an object is an sm_corpus
is_sm_corpus(x)is_sm_corpus(x)
x |
An object to test. |
TRUE if x is an sm_corpus, FALSE otherwise.
Other corpus:
as_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
corpus <- sm_example_corpus() is_sm_corpus(corpus)corpus <- sm_example_corpus() is_sm_corpus(corpus)
Test if an object is an sm_question
is_sm_question(x)is_sm_question(x)
x |
An object to test. |
Logical.
Other question:
sm_corpus_for_question(),
sm_question(),
sm_screen_against_question(),
sm_screen_regex(),
sm_screen_summary()
Test if an object is an sm_trajectory
is_sm_trajectory(x)is_sm_trajectory(x)
x |
An object to test. |
Logical.
Other trajectory:
sm_author_trajectory(),
sm_plot_collab_turnover(),
sm_plot_topic_pivots(),
sm_plot_trajectory(),
sm_researcher_profile(),
sm_trajectory
is_sm_trajectory(1)is_sm_trajectory(1)
Runs all available equity audits (geographic, gender, funding, OA status)
on a corpus and returns a combined summary. Individual audit results can
be passed as ... arguments to avoid re-computation; any missing audits
are run with default parameters, except that the gender audit uses
method = "manual" and audits that send web requests run only when
online = TRUE.
## S3 method for class 'sm_audit_summary' print(x, ...) sm_audit_summary(corpus, ..., online = FALSE, call = rlang::caller_env())## S3 method for class 'sm_audit_summary' print(x, ...) sm_audit_summary(corpus, ..., online = FALSE, call = rlang::caller_env())
x |
An audit object to print. |
... |
Optional pre-computed audit objects (e.g., |
corpus |
An |
online |
Logical. Whether audits that send web requests may run.
Only the funding audit does: |
call |
Caller environment for error reporting. |
An sm_audit_summary S3 object containing:
An sm_audit_geographic result.
An sm_audit_gender result.
An sm_audit_funding result. A funding audit that was
not run has no funders, source = "none" and coverage = NA.
An sm_audit_oa result.
A tibble with one row per audit: audit, coverage
(NA for an audit that was not run) and top_finding.
Other audit:
sm_audit_funding(),
sm_audit_gender(),
sm_audit_geographic(),
sm_audit_oa()
corpus <- sm_example_corpus(n_works = 50, n_authors = 20) # The funding audit sends batched Crossref requests, so it is not run # unless online = TRUE. summary_audit <- sm_audit_summary(corpus) summary_audit$overview # A precomputed audit is used as it is. This funding audit sends no # request because its copy of the corpus has no DOIs. no_doi <- corpus no_doi$works$doi <- NA_character_ funding <- sm_audit_funding(no_doi) summary_audit <- sm_audit_summary(corpus, funding) print(summary_audit)corpus <- sm_example_corpus(n_works = 50, n_authors = 20) # The funding audit sends batched Crossref requests, so it is not run # unless online = TRUE. summary_audit <- sm_audit_summary(corpus) summary_audit$overview # A precomputed audit is used as it is. This funding audit sends no # request because its copy of the corpus has no DOIs. no_doi <- corpus no_doi$works$doi <- NA_character_ funding <- sm_audit_funding(no_doi) summary_audit <- sm_audit_summary(corpus, funding) print(summary_audit)
A small, documented, user-overridable dictionary of institution name
variants used by sm_affiliation_match(). It covers common military /
medical institution variants (including German synonyms) plus email-domain
fallbacks, and is designed to be extended by appending rows.
sm_affiliation_dictsm_affiliation_dict
A tibble with three columns:
Canonical institution name (character).
A case-insensitive Perl-compatible regular expression matched against affiliation strings (character).
Optional email domain used by the email-domain
fallback, or NA (character).
Patterns are case-insensitive PCRE with Unicode-aware \w, \b, \d
and \s: sm_affiliation_match() matches them in Unicode mode by
default, so a row added for a German, French or Scandinavian name can use
these classes next to non-ASCII letters (see the Details of
sm_affiliation_match(), including its unicode argument). In that
mode the shipped row \bnato\b no longer matches "NATO" directly
followed by an umlaut, as in a German compound word. The first pattern
that matches wins, in row order, so add specific rows before generic
ones.
The rows for "Bundeswehr Hospital" match its German name, its
abbreviation, or "hospital" after "Bundeswehr" or after "German Armed
Forces", "German Army" or "German Military", with at most one word in
between. A bare "military hospital", "army hospital" or "armed forces
hospital", which names hospitals of many countries, stays unmatched; up
to scimapR 0.4.0 these phrases matched "Bundeswehr Hospital" wherever
they appeared. The dictionary is a starting point: for an audit of your
own institutions, pass a dictionary of them to sm_affiliation_match().
sm_affiliation_match(), sm_affiliation_unmatched()
sm_affiliation_dictsm_affiliation_dict
A tested, extensible institution matcher for the authorships table. It
tags each authorship with the institution it belongs to, using a dictionary
of name variants (multilingual and synonym-aware) and an optional
email-domain fallback for records whose affiliation string is missing.
Because the matcher operates per authorship row, it naturally handles secondary / multiple affiliations per author (each authorship row is matched independently).
sm_affiliation_match( corpus, patterns = sm_affiliation_dict, fields = NULL, email_domain_fallback = TRUE, postcode_signal = FALSE, unicode = TRUE, call = rlang::caller_env() )sm_affiliation_match( corpus, patterns = sm_affiliation_dict, fields = NULL, email_domain_fallback = TRUE, postcode_signal = FALSE, unicode = TRUE, call = rlang::caller_env() )
corpus |
An |
patterns |
A dictionary of institution name variants. Either:
Defaults to sm_affiliation_dict. |
fields |
Character vector of |
email_domain_fallback |
Logical; if |
postcode_signal |
Logical (default |
unicode |
Logical (default |
call |
Caller environment for error reporting. |
Dictionary patterns are case-insensitive Perl-compatible regular
expressions (PCRE) with Unicode-aware \w, \b, \d and \s, matched
against the affiliation text of each authorship. The rows are tried in
dictionary order and the first pattern that matches wins, so list specific
patterns before generic ones. sm_affiliation_unmatched() lists the most
frequent affiliation strings that nothing matched.
In Unicode mode (unicode = TRUE, the default) each pattern is prefixed
with the PCRE item (*UCP) unless its leading items already include it.
Umlauts, accented letters and the sharp s are then word characters: \w
consumes them, \b finds the edge of a word that starts or ends with one,
and POSIX classes such as [[:alpha:]] are Unicode-aware too. In ASCII
mode (unicode = FALSE) every non-ASCII letter is a non-word character,
like punctuation: a pattern such as sanit\w+wesens cannot match the
German word spelled with an a-umlaut, and \b finds no edge between a
space and a word that starts with an umlaut. Unicode mode can therefore
also lose matches. A pattern that used \W, \S, \D or \B to stand
for a non-ASCII letter no longer matches it, and \b no longer finds an
edge between an ASCII letter and a non-ASCII letter next to it: the
shipped pattern \bnato\b does not match "NATO" directly followed by an
umlaut, as in a German compound word. To keep ASCII semantics in one
place, write the class out, for example [A-Za-z0-9_] instead of \w.
unicode = FALSE gives the matches of scimapR 0.4.0.
sm_attribute_institution() calls sm_affiliation_match() with its
defaults, and so matches in Unicode mode, when the corpus has no
institution_match column; running sm_affiliation_match(unicode = FALSE)
first gives its earlier results. sm_count(level = "institution") no
longer matches affiliations itself: it counts each authorship by its
institution label, institution_match first, and by its raw affiliation
string when it has none (see sm_count()).
The optional postcode column (used with postcode_signal = TRUE) is
matched case-sensitively as a whole word, in the same mode: it is wrapped
in \b, any run of whitespace in it matches any amount of whitespace
(including none), and it is used as a regular expression without escaping.
To extend the dictionary, append rows to sm_affiliation_dict (or build your
own data frame with the same columns) and pass it as patterns. For example
rbind(sm_affiliation_dict, tibble::tibble(institution = "My Uni", pattern = "my university", email_domain = "myuni.edu")).
The corpus with its authorships table gaining (or having
updated) four columns:
Canonical institution name, or NA.
"pattern", "email_domain", "postcode", or
"none".
The signal that fired: "name_token",
"email_domain", "postcode", or "none". Precedence (highest
first): name token, email domain, postcode.
The actual substring / domain / postcode that
triggered the match (an audit trail), or NA.
Type-stable: a corpus with no authorships is returned unchanged with the
four columns present and 0 rows. See sm_affiliation_summary() for a tidy
breakdown.
sm_attribute_institution(), sm_affiliation_dict,
sm_affiliation_unmatched()
Other affiliation:
sm_affiliation_methods(),
sm_affiliation_signals(),
sm_affiliation_summary(),
sm_affiliation_unmatched(),
sm_attribute_institution()
corpus <- sm_example_corpus(n_works = 5, n_authors = 5) corpus$authorships$raw_affiliation[1] <- "Bundeswehrkrankenhaus Berlin" matched <- sm_affiliation_match(corpus) matched$authorships$institution_match[1] # In Unicode mode \w consumes the umlaut of a German unit name corpus$authorships$raw_affiliation[2] <- "Zentrales Institut des Sanit\u00e4tswesens Nordstadt" dict <- list("Central Medical Institute" = "sanit\\w+wesens") matched <- sm_affiliation_match(corpus, patterns = dict) matched$authorships$institution_match[2] ascii <- sm_affiliation_match(corpus, patterns = dict, unicode = FALSE) ascii$authorships$institution_match[2]corpus <- sm_example_corpus(n_works = 5, n_authors = 5) corpus$authorships$raw_affiliation[1] <- "Bundeswehrkrankenhaus Berlin" matched <- sm_affiliation_match(corpus) matched$authorships$institution_match[1] # In Unicode mode \w consumes the umlaut of a German unit name corpus$authorships$raw_affiliation[2] <- "Zentrales Institut des Sanit\u00e4tswesens Nordstadt" dict <- list("Central Medical Institute" = "sanit\\w+wesens") matched <- sm_affiliation_match(corpus, patterns = dict) matched$authorships$institution_match[2] ascii <- sm_affiliation_match(corpus, patterns = dict, unicode = FALSE) ascii$authorships$institution_match[2]
The set of values the match_method column of sm_affiliation_match() can
take.
sm_affiliation_methods(describe = FALSE)sm_affiliation_methods(describe = FALSE)
describe |
Logical; if |
A character vector or a tibble.
Other affiliation:
sm_affiliation_match(),
sm_affiliation_signals(),
sm_affiliation_summary(),
sm_affiliation_unmatched(),
sm_attribute_institution()
sm_affiliation_methods()sm_affiliation_methods()
The ordered set of values the match_signal column of
sm_affiliation_match() / sm_affiliation_summary() can take. Using this
helper (rather than hard-coding strings) means downstream filtering — e.g.
separating institution-name matches from email-domain matches — cannot
drift if the levels change.
sm_affiliation_signals(describe = FALSE)sm_affiliation_signals(describe = FALSE)
describe |
Logical; if |
A character vector (ordered, highest matching priority first) or a
tibble of level/description.
sm_affiliation_match(), sm_affiliation_methods()
Other affiliation:
sm_affiliation_match(),
sm_affiliation_methods(),
sm_affiliation_summary(),
sm_affiliation_unmatched(),
sm_attribute_institution()
sm_affiliation_signals() sm_affiliation_signals(describe = TRUE)sm_affiliation_signals() sm_affiliation_signals(describe = TRUE)
Tidy summary of the matches produced by sm_affiliation_match(), mirroring
the audit-style summaries elsewhere in the package: counts of works and
authorships flagged, broken down by institution and by match_signal.
sm_affiliation_summary(corpus, call = rlang::caller_env())sm_affiliation_summary(corpus, call = rlang::caller_env())
corpus |
An |
call |
Caller environment for error reporting. |
A tibble with columns institution, match_signal (a factor; see
sm_affiliation_signals()), n_authorships, n_works, and
example_evidence (a representative matched-evidence string for that
institution x signal — the audit trail), sorted by n_authorships
descending. Type-stable: a 0-row tibble (with a warning) when no matches
are present.
Other affiliation:
sm_affiliation_match(),
sm_affiliation_methods(),
sm_affiliation_signals(),
sm_affiliation_unmatched(),
sm_attribute_institution()
corpus <- sm_example_corpus(n_works = 5, n_authors = 5) corpus$authorships$raw_affiliation[1] <- "Bundeswehrkrankenhaus Berlin" corpus <- sm_affiliation_match(corpus) sm_affiliation_summary(corpus)corpus <- sm_example_corpus(n_works = 5, n_authors = 5) corpus$authorships$raw_affiliation[1] <- "Bundeswehrkrankenhaus Berlin" corpus <- sm_affiliation_match(corpus) sm_affiliation_summary(corpus)
Audits an affiliation dictionary. After sm_affiliation_match(), it lists
the most frequent raw_affiliation strings of the authorships that no
signal (name pattern, email domain or postcode) assigned to an
institution, so the dictionary can be extended where that matters most.
sm_affiliation_unmatched(corpus, n = 25L, call = rlang::caller_env())sm_affiliation_unmatched(corpus, n = 25L, call = rlang::caller_env())
corpus |
An |
n |
Maximum number of strings to return: a positive whole number, or
|
call |
Caller environment for error reporting. |
A tibble with columns raw_affiliation (character),
n_authorships (integer: unmatched authorships with that string) and
n_works (integer: distinct works among them), at most n rows,
sorted by n_authorships descending and then by raw_affiliation in
code-point order. Authorships whose raw_affiliation is NA or blank
are not listed. Type-stable: a 0-row tibble when every affiliation
matched, and a 0-row tibble with a warning when the corpus has not been
matched.
sm_affiliation_match(), sm_affiliation_summary()
Other affiliation:
sm_affiliation_match(),
sm_affiliation_methods(),
sm_affiliation_signals(),
sm_affiliation_summary(),
sm_attribute_institution()
fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) dict <- readr::read_csv( sm_example_files("example_realuse_affiliation_dict.csv"), col_types = "ccc" ) matched <- sm_affiliation_match(fix, dict) sm_affiliation_unmatched(matched, n = 5)fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) dict <- readr::read_csv( sm_example_files("example_realuse_affiliation_dict.csv"), col_types = "ccc" ) matched <- sm_affiliation_match(fix, dict) sm_affiliation_unmatched(matched, n = 5)
Rolls institution matches (from sm_affiliation_match()) up to a controlled
vocabulary, writing a normalised institution_id and institution_name
onto the authorships table. Supports a ROR-backed vocabulary (via a
user-supplied offline ROR table) or a "custom" vocabulary derived directly
from the matched institution names.
sm_attribute_institution( corpus, vocabulary = c("ror", "custom"), ror_table = NULL, call = rlang::caller_env() )sm_attribute_institution( corpus, vocabulary = c("ror", "custom"), ror_table = NULL, call = rlang::caller_env() )
corpus |
An |
vocabulary |
|
ror_table |
For |
call |
Caller environment for error reporting. |
The institution_match column stays as it is.
sm_count(level = "institution") labels each authorship by
institution_match before institution_name and institution_id, so
the roll-up to the vocabulary reaches the counts only for authorships
without a match, and the "affiliation" breakdown of
sm_coverage_audit() uses institution_name only when no authorship has
a match. Up to scimapR 0.4.0, sm_count() counted by institution_id
whenever any authorship had one. To count or break down by the
vocabulary, put its names first, for example:
a <- corpus$authorships corpus$authorships$institution_match <- dplyr::coalesce(a$institution_name, a$institution_match)
The ids written to matched rows are ROR ids or CUST: ids, which join
corpus$institutions only where that table lists them, so functions
that read the institutions table for these rows (such as
sm_audit_geographic(by = "region")) find no entry for them. Unmatched
rows keep their earlier ids and so their entries. Up to scimapR 0.4.0,
unmatched rows lost their institution_id.
The corpus with institution_id and institution_name set on
the authorships rows that match the vocabulary (for "ror",
institution_id holds the ROR id; for "custom", CUST: and the
matched name). Unmatched rows keep the institution_id and
institution_name they had, such as the OpenAlex institution ids of
a fetched corpus, or NA – the function never errors on unmatched
affiliations. Type-stable.
Other affiliation:
sm_affiliation_match(),
sm_affiliation_methods(),
sm_affiliation_signals(),
sm_affiliation_summary(),
sm_affiliation_unmatched()
ror <- utils::read.csv( system.file("extdata", "example_ror.csv", package = "scimapR"), stringsAsFactors = FALSE ) corpus <- sm_example_corpus(n_works = 5, n_authors = 5) corpus$authorships$raw_affiliation[1] <- "Charite Universitatsmedizin Berlin" corpus <- sm_affiliation_match(corpus) corpus <- sm_attribute_institution(corpus, vocabulary = "ror", ror_table = ror) corpus$authorships$institution_name[1]ror <- utils::read.csv( system.file("extdata", "example_ror.csv", package = "scimapR"), stringsAsFactors = FALSE ) corpus <- sm_example_corpus(n_works = 5, n_authors = 5) corpus$authorships$raw_affiliation[1] <- "Charite Universitatsmedizin Berlin" corpus <- sm_affiliation_match(corpus) corpus <- sm_attribute_institution(corpus, vocabulary = "ror", ror_table = ror) corpus$authorships$institution_name[1]
Audits the distribution of funding sources across works in a corpus.
Funding metadata is extracted from either Crossref funder fields or
OpenAlex funders and awards fields (also accepting legacy grants),
depending on the source parameter.
The result includes funder counts, the proportion of works with known funding, and a concentration analysis.
sm_audit_funding( corpus, source = c("crossref", "openalex"), api_key = Sys.getenv("OPENALEX_API_KEY"), mailto = Sys.getenv("SCIMAPR_MAILTO"), call = rlang::caller_env() ) ## S3 method for class 'sm_audit_funding' print(x, ...)sm_audit_funding( corpus, source = c("crossref", "openalex"), api_key = Sys.getenv("OPENALEX_API_KEY"), mailto = Sys.getenv("SCIMAPR_MAILTO"), call = rlang::caller_env() ) ## S3 method for class 'sm_audit_funding' print(x, ...)
corpus |
An |
source |
Character. Where to look for funding data: |
api_key |
OpenAlex API key, sent in the authorization header. |
mailto |
Contact address for Crossref requests. |
call |
Caller environment for error reporting. |
x |
An audit object to print. |
... |
Ignored. |
An sm_audit_funding S3 object containing:
Tibble with columns funder_name, funder_doi,
n_works, pct (percentage of checked works).
Proportion of checked works with at least one funder;
NA if none could be checked, and 0 for an empty corpus.
Total number of works in corpus.
Number of works with funding data.
Works returned by the source.
Works whose request failed; reported in a warning.
Works without an identifier for the chosen source.
The data source used.
Other audit:
print.sm_audit_summary(),
sm_audit_gender(),
sm_audit_geographic(),
sm_audit_oa()
# Works without a DOI need no Crossref lookup, so this runs offline and # returns an empty audit. corpus <- sm_example_corpus(n_works = 20, n_authors = 10) corpus$works$doi <- NA_character_ funding <- sm_audit_funding(corpus, source = "crossref") print(funding) # Live lookup: batched Crossref requests (the example DOIs are invented, # so no funders are found). corpus <- sm_example_corpus(n_works = 3, n_authors = 3) funding <- sm_audit_funding(corpus, source = "crossref") print(funding)# Works without a DOI need no Crossref lookup, so this runs offline and # returns an empty audit. corpus <- sm_example_corpus(n_works = 20, n_authors = 10) corpus$works$doi <- NA_character_ funding <- sm_audit_funding(corpus, source = "crossref") print(funding) # Live lookup: batched Crossref requests (the example DOIs are invented, # so no funders are found). corpus <- sm_example_corpus(n_works = 3, n_authors = 3) funding <- sm_audit_funding(corpus, source = "crossref") print(funding)
Audits the inferred gender distribution of authors in a corpus. Gender is inferred from first names using the specified method. Results include the overall distribution, authorship-position breakdown, and a coverage metric.
This function infers a binary gender proxy from names, which has well-documented limitations. See the Limitations section in the printed output and the details below.
sm_audit_gender( corpus, method = c("genderize", "ssa", "manual"), api_key = Sys.getenv("GENDERIZE_API_KEY"), cache_dir = tools::R_user_dir("scimapR", "cache"), call = rlang::caller_env() ) ## S3 method for class 'sm_audit_gender' print(x, ...)sm_audit_gender( corpus, method = c("genderize", "ssa", "manual"), api_key = Sys.getenv("GENDERIZE_API_KEY"), cache_dir = tools::R_user_dir("scimapR", "cache"), call = rlang::caller_env() ) ## S3 method for class 'sm_audit_gender' print(x, ...)
corpus |
An |
method |
Character. Gender inference method:
|
api_key |
Character. API key for genderize.io. Read from the
|
cache_dir |
Character. Directory for caching API results. |
call |
Caller environment for error reporting. |
x |
An audit object to print. |
... |
Ignored. |
Known limitations of automated gender inference:
Name-based methods assign a binary gender proxy, which does not capture the full spectrum of gender identity.
Accuracy varies dramatically by cultural context: names from East Asian, South Asian, and many African cultures are poorly served by Western-trained models.
Non-binary, transgender, and gender-diverse individuals are systematically misclassified.
The method cannot account for name changes, pen names, or initialised first names.
An sm_audit_gender S3 object containing:
Tibble with columns inferred_gender, count,
pct.
Tibble breaking down gender by authorship position:
position_type ("first", "corresponding" for a corresponding
author who is not first, else "middle/last"), inferred_gender,
count and pct. Each work and author counts once, however many
address rows the author has; corresponding authors are those with
authorships$is_corresponding = TRUE (NA counts as not
corresponding).
Proportion of authors with an inferred gender.
The method used.
Number of distinct names without an API answer; these are not cached and are retried on the next run.
Summary statistics for gender confidence scores.
Other audit:
print.sm_audit_summary(),
sm_audit_funding(),
sm_audit_geographic(),
sm_audit_oa()
corpus <- sm_example_corpus() # Using manual method (no API call needed for examples): gender_audit <- sm_audit_gender(corpus, method = "manual") print(gender_audit)corpus <- sm_example_corpus() # Using manual method (no API call needed for examples): gender_audit <- sm_audit_gender(corpus, method = "manual") print(gender_audit)
Audits the geographic distribution of a corpus, tabulating the representation of countries, regions, or World Bank income tiers. The analysis can be weighted by work count, total citations, first-authorship, or corresponding authorship. Each work counts once per distinct known group, with optional fractional credit.
The result includes a Gini coefficient for concentration and a coverage metric (proportion of works with known geography).
sm_audit_geographic( corpus, by = c("country", "region", "income_tier"), weight = c("count", "citations", "first-author", "corresponding"), method = c("full", "fractional"), call = rlang::caller_env() ) ## S3 method for class 'sm_audit_geographic' print(x, ...)sm_audit_geographic( corpus, by = c("country", "region", "income_tier"), weight = c("count", "citations", "first-author", "corresponding"), method = c("full", "fractional"), call = rlang::caller_env() ) ## S3 method for class 'sm_audit_geographic' print(x, ...)
corpus |
An |
by |
Character. Grouping variable: |
weight |
Character. How to weight each work: |
method |
Counting rule: |
call |
Caller environment for error reporting. |
x |
An audit object to print. |
... |
Ignored. |
Coverage uses all corpus works as its denominator and counts works with at least one known group after the authorship filter. Missing geography is excluded from credit allocation; incomplete metadata can therefore change both coverage and the shares assigned to known groups.
An sm_audit_geographic S3 object containing:
Tibble with columns group, count, citations,
pct, credit and weighted_citations. Counts and raw citations
include each work once per group under both methods. Percentages
and the Gini use credit, or weighted citations for citation weighting.
Count percentages describe work-group pairs under full counting and
works under fractional counting.
Gini coefficient of the distribution.
Proportion of works with at least one known geography.
The grouping variable used.
The weighting method used.
The counting rule used.
x invisibly (print methods).
Other audit:
print.sm_audit_summary(),
sm_audit_funding(),
sm_audit_gender(),
sm_audit_oa()
corpus <- sm_example_corpus() geo <- sm_audit_geographic(corpus) print(geo)corpus <- sm_example_corpus() geo <- sm_audit_geographic(corpus) print(geo)
Audits the open-access (OA) status distribution of works in a corpus.
Uses the statuses of sm_oa_statuses(); every recognised status except
closed is open. Missing and unrecognised values are unknown.
The open percentage uses all works as the denominator and agrees with
sm_summary_works() to one decimal place.
The result includes counts and percentages for each OA category, as well as a coverage metric.
sm_audit_oa(corpus, call = rlang::caller_env()) ## S3 method for class 'sm_audit_oa' print(x, ...)sm_audit_oa(corpus, call = rlang::caller_env()) ## S3 method for class 'sm_audit_oa' print(x, ...)
corpus |
An |
call |
Caller environment for error reporting. |
x |
An audit object to print. |
... |
Ignored. |
An sm_audit_oa S3 object containing:
Tibble with columns oa_status, count, pct,
and logical is_open (NA for unknown statuses).
Percentage of works with an open status in
sm_oa_statuses().
Proportion of works with a known OA status.
Total number of works.
Other audit:
print.sm_audit_summary(),
sm_audit_funding(),
sm_audit_gender(),
sm_audit_geographic()
corpus <- sm_example_corpus() oa <- sm_audit_oa(corpus) print(oa)corpus <- sm_example_corpus() oa <- sm_audit_oa(corpus) print(oa)
Analyses the career trajectory of a single author within a corpus,
partitioning their publication record into n_periods career stages.
For each period, the function computes:
Career stages: dominant topics (the three concept names found in most of the period's works) and publication volume.
Topic pivots: how much the research focus shifted between periods, measured as the Jaccard distance of concept sets.
Collaborator turnover: Jaccard similarity of co-author sets across consecutive periods, plus counts of new, kept, and lost collaborators.
Emerging collaborators: co-authors who first appear in the most recent period.
H-index curve: cumulative h-index at the end of each period.
Novelty curve: average concept novelty per period, the mean number of distinct concept names per work (over the works with concepts).
Citation acceleration: mean citations per period relative to the corpus-wide mean for the same years.
sm_author_trajectory( corpus, orcid = NULL, author_id = NULL, n_periods = 5L, call = rlang::caller_env() ) ## S3 method for class 'sm_trajectory' print(x, ...) ## S3 method for class 'sm_trajectory' format(x, ...)sm_author_trajectory( corpus, orcid = NULL, author_id = NULL, n_periods = 5L, call = rlang::caller_env() ) ## S3 method for class 'sm_trajectory' print(x, ...) ## S3 method for class 'sm_trajectory' format(x, ...)
corpus |
An |
orcid |
Character. ORCID of the author to analyse, with or without
the |
author_id |
Character. Internal author ID. Either |
n_periods |
Integer. Number of career periods to divide the
publication span into. Default |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
An sm_trajectory S3 object.
print(): x invisibly.
format(): A single character string.
Other trajectory:
is_sm_trajectory(),
sm_plot_collab_turnover(),
sm_plot_topic_pivots(),
sm_plot_trajectory(),
sm_researcher_profile(),
sm_trajectory
corpus <- sm_example_corpus() # Use the first author's ID traj <- sm_author_trajectory(corpus, author_id = "A000000001") print(traj)corpus <- sm_example_corpus() # Use the first author's ID traj <- sm_author_trajectory(corpus, author_id = "A000000001") print(traj)
Combine two corpora, assigning new IDs to colliding records in the
second input and rewriting their links in every table and embedding.
This includes yearly citation counts (citations_by_year). The bound
corpus keeps the metadata of corpus1, with the local input files
(sm_register_input()) of both corpora, each file and content once.
sm_bind_corpora(corpus1, corpus2, mix_models = FALSE, verbose = TRUE)sm_bind_corpora(corpus1, corpus2, mix_models = FALSE, verbose = TRUE)
corpus1 |
An |
corpus2 |
An |
mix_models |
Logical; bind embeddings that come from different
models (or of which only one corpus records its model)? Defaults to
|
verbose |
Logical; report the number of re-keyed IDs and works whose normalised DOI also occurs in the first input? |
Work IDs shared by the inputs always get new values in corpus2.
Author, source and institution IDs are shared only when their lookup
rows agree on every common column, including missing and list values.
Other collisions get new IDs above the numbers used by either input;
external cited work IDs are reserved too. Shared DOIs remain separate
records until sm_dedupe() is called. The merged corpus is unlocked.
Embeddings are bound when both corpora have them, with the same number
of dimensions, and when they come from the same model as
metadata$embedding records it (see sm_corpus()); the bound corpus
keeps that record. Embeddings that record different models, or of
which only one corpus records its model, are not comparable: the
function stops (class scimapR_embedding_model_mismatch) unless
mix_models = TRUE, which binds them and records the model as
"mixed". Embeddings of two corpora that record no model, such as
corpora embedded by earlier versions of scimapR, are bound as before,
and the model of the bound rows stays unrecorded. When only one corpus
has embeddings, or their widths differ, the bound corpus has none and
warns with class scimapR_bind_embeddings_dropped.
An sm_corpus object.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
Combine multiple file reads and/or API fetches into a single
deduplicated sm_corpus.
sm_build_corpus(..., dedupe = TRUE, mix_models = FALSE, verbose = TRUE)sm_build_corpus(..., dedupe = TRUE, mix_models = FALSE, verbose = TRUE)
... |
One or more |
dedupe |
Logical; deduplicate by normalised DOI after combining, keeping the most complete record and its child rows? |
mix_models |
Logical; bind embeddings that come from different
models (or of which only one input records its model)? Passed to
|
verbose |
Logical; show progress messages? |
The inputs are bound in turn with sm_bind_corpora(), so its rules for
embeddings apply: embeddings that record different models in
metadata$embedding, or of which only one input records its model,
stop the function (class scimapR_embedding_model_mismatch) unless
mix_models = TRUE.
Pass every file of a split export; the IDs stay unique.
An sm_corpus object.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
c1 <- sm_example_corpus(n_works = 50, seed = 1) c2 <- sm_example_corpus(n_works = 50, seed = 2) combined <- sm_build_corpus(c1, c2, dedupe = TRUE)c1 <- sm_example_corpus(n_works = 50, seed = 1) c2 <- sm_example_corpus(n_works = 50, seed = 2) combined <- sm_build_corpus(c1, c2, dedupe = TRUE)
A corpus certificate is a self-contained YAML document that records how
a corpus was made and lets others check it: the corpus hash
(sm_hash_corpus()) and the size of its tables, the steps recorded in
its provenance (searches, file reads, enrichments and refreshes), a
summary of its screening decisions, its embeddings and their model, and
the local input files registered with sm_register_input(), each with
its SHA-256 hash.
sm_certificate() creates a certificate from a corpus.
sm_verify_certificate() compares a live corpus, and its local input
files, against a certificate to detect divergence.
sm_rebuild_from_cert() runs the recorded searches again (see
"Rebuilding a corpus"); file reads and local inputs cannot be replayed,
and verification checks them instead.
Each recorded step (queries) has a source, query, engine and
kind: "api_query" for searches of a bibliographic API, "file" for
records read from a local file (the file relative to the working
directory when it lies below it, else its name), "enrichment" for
steps such as sm_enrich_openalex_impact() that added fields to works
already in the corpus, "refresh" for sm_refresh() and "manual"
for synthetic records. The searches of sm_fetch_openalex(),
sm_fetch_pubmed(), sm_fetch_crossref() and
sm_corpus_for_question() also record their filter and n_max. The
kind comes from the provenance column kind, else (for corpora and
certificates written before kinds were recorded) from the source label,
the query and the engine (see sm_provenance()).
sm_certificate(corpus, path = NULL, call = rlang::caller_env()) sm_rebuild_from_cert(cert, verbose = TRUE, call = rlang::caller_env()) sm_verify_certificate( corpus, cert, base_dir = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_certificate' print(x, ...) ## S3 method for class 'sm_cert_verification' print(x, ...)sm_certificate(corpus, path = NULL, call = rlang::caller_env()) sm_rebuild_from_cert(cert, verbose = TRUE, call = rlang::caller_env()) sm_verify_certificate( corpus, cert, base_dir = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_certificate' print(x, ...) ## S3 method for class 'sm_cert_verification' print(x, ...)
corpus |
An |
path |
Character. Optional file path to write the certificate YAML.
If |
call |
Caller environment for error reporting. |
cert |
A certificate object (list) or path to a YAML certificate file. |
verbose |
Logical. Print progress? |
base_dir |
|
x |
An |
... |
Ignored. |
Certificates write their timestamps (created, the corpus's
build_date and last_refresh, and the registered_at of each local
input) in UTC as ISO 8601 text, for example 2026-10-17T11:45:00Z, and
read back both this form and the older one with a numeric offset.
Printing a certificate shows created in UTC as well, so a certificate
read back from YAML prints the same time as the one it was written from.
The corpus timestamps may be stored as POSIXct, a Date, numeric seconds
since 1970-01-01 UTC (as in corpora restored from tables) or ISO 8601
text. A missing or implausible value is written as NA.
embedding_info describes corpus$embeddings: its size (n_works,
n_dimensions) and, from the record in corpus$metadata$embedding
(see sm_corpus()), the model behind it: model, model_id,
base_model_id, revision, base_revision, text, prefix,
models, versions and created. method names how the embeddings
were made: "sentence-transformers" or "adapters" (sm_embed_works()),
"semantic-scholar" (sm_enrich_specter(),
sm_fetch_semantic_scholar()) or "mixed" for rows of several models;
it is "unknown" for embeddings without a record that fits them (such
as embeddings made by earlier versions of scimapR, or a matrix replaced
by hand) and "none" when the corpus has no embeddings. In these two
cases model and model_id say the same.
For sm_certificate(): an sm_certificate S3 object (list),
also written to path if provided.
For sm_rebuild_from_cert(): an sm_corpus with the works of the
replayed searches. For sm_verify_certificate(): an
sm_cert_verification S3 object, a list with pass, checks,
n_not_checked, corpus_hash (computed with the certificate's hash
version), cert_hash, cert_created and hash_version.
x invisibly (print methods).
The local_inputs block lists the files of metadata$local_inputs
(sm_register_input()) with their path, path_abs, root,
basename, sha256, bytes, role, builder and registered_at.
It is a tibble in the certificate object, with no rows when no file is
registered, and a list with one entry per file in the YAML file.
sm_verify_certificate() adds one check per file, named
input:<basename> (input:<path> when files share a name). It looks
for a file with a relative path in base_dir, then in the folder of
the certificate file (when cert is a path), then in the working
directory, and last at path_abs; a file with an absolute path is
looked for there and at path_abs. The first file found is hashed
again, its check passes when the hash is the recorded one, and the
check's location says where it was found. A file found nowhere is not
checked, and the function warns (class scimapR_cert_inputs_missing),
naming the files and the folders searched, because a verification run
from the wrong folder would otherwise pass without a word.
checks has one row per check, with the columns check, expected,
actual (for a local input the first 16 characters of the hashes, or
"missing"), pass, status and location. status (character) is
"pass", "fail" or "not checked"; pass is TRUE, FALSE, or
NA for a check that was not made. The overall pass is TRUE unless
a check failed, so it can be TRUE while n_not_checked counts checks
that were not made. The corpus hash is computed with the
hash_version the certificate records, or with version 1 for
certificates written before scimapR 0.5.0 (see sm_hash_corpus()).
sm_rebuild_from_cert() replays the "api_query" steps of OpenAlex,
PubMed and Crossref with the fetcher of their source
(sm_fetch_openalex(), sm_fetch_pubmed() or sm_fetch_crossref()),
the engine they were made with and the recorded filter and n_max.
The searches of sm_corpus_for_question() record the client package
that ran them (openalexR, rentrez or rcrossref) as their engine. A step
without n_max, as in certificates of earlier versions, fetches up to
the number of works of the certified corpus, with a message. The other
steps (file reads, enrichments, refreshes, synthetic records and
searches of other sources) and the local inputs cannot be replayed, and
a message of class scimapR_cert_not_replayed names them. When a
certificate has no search to replay but records file reads or local
inputs, the function stops instead, with an error of class
scimapR_cert_not_replayable that lists them, with the role, hash and
builder of each local input. Without a search to replay, file reads or
local inputs (only synthetic records, enrichments, refreshes or searches
of other sources, or no step at all), it returns an empty corpus.
The rebuilt corpus holds the works of the replayed searches, without
duplicate DOIs and with new work ids, and their provenance; its other
tables are empty. It shows what the recorded searches find today, so
it does not pass sm_verify_certificate(): its hash and table sizes
differ from those of the certified corpus.
Other reproducibility:
sm_cite_corpus(),
sm_diff_corpora(),
sm_hash_corpus(),
sm_provenance(),
sm_register_input(),
sm_snapshot()
corpus <- sm_example_corpus() tracker <- tempfile(fileext = ".csv") writeLines(c("doi,unit", "10.9999/example.1,Unit A"), tracker) corpus <- sm_register_input(corpus, tracker, role = "publication tracker") cert <- sm_certificate(corpus) print(cert) verification <- sm_verify_certificate(corpus, cert) print(verification) # a changed input file fails its check cat("10.9999/example.2,Unit B\n", file = tracker, append = TRUE) sm_verify_certificate(corpus, cert)$passcorpus <- sm_example_corpus() tracker <- tempfile(fileext = ".csv") writeLines(c("doi,unit", "10.9999/example.1,Unit A"), tracker) corpus <- sm_register_input(corpus, tracker, role = "publication tracker") cert <- sm_certificate(corpus) print(cert) verification <- sm_verify_certificate(corpus, cert) print(verification) # a changed input file fails its check cat("10.9999/example.2,Unit B\n", file = tracker, append = TRUE) sm_verify_certificate(corpus, cert)$pass
Ask a question of a corpus using retrieval-augmented generation (RAG). The function retrieves the most relevant works from the corpus using embeddings, TF-IDF, or a hybrid method, then sends them as context to a chat provider along with the user's question. The response includes inline citations keyed to work IDs. No hallucinated references are possible because citations are constrained to retrieved works.
sm_chat( corpus, question, provider = NULL, retrieve_n = 30L, retrieve_method = c("embedding", "tfidf", "hybrid"), cite = TRUE, max_tokens = lifecycle::deprecated(), temperature = lifecycle::deprecated(), call = rlang::caller_env() ) ## S3 method for class 'sm_chat_response' print(x, ...)sm_chat( corpus, question, provider = NULL, retrieve_n = 30L, retrieve_method = c("embedding", "tfidf", "hybrid"), cite = TRUE, max_tokens = lifecycle::deprecated(), temperature = lifecycle::deprecated(), call = rlang::caller_env() ) ## S3 method for class 'sm_chat_response' print(x, ...)
corpus |
An |
question |
Character. The user's question in natural language. |
provider |
An ellmer chat provider object (e.g., from
|
retrieve_n |
Integer. Number of works to retrieve as context.
Default |
retrieve_method |
Character. Retrieval method:
|
cite |
Logical. If |
max_tokens, temperature
|
Deprecated; these arguments never affected requests. Set response parameters on the chat object instead. |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
An sm_chat_response S3 object with fields:
Character. The chat provider's response.
Tibble of cited work IDs and their details.
Tibble of the works sent as context.
SHA-256 hash of the full prompt for reproducibility.
Character. The model used.
POSIXct. When the response was generated.
The original question.
The retrieval method used.
Other chat:
sm_chat_render()
## Not run: # Requires ellmer package and chat-provider API access: # response <- sm_chat(corpus, "What methods dominate?", # provider = ellmer::chat_openai()) ## End(Not run)## Not run: # Requires ellmer package and chat-provider API access: # response <- sm_chat(corpus, "What methods dominate?", # provider = ellmer::chat_openai()) ## End(Not run)
Converts an sm_chat_response object into a formatted document string.
The rendered output includes the question, answer with inline citations,
and a reference list. Supported formats are Markdown, HTML, and Quarto.
sm_chat_render( response, format = c("markdown", "html", "quarto"), call = rlang::caller_env() )sm_chat_render( response, format = c("markdown", "html", "quarto"), call = rlang::caller_env() )
response |
An |
format |
Character. Output format: |
call |
Caller environment for error reporting. |
A character string containing the rendered response. Printed to the console and returned invisibly.
Other chat:
sm_chat()
# Requires a chat response object: # rendered <- sm_chat_render(response, format = "markdown") # cat(rendered)# Requires a chat response object: # rendered <- sm_chat_render(response, format = "markdown") # cat(rendered)
Adds citation-maturity flags to the works table so that downstream
trend and impact functions can shade or exclude recent, citation-immature
years instead of treating their (artificially low) citation counts as final.
Works published within the most recent lag years – relative to the
corpus's "as-of" date (metadata$last_refresh if present, else the maximum
publication year) – are flagged provisional. last_refresh may be a
POSIXct, a Date, numeric seconds since 1970-01-01 UTC (as in corpora
restored from tables) or ISO 8601 text; its year is taken in UTC. A value
that is not a plausible date (for example milliseconds stored as seconds)
is ignored with a message.
sm_citation_maturity(corpus, lag = 2L, call = rlang::caller_env())sm_citation_maturity(corpus, lag = 2L, call = rlang::caller_env())
corpus |
An |
lag |
Integer number of recent years to treat as citation-immature
(default |
call |
Caller environment for error reporting. |
The corpus with two logical columns added to works:
citation_mature (TRUE when the year is mature) and cnci_provisional
(the negation; TRUE when citation-based indicators should be treated as
provisional). Type-stable: an empty corpus gains the columns with 0 rows.
Other counting:
sm_citation_window(),
sm_count(),
sm_metric_summary()
corpus <- sm_example_corpus(n_works = 20, seed = 1) corpus <- sm_citation_maturity(corpus, lag = 2) table(corpus$works$cnci_provisional)corpus <- sm_example_corpus(n_works = 20, seed = 1) corpus <- sm_citation_maturity(corpus, lag = 2) table(corpus$works$cnci_provisional)
Counts each work's citations in its publication year and the window
years after it, from the yearly citation counts of the corpus
(citations_by_year, written by the OpenAlex readers and
sm_enrich_openalex_impact()), and says whether the counts cover the
whole window. Fixed windows compare old and recent works fairly, which
cited_by_count (all citations up to now) does not.
sm_citation_window(corpus, window = 3L, call = rlang::caller_env())sm_citation_window(corpus, window = 3L, call = rlang::caller_env())
corpus |
An |
window |
Number of years after the publication year, a whole number
of 0 or more (default |
call |
Caller environment for error reporting. |
OpenAlex gives the citations of the ten most recent years only (its
counts_by_year: the year of the counts and the nine before it), and
leaves out the years without citations. A year missing from
citations_by_year therefore counts 0 for a work that has yearly
counts. The date of the counts is works$citations_as_of (the day
OpenAlex last updated the record, or of the fetch); its year is still
running, so it counts as partial. A corpus restored with
sm_corpus_from_tables() may hold this date as a day count (days since
1970-01-01, as SQLite keeps dates) or as ISO 8601 text; both are read as
the date they stand for.
A window is complete when the work has a publication year and yearly
counts with a known date, its first year (the publication year) is no
earlier than nine years before the year of the counts, and its last year
is before the year of the counts. Otherwise complete is FALSE and
citations is a lower bound: the citations the counts hold for the
window years. A work without a publication year, or without yearly
counts (no rows in citations_by_year and no citations_as_of), gets
citations = NA.
The result depends only on the corpus, never on today's date, so it is
the same in every session. A corpus without citations_by_year gives
NA for every work, with one message (class
scimapR_no_citations_by_year) that points to
sm_enrich_openalex_impact().
A tibble with one row per work, in the order of corpus$works:
work_id (character), year (integer, the publication year), window
(integer), citations (integer) and complete (logical). A corpus
without works gives a 0-row tibble with these columns.
sm_citation_maturity(), sm_enrich_openalex_impact()
Other counting:
sm_citation_maturity(),
sm_count(),
sm_metric_summary()
works <- data.frame( work_id = c("W1", "W2", "W3"), title = c("Early work", "Recent work", "Unknown counts"), year = c(2018L, 2024L, 2019L), citations_as_of = as.Date(c("2026-10-17", "2026-10-17", NA)) ) yearly <- data.frame( work_id = c("W1", "W1", "W1", "W2"), year = c(2018L, 2019L, 2021L, 2025L), cited_by_count = c(2L, 5L, 4L, 3L) ) corpus <- sm_corpus_from_tables( list(works = works, citations_by_year = yearly) ) sm_citation_window(corpus, window = 3)works <- data.frame( work_id = c("W1", "W2", "W3"), title = c("Early work", "Recent work", "Unknown counts"), year = c(2018L, 2024L, 2019L), citations_as_of = as.Date(c("2026-10-17", "2026-10-17", NA)) ) yearly <- data.frame( work_id = c("W1", "W1", "W1", "W2"), year = c(2018L, 2019L, 2021L, 2025L), cited_by_count = c(2L, 5L, 4L, 3L) ) corpus <- sm_corpus_from_tables( list(works = works, citations_by_year = yearly) ) sm_citation_window(corpus, window = 3)
Produces a structured citation block for a corpus that can be included in manuscripts. The block documents the number of works, sources queried, date range, scimapR version, and corpus hash for reproducibility.
The build date is the UTC date of metadata$build_date, which may be a
POSIXct, a Date, numeric seconds since 1970-01-01 UTC or ISO 8601 text;
a missing or implausible value is shown as unknown.
sm_cite_corpus( corpus, style = c("text", "bibtex", "yaml"), call = rlang::caller_env() )sm_cite_corpus( corpus, style = c("text", "bibtex", "yaml"), call = rlang::caller_env() )
corpus |
An |
style |
Character. Citation style: |
call |
Caller environment for error reporting. |
A character string containing the formatted citation. Printed to the console and returned invisibly.
Other reproducibility:
sm_certificate(),
sm_diff_corpora(),
sm_hash_corpus(),
sm_provenance(),
sm_register_input(),
sm_snapshot()
corpus <- sm_example_corpus() sm_cite_corpus(corpus)corpus <- sm_example_corpus() sm_cite_corpus(corpus)
Analyses how research clusters evolve across time windows. For each pair
of consecutive windows, clusters are linked either by the overlap of the
terms in their titles (link_by = "terms", the default) or, after one
clustering of the whole corpus, by their shared cluster_id
(link_by = "cluster_id").
sm_cluster_evolution( corpus, time_windows = NULL, link_threshold = 0.3, link_by = c("terms", "cluster_id"), noise = 0L, call = rlang::caller_env() )sm_cluster_evolution( corpus, time_windows = NULL, link_threshold = 0.3, link_by = c("terms", "cluster_id"), noise = 0L, call = rlang::caller_env() )
corpus |
An sm_corpus object with a |
time_windows |
A list of integer vectors defining year ranges for each
window, or |
link_threshold |
Numeric between 0 and 1; minimum Jaccard similarity
to link two clusters across time windows. Defaults to |
link_by |
How clusters of consecutive windows are linked. |
noise |
The |
call |
Caller environment for error reporting. |
The function:
Splits works into time windows.
Within each window, identifies the cluster composition.
Between consecutive windows, links clusters. A work sits in exactly
one window, so clusters cannot share works; "terms" therefore
compares the terms of their titles and links the pairs whose Jaccard
similarity reaches link_threshold, while "cluster_id" links each
cluster to itself and reports the title-term Jaccard of its two
windows without filtering.
With one clustering of the whole corpus, the same cluster_id in two
windows is the continuity, and large clusters rarely reach the term
threshold, so "terms" can report no transition at all. Use
link_by = "cluster_id" there.
A list with two components and the attribute link_by (the
linking mode used):
snapshotsA tibble with columns window, cluster_id,
n_works, top_terms (a character string of representative
terms) and share (the cluster's share of the window's clustered
works, noise included, so the shares of a window sum to 1).
transitionsA tibble with columns from_window, to_window,
from_cluster, to_cluster, jaccard (the Jaccard similarity of
the two clusters' title terms, rounded to 4 digits; 0 when neither
has any) and n_shared (the number of shared title terms).
Other clustering:
sm_cluster_hdbscan(),
sm_cluster_kmeans(),
sm_cluster_label(),
sm_cluster_leiden()
corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_kmeans(corpus, k = 5, reducer = "pca") evo <- sm_cluster_evolution(corpus) evo$snapshots evo$transitions # one clustering for the whole corpus: follow each cluster id evo <- sm_cluster_evolution(corpus, link_by = "cluster_id") evo$transitionscorpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_kmeans(corpus, k = 5, reducer = "pca") evo <- sm_cluster_evolution(corpus) evo$snapshots evo$transitions # one clustering for the whole corpus: follow each cluster id evo <- sm_cluster_evolution(corpus, link_by = "cluster_id") evo$transitions
Clusters works using the HDBSCAN (Hierarchical Density-Based Spatial
Clustering of Applications with Noise) algorithm via dbscan::hdbscan().
Optionally reduces embedding dimensions first with UMAP or PCA.
sm_cluster_hdbscan( corpus, min_cluster_size = 15L, min_samples = NULL, reducer = c("umap", "pca", "none"), n_components = 5L, call = rlang::caller_env() )sm_cluster_hdbscan( corpus, min_cluster_size = 15L, min_samples = NULL, reducer = c("umap", "pca", "none"), n_components = 5L, call = rlang::caller_env() )
corpus |
An sm_corpus object with embeddings. |
min_cluster_size |
Integer; minimum cluster size for HDBSCAN. Defaults
to |
min_samples |
Integer or |
reducer |
Character; dimensionality reduction method to apply before
clustering. One of |
n_components |
Integer; number of dimensions to reduce to. Defaults
to |
call |
Caller environment for error reporting. |
Work IDs and embedding row names must be unique. Repeated IDs stop
with class scimapR_duplicate_ids; use sm_validate() to inspect them.
Requires embeddings in corpus$embeddings. Compute them first with
sm_embed_works() or load from cache with sm_embed_load().
The reducer step helps HDBSCAN work in a lower-dimensional space where density estimation is more reliable.
UMAP requires its optional dependency only when reduction is needed.
If the PCA dependency raises the known NULL-input error on older R,
reduction retries with base SVD initialisation and reports
scimapR_umap_pca_fallback. Other errors propagate unchanged.
The input corpus with a cluster_id column added to
corpus$works. Noise points (not assigned to any cluster) receive
cluster_id = 0L. Works without a row in corpus$embeddings (such as
works without text, see sm_embed_works()) get NA.
Other clustering:
sm_cluster_evolution(),
sm_cluster_kmeans(),
sm_cluster_label(),
sm_cluster_leiden()
corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10L) table(corpus$works$cluster_id)corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10L) table(corpus$works$cluster_id)
Clusters works using K-means clustering via stats::kmeans(). Optionally
reduces embedding dimensions first with UMAP or PCA.
sm_cluster_kmeans( corpus, k, reducer = c("umap", "pca", "none"), n_components = 5L, call = rlang::caller_env() )sm_cluster_kmeans( corpus, k, reducer = c("umap", "pca", "none"), n_components = 5L, call = rlang::caller_env() )
corpus |
An sm_corpus object with embeddings. |
k |
Integer; the number of clusters. Required. |
reducer |
Character; dimensionality reduction method to apply before
clustering. One of |
n_components |
Integer; number of dimensions to reduce to. Defaults
to |
call |
Caller environment for error reporting. |
Work IDs and embedding row names must be unique. Repeated IDs stop
with class scimapR_duplicate_ids; use sm_validate() to inspect them.
Requires embeddings in corpus$embeddings. Compute them first with
sm_embed_works() or load from cache with sm_embed_load().
K-means is deterministic given a fixed random seed. Consider setting a seed before calling this function for reproducibility.
UMAP requires its optional dependency only when reduction is needed.
If the PCA dependency raises the known NULL-input error on older R,
reduction retries with base SVD initialisation and reports
scimapR_umap_pca_fallback. Other errors propagate unchanged.
The input corpus with a cluster_id column added to
corpus$works. Works without a row in corpus$embeddings (such as
works without text, see sm_embed_works()) get NA.
Other clustering:
sm_cluster_evolution(),
sm_cluster_hdbscan(),
sm_cluster_label(),
sm_cluster_leiden()
corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_kmeans(corpus, k = 5, reducer = "pca") table(corpus$works$cluster_id)corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_kmeans(corpus, k = 5, reducer = "pca") table(corpus$works$cluster_id)
Assigns human-readable labels to clusters by extracting representative terms. Supports TF-IDF-based extraction (default), labels from the author and indexer keywords of the corpus, YAKE keyword extraction, or labels from a chat provider via the ellmer package.
sm_cluster_label( corpus, method = c("tfidf", "keywords", "yake", "llm"), n_terms = 5L, llm_provider = NULL, min_works = NULL, ngram = 1:2, stop_terms = NULL, noise = 0L, seed = NULL, call = rlang::caller_env() )sm_cluster_label( corpus, method = c("tfidf", "keywords", "yake", "llm"), n_terms = 5L, llm_provider = NULL, min_works = NULL, ngram = 1:2, stop_terms = NULL, noise = 0L, seed = NULL, call = rlang::caller_env() )
corpus |
An sm_corpus object with a |
method |
Character; labelling method. One of |
n_terms |
Integer; number of top terms to include in each cluster
label. Defaults to |
llm_provider |
An ellmer chat provider object (e.g.,
|
min_works |
For |
ngram |
For |
stop_terms |
Character vector of terms to leave out of the labels,
such as generic words that every cluster shares; |
noise |
The |
seed |
Optional seed for sampling titles with a chat provider.
The session random state is restored afterwards. |
call |
Caller environment for error reporting. |
Noise and clusters without a label: works whose cluster_id is
NA or noise get no label from any method, and the "tfidf" and
"keywords" methods leave them out of the works that make a term
common or rare in the corpus. A cluster whose works share no term, such
as a cluster of a single work, gets no label either, with a message.
TF-IDF method: Splits the titles and abstracts of the works into
terms of ngram words. A term's score in a cluster is the share of the
cluster's works that contain it times log(N / n), where N is the
number of clustered works and n the number of them that contain the
term. As the score counts works, not occurrences, and a term must occur
in at least min_works works of the cluster, a term that one paper
repeats cannot name a cluster. The n_terms terms with the highest
scores form the label; ties go to the term that occurs more often in the
cluster, then to the first in alphabetical order. A longer term takes
the place of a shorter one it contains when it occurs in at least 40% of
the shorter term's works ("cancer" gives way to "testicular cancer", and
to "metastatic testicular cancer" only if that also occurs in 40% of the
works with "cancer"), and a term contained in one already chosen is
skipped. When a term overlaps one already chosen, as "heat illness"
overlaps "exertional heat", the phrase they form ("exertional heat
illness") counts as a longer term that contains both, also beyond the
lengths in ngram, if it occurs in enough of the cluster's works to be
a term of the label; so one phrase does not fill two places of a label.
Words of fewer than 3 characters, numbers and stop words are left out,
and no term spans one of them, the end of a sentence or clause, or the
border between title and abstract. The stop words of every work are the
English ones of tidytext::stop_words, plus the Snowball list of the
stopwords package for the work's own language in works$language,
given as a name such as "German", an ISO 639-1 code such as "de" or
an ISO 639-2 code such as "ger". Works without a language get the
English list only, and the lists of other languages are never pooled, so
German stop words such as "war" or "die" stay in English text. Without
the stopwords package, or for a language it has no list for, only
the English list is used, with a message.
Singular and plural count as one term: a final "ies" becomes "y", and a final "s" is dropped after a letter other than s, i, u, o, a and y, so "fractures" counts with "fracture" and "ticks" with "tick", while "diagnosis", "virus", "class" and "days" stay as they are. A possessive "'s" is dropped as well, and words such as "species", "series", "diabetes", "measles" or "aids" are kept as written, as are names of fields such as "logistics", "genetics", "genomics" or "pediatrics", which would otherwise count with the adjectives "logistic", "genetic", "genomic" or "pediatric". A label shows each term in the spelling the cluster's works use most often.
Keyword method: The same scoring on the author keywords and the
indexer keywords (such as Keywords Plus or MeSH) in corpus$concepts,
the families "author_keyword" and "indexer_keyword" of
sm_concept_vocabularies(). Keywords are compared ignoring case, with
hyphens read as spaces and plurals folded as above, and containment is
checked on whole words; keywords that overlap form no phrase. A work
counts once for each keyword, however many of its vocabularies give it.
A keyword is shown as an author keyword spells it, or else in lower
case. A cluster without a keyword in
at least 2 of its works gets the TF-IDF terms of its titles instead;
when the clustered works have no such keywords at all, every cluster
does, with a message.
YAKE method: Uses a simplified YAKE-like scoring based on term frequency, position, and spread across works within each cluster, with the stop words of the TF-IDF method.
LLM method: Sends titles and abstracts of each cluster to an LLM with a prompt asking for a concise topical label. Requires the ellmer package and a configured provider.
The input corpus with two columns added to corpus$works, in
place of earlier ones:
cluster_labelThe terms of the work's cluster joined with
"; ", the best first; NA for noise, for works without a
cluster and for clusters that got no label.
cluster_label_basisHow the label was made: "tfidf" or
"keywords" when every term occurs in at least min_works of the
cluster's works; "tfidf (relaxed)" or "keywords (relaxed)"
when no term reached min_works, so that the terms occur in at
least 2 works; "tfidf (fallback)" or
"tfidf (fallback, relaxed)" when method = "keywords" found no
keyword in 2 or more of the cluster's works and took the terms of
its titles; "yake" or "llm" for these methods; NA where
cluster_label is NA.
Other clustering:
sm_cluster_evolution(),
sm_cluster_hdbscan(),
sm_cluster_kmeans(),
sm_cluster_leiden()
corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_kmeans(corpus, k = 5, reducer = "pca") corpus <- sm_cluster_label(corpus, method = "tfidf", n_terms = 3L) unique(corpus$works[, c("cluster_id", "cluster_label", "cluster_label_basis")]) # keyword labels: the real-use example corpus grouped by journal category fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) fix$works$cluster_id <- match(fix$works$field, unique(fix$works$field)) fix <- sm_cluster_label(fix, method = "keywords", n_terms = 3L) unique(fix$works[, c("field", "cluster_label", "cluster_label_basis")])corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_kmeans(corpus, k = 5, reducer = "pca") corpus <- sm_cluster_label(corpus, method = "tfidf", n_terms = 3L) unique(corpus$works[, c("cluster_id", "cluster_label", "cluster_label_basis")]) # keyword labels: the real-use example corpus grouped by journal category fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) fix$works$cluster_id <- match(fix$works$field, unique(fix$works$field)) fix <- sm_cluster_label(fix, method = "keywords", n_terms = 3L) unique(fix$works[, c("field", "cluster_label", "cluster_label_basis")])
Clusters works using the Leiden community detection algorithm via
igraph::cluster_leiden(). If no network is provided, a semantic
similarity network is built automatically from embeddings.
sm_cluster_leiden( corpus, network = NULL, resolution = 1, call = rlang::caller_env() )sm_cluster_leiden( corpus, network = NULL, resolution = 1, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
network |
A tidygraph::tbl_graph or igraph::igraph object, or
|
resolution |
Numeric; resolution parameter for the Leiden algorithm.
Higher values produce more (smaller) clusters. Defaults to |
call |
Caller environment for error reporting. |
Work IDs and network node names must be unique. Repeated IDs stop
with class scimapR_duplicate_ids; use sm_validate() to inspect them.
The Leiden algorithm (Traag et al., 2019) is a refinement of the Louvain algorithm that guarantees well-connected communities. It operates on the edge weights of the network.
When network = NULL, embeddings must be present in the corpus so that
sm_network_semantic() can build a k-NN graph.
The input corpus with a cluster_id column added to
corpus$works. Works that are not nodes of the network, such as works
without a row in corpus$embeddings, get NA.
Other clustering:
sm_cluster_evolution(),
sm_cluster_hdbscan(),
sm_cluster_kmeans(),
sm_cluster_label()
corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_leiden(corpus, resolution = 1.0) table(corpus$works$cluster_id)corpus <- sm_example_corpus(with_embeddings = TRUE) corpus <- sm_cluster_leiden(corpus, resolution = 1.0) table(corpus$works$cluster_id)
The canonical values of the vocabulary column of corpus$concepts.
Readers write these names (for example "author-keywords" for author
keywords and "wos-category" for the Web of Science categories), and the
functions that select concepts by vocabulary, such as
sm_to_bibliometrix() (DE) and sm_network_coword()
(field = "keywords"), read them.
Each vocabulary belongs to a family:
author_keyword: author-keywords, manual-tags;
indexer_keyword: keywords-plus, index-keywords,
automatic-tags, mesh;
category: wos-category (WC, the Web of Science categories),
wos-subject-category (SC, the research areas);
concept: openalex, arxiv, biorxiv, medrxiv;
custom: custom, for vocabularies of your own.
sm_corpus() and sm_corpus_from_tables() rename legacy and common
spellings to these names, with a message: case, white space and the
separators -, _, . and / do not matter, "keywords-author",
"keyword" and "keywords" become "author-keywords", "medline"
becomes "mesh", and the Web of Science field tags "DE", "ID",
"WC" and "SC" name their vocabularies. Other values are kept and
named in the message. The functions that select concepts by vocabulary
read these spellings too, so saved corpora and tables assigned to
corpus$concepts work as well. The column stays character.
sm_concept_vocabularies(describe = FALSE)sm_concept_vocabularies(describe = FALSE)
describe |
Logical; if |
A character vector (grouped by family) or a tibble of
level/family/description.
sm_to_bibliometrix(), sm_network_coword(), sm_read_wos()
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
sm_concept_vocabularies() sm_concept_vocabularies(describe = TRUE) corpus <- sm_example_corpus(n_works = 5, with_embeddings = FALSE) table(corpus$concepts$vocabulary)sm_concept_vocabularies() sm_concept_vocabularies(describe = TRUE) corpus <- sm_example_corpus(n_works = 5, with_embeddings = FALSE) table(corpus$concepts$vocabulary)
Build a typed, tibble-of-tibbles corpus container for bibliometric analysis. The corpus is the central data structure in scimapR, holding works, authors, authorships, institutions, sources, references, concepts, embeddings, provenance, screening decisions, and metadata.
sm_corpus( works, authors = NULL, authorships = NULL, institutions = NULL, sources = NULL, references = NULL, concepts = NULL, embeddings = NULL, provenance = NULL, screening = NULL, metadata = list(), citations_by_year = NULL ) ## S3 method for class 'sm_corpus' x[i, ...] ## S3 method for class 'sm_corpus' length(x) ## S3 method for class 'sm_corpus' dim(x) ## S3 method for class 'sm_corpus' as_tibble(x, ...) ## S3 method for class 'sm_corpus' as.data.frame(x, ...) ## S3 method for class 'sm_corpus' print(x, ...) ## S3 method for class 'sm_corpus' format(x, ...) ## S3 method for class 'sm_corpus' summary(object, ...) ## S3 method for class 'sm_corpus' str(object, ...)sm_corpus( works, authors = NULL, authorships = NULL, institutions = NULL, sources = NULL, references = NULL, concepts = NULL, embeddings = NULL, provenance = NULL, screening = NULL, metadata = list(), citations_by_year = NULL ) ## S3 method for class 'sm_corpus' x[i, ...] ## S3 method for class 'sm_corpus' length(x) ## S3 method for class 'sm_corpus' dim(x) ## S3 method for class 'sm_corpus' as_tibble(x, ...) ## S3 method for class 'sm_corpus' as.data.frame(x, ...) ## S3 method for class 'sm_corpus' print(x, ...) ## S3 method for class 'sm_corpus' format(x, ...) ## S3 method for class 'sm_corpus' summary(object, ...) ## S3 method for class 'sm_corpus' str(object, ...)
works |
A tibble of works (publications). See Details for schema.
Common spellings of |
authors |
A tibble of authors. If |
authorships |
A tibble linking works to authors. If |
institutions |
A tibble of institutions. If |
sources |
A tibble of publication sources/journals. If |
references |
A tibble of cited references. If |
concepts |
A tibble of concepts/keywords. If |
embeddings |
A numeric matrix of work embeddings, or |
provenance |
A tibble tracking data lineage. If |
screening |
A tibble of screening decisions. If |
metadata |
A list of corpus-level metadata. |
citations_by_year |
|
x, object
|
An |
i |
Row index for subsetting. |
... |
Ignored. |
IDs are unique within a corpus; sm_bind_corpora() gives the second
corpus new IDs where they collide.
A corpus is a list of tables that share identifiers: work_id links
every table to works, author_id links authorships to authors,
institution_id links them to institutions and source_id links
works to sources. The tables below distinguish the standard schema
columns (as in the empty tables) from optional columns that particular
readers and functions write or read. A schema value may be NA.
sm_corpus() adds missing schema columns to works (mostly NA);
it takes other supplied tables as given. validate_sm_corpus() checks
the required list elements, that works is a tibble with work_id,
that metadata is a list, and the yearly citation schema below. It does
not require or check every standard column of the other tables. Use
sm_validate() for further data checks.
works: one row per work. Standard schema:
| Column | Type | Content |
work_id |
character | Identifier, unique within the corpus |
doi |
character | DOI, lower case, without https://doi.org/ |
title, abstract |
character | Title and abstract |
year |
integer | Publication year |
type |
character | Document type, as the source names it |
source_id |
character | The journal or other source (sources) |
cited_by_count |
integer | Citations, as the source counts them |
oa_status |
character | One of sm_oa_statuses(), or NA |
language |
character | Language |
pmid, arxiv_id, openalex_id |
character | Other identifiers |
is_retracted |
logical | Retraction flag |
retraction_date |
Date | Date of the retraction |
last_refreshed |
POSIXct | When the record was read or fetched |
Optional: quartile, jif_quartile or sjr_quartile (journal
quartiles such as "Q1", for sm_its(outcome = "share_q1")); numeric
impact columns cnci, fwci, fnci, rcr, cnci_value, ncs, mncs
and field_citation_ratio (searched in that order by sm_its() and
sm_metric_summary()); field (for
sm_metric_fnci(classification = "manual")); citation_mature and
cnci_provisional (sm_citation_maturity()); cluster_id (the
clustering functions); cluster_label and cluster_label_basis, a
cluster's label and how it was made (sm_cluster_label()). The
OpenAlex readers and sm_enrich_openalex_impact() write these impact
columns:
| Column | Type | Content |
fwci |
double | Field-weighted citation impact from OpenAlex |
citation_percentile |
double | OpenAlex citation percentile, 0 to 1 |
is_top_10pct |
logical | Among the 10% most cited of field and year |
is_top_1pct |
logical | Among the 1% most cited of field and year |
citations_as_of |
Date | Date of these values and the yearly counts |
The openalexR engine of sm_fetch_openalex() has no percentile, so its
citation_percentile, is_top_10pct and is_top_1pct are NA.
citations_as_of is the day of the OpenAlex record's updated_date, or
of the fetch when the record has none; sm_read_openalex_json() leaves
it NA for records without updated_date. sm_citation_window() reads
it to tell complete citation windows from partial ones. A corpus
restored with sm_corpus_from_tables() can hold it as a day count (days
since 1970-01-01, as SQLite keeps dates) or as ISO 8601 text;
sm_citation_window() and sm_enrich_openalex_impact() read both as
the date they stand for.
authors: one row per author. Standard schema:
| Column | Type | Content |
author_id |
character | Identifier, unique within the corpus |
orcid |
character | ORCID iD |
display_name |
character | Name |
display_name_alternatives |
list of character | Other spellings |
inferred_gender, gender_method |
character | Gender and its method |
gender_confidence |
double | Confidence of inferred_gender
|
display_name_alternatives holds, for example, the names as a Web of
Science file writes them, and sm_audit_gender(method = "manual")
reads the gender columns. Optional: none so far.
authorships: one row per work, author and affiliation, so an author
with several addresses in a work has several rows, which share
position and is_corresponding. Standard schema:
| Column | Type | Content |
work_id, author_id |
character | The work and the author |
position |
integer | Place in the author list, from 1 (NA: unknown) |
is_corresponding |
logical | Corresponding author: see below |
institution_id |
character | The institution (institutions) |
raw_affiliation |
character | The address as the source writes it |
country_code |
character | ISO 3166-1 alpha-2 country code |
is_corresponding is TRUE for the authors the source names as
corresponding, FALSE for the other authors of such a work and NA
when the source does not say who the corresponding author is.
sm_read_wos() and as_sm_corpus() read it from the RP field,
sm_read_openalex_json() and sm_fetch_openalex() from OpenAlex (a
work whose authors are all marked false takes its corresponding
authors from corresponding_author_ids, and counts as unknown when
that lists none of them), sm_read_dimensions() from its Corresponding
Authors column and sm_fetch_biorxiv() from the bioRxiv API; the other
readers and fetchers write NA. Up to scimapR 0.4.0 they flagged the
first author instead. A name that fits several authors of a work
equally (namesakes with the same surname and initials) and that the
source does not tell apart leaves them NA. Functions that select
corresponding authors take TRUE only, and the leadership outcome of
sm_its() leaves works whose corresponding author is unknown out of
its shares.
Optional: institution_match, match_method, match_signal and
match_evidence (sm_affiliation_match()); institution_name
(sm_attribute_institution()); email (exported as EM by
sm_to_bibliometrix()).
institutions: one row per institution. Standard schema:
| Column | Type | Content |
institution_id |
character | Identifier, as in authorships |
ror |
character | ROR id |
display_name |
character | Name |
country_code |
character | ISO 3166-1 alpha-2 country code |
region, income_tier |
character | World region and income group |
type |
character | Kind of institution, such as education or healthcare |
Optional: none so far.
sources: one row per journal or other source. Standard schema:
| Column | Type | Content |
source_id |
character | Identifier, as in works |
issn_l |
character | Linking ISSN |
issn |
list of character | All ISSNs of the source |
display_name |
character | Name |
type |
character | Kind of source, such as journal |
is_oa |
logical | Open-access source |
publisher, publisher_country |
character | Publisher and its country |
Optional: abbreviation and iso_abbreviation (character; the Web of
Science abbreviations J9 and JI, written by sm_read_wos() and the
bibliometrix conversion and exported by sm_to_bibliometrix()).
references: one row per cited reference. Standard schema:
| Column | Type | Content |
work_id |
character | The citing work |
ref_index |
integer | Place in the work's reference list |
cited_work_id |
character | Cited work in works; see linking below |
cited_doi |
character | DOI of the cited work |
cited_raw |
character | The reference as the source writes it |
Reference linking: readers, fetchers, sm_bind_corpora(), sm_dedupe()
and sm_link_references() set cited_work_id from cited_doi or an
OpenAlex id in cited_raw when the cited work is in works.
Optional: none so far.
concepts: one row per keyword, category or concept of a work.
Standard schema:
| Column | Type | Content |
work_id |
character | The work |
concept_id |
character | Identifier in its vocabulary |
concept_name |
character | The keyword, category or concept |
level |
integer | Level in the hierarchy of the vocabulary |
score |
double | Weight given by the source |
vocabulary |
character | One of sm_concept_vocabularies()
|
For MeSH headings score is 1 for a major topic and 0 otherwise.
Optional: none so far.
embeddings: NULL or a numeric matrix with one row per embedded
work, named by its work_id, and one column per dimension. Works may
have no row: works without text to embed (sm_embed_works()), works
missing from a cache (sm_embed_load()) or works for which a service
has no vector (sm_enrich_specter()). The clustering functions give
them no cluster (NA), and sm_network_semantic() and
sm_plot_landscape() leave them out. metadata$embedding records the
model (see below).
provenance: one row per work and source. Standard schema:
| Column | Type | Content |
work_id |
character | The work |
source |
character | Where the record comes from, such as "wos" |
source_id_external |
character | Its identifier at the source |
fetch_date |
POSIXct | When it was read or fetched |
query |
character | The query or file it came from |
engine |
character | The parser or client that read it |
scimapR_version |
character | The scimapR version that read it |
prompt_hash |
character | Hash of a prompt behind the record |
source_id_external is, for example, the Web of Science accession
number (UT); prompt_hash is NA for records that no prompt
produced. Optional:
| Column | Type | Content |
kind |
character | The kind of step that wrote the row (see below) |
filter |
character | The filter of a search, else NA |
n_max |
integer | The maximum number of works of a search |
kind is "api_query" for the searches of sm_fetch_openalex(),
sm_fetch_pubmed(), sm_fetch_crossref() and
sm_corpus_for_question(), which also write filter and n_max;
"file" for the file readers, whose query holds the file relative to
the working directory when it lies below it, else its name; and
"enrichment" for the enrichers, such as sm_enrich_openalex_impact(),
whose rows have the source "openalex_impact". sm_certificate()
records the kind of each step, inferred from the source, query and
engine for rows without kind, and sm_rebuild_from_cert() replays
only searches, never a file read or an enrichment.
screening: one row per screening decision. Standard schema:
| Column | Type | Content |
work_id |
character | The work |
stage |
character | Stage, such as "title-abstract" or "full-text" |
decision |
character | "include", "exclude" or "uncertain" |
reason |
character | Reason for the decision |
confidence |
double | Confidence of the decision |
source |
character | Who or what decided |
decided_at |
POSIXct | When |
Optional: model records the chat-provider model for the decision.
The legacy decision "unclear" is read as "uncertain".
citations_by_year: optional (NULL when the corpus has no yearly
citation counts); one row per work and citation year, as the OpenAlex
readers, sm_enrich_openalex_impact() and sm_corpus_from_tables()
write it:
| Column | Type | Content |
work_id |
character | The cited work |
year |
integer | The year of the citations |
cited_by_count |
integer | Citations received in that year, 0 or more |
work_id and year may not be missing, and each pair appears once.
OpenAlex gives the counts of the ten most recent years and leaves out
the years without citations, so a missing year counts 0 for a work with
yearly counts (see works$citations_as_of and sm_citation_window()).
Subsetting keeps the rows of the kept works, sm_bind_corpora() binds
them and sm_hash_corpus() includes them when there are any.
metrics: an optional legacy table read by the impact resolver
when no impact column in works has values. It is not created by
sm_corpus() or included in the standard empty schema:
| Column | Type | Content |
work_id |
character | The work |
| Impact column | numeric | One of the impact names above, or value
|
The first named impact column, in the same order as for works and
then value, is used when it contains a nonmissing value. Prefer
impact columns in works for new corpora.
metadata: a list. Standard entries unless marked optional:
| Entry | Type | Content |
scimapR_version |
character | The scimapR version that built the corpus |
build_date |
POSIXct | When the corpus was built |
corpus_hash |
character | Hash of the content (sm_hash_corpus()) |
is_locked |
logical | Whether sm_lock() locked the corpus |
last_refresh |
timestamp | Last refresh without a failed request |
n_identified |
integer | Optional count before search deduplication |
question_id |
character | Id of its sm_question() |
fetch_incomplete |
list | Optional failed-fetch report (see below) |
n_identified includes records removed by question-search deduplication
or its result limit.
last_refresh advances only when sm_refresh() refreshed at least one
work and no request failed. A recovered partial fetch has optional
fetch_incomplete (list) with source, n_fetched, n_expected and
reason; see the fetchers' Failed requests sections.
question_id is set by sm_corpus_for_question(). Optional entries
locked_at (POSIXct), lock_reason (character) and lock_hash
(character) record when, why and which content sm_lock() locked.
Subsets remain locked but discard lock_hash.
Other optional entries include those added by readers, such as
source_file and reader;
hash_version (integer), the algorithm of corpus_hash
(sm_hash_corpus()), which sm_snapshot() writes (a snapshot without
it used version 1); and local_inputs, a tibble of the local files the
corpus was built from, with their SHA-256 hashes, which
sm_register_input() and sm_corpus_from_tables() (inputs) write,
sm_certificate() records and sm_verify_certificate() checks (see
sm_register_input() for its columns). local_inputs is NULL until
inputs are registered or supplied.
build_date and last_refresh may also be numbers of seconds since
1970-01-01 UTC, as corpora restored from tables store them, ISO 8601
text or dates; sm_certificate() and the citation-maturity cut-offs
read all of these. print() also reads these forms for last_refresh
and shows YYYY-MM-DD HH:MM UTC. NULL and a single missing value
print as "never"; unsupported values and vectors use format().
Optional: embedding, the record of the model behind embeddings, a
list that sm_embed_works(), sm_enrich_specter(),
sm_fetch_semantic_scholar(), sm_embed_load() (from the cache of
sm_embed_save()) and sm_bind_corpora() write and
sm_certificate() reports:
| Entry | Type | Content |
model |
character | Short model name, "mixed" for several |
model_id |
character | Hugging Face id or Semantic Scholar field |
base_model_id |
character | Base model of an adapter, else NA |
revision |
character | Commit of the model snapshot, or NA |
base_revision |
character | Commit of the base model, or NA |
backend |
character | The Python package or the service used |
text |
character | The text embedded, such as "title_abstract" |
prefix |
character | Input prefix, else "" |
versions |
list | Versions of the Python packages used |
models |
character | The model ids behind the rows |
n_works, n_dimensions |
integer | Size of the matrix when made |
created |
POSIXct | When the record was written (UTC) |
model_id is the Hugging Face id of the model (for "specter2" that
of the adapter, whose base model is base_model_id) or the Semantic
Scholar field of the vectors ("embedding.specter_v2" or
"embedding.specter_v1"), whose revision is the model version that
Semantic Scholar reports. backend is "sentence-transformers",
"adapters", "semantic-scholar" or "mixed"; a mix lists its models
in models. prefix is "query: " for "multilingual-e5".
A record whose n_dimensions differs from the width of embeddings,
or whose n_works is smaller than its number of rows, no longer
describes the matrix and counts as no record. Embeddings without a
record, such as those made by earlier versions of scimapR, have a model
that is not recorded: sm_certificate() reports it as "unknown", and
the functions that combine embeddings combine them with recorded ones
only when asked to (mix_models).
An sm_corpus S3 object.
[: An sm_corpus with the selected works and only the authors,
institutions and sources they refer to. Counts and the corpus hash
describe the subset, including when all works are selected.
length(): Number of works (integer).
dim(): Integer vector of length 2 (rows, columns of works table).
as_tibble(): The works tibble.
as.data.frame(): The works table as a data frame.
print(): x invisibly.
format(): A single character string.
summary(): A named list of summary statistics.
str(): object invisibly.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
corpus <- sm_corpus( works = tibble::tibble( work_id = "W000000001", doi = "10.1234/example", title = "Example Work", abstract = "An example abstract.", year = 2024L, type = "journal-article", source_id = NA_character_, cited_by_count = 0L, oa_status = "closed", language = "en", pmid = NA_character_, arxiv_id = NA_character_, openalex_id = NA_character_, is_retracted = FALSE, retraction_date = NA_real_, last_refreshed = Sys.time() ) ) print(corpus)corpus <- sm_corpus( works = tibble::tibble( work_id = "W000000001", doi = "10.1234/example", title = "Example Work", abstract = "An example abstract.", year = 2024L, type = "journal-article", source_id = NA_character_, cited_by_count = 0L, oa_status = "closed", language = "en", pmid = NA_character_, arxiv_id = NA_character_, openalex_id = NA_character_, is_retracted = FALSE, retraction_date = NA_real_, last_refreshed = Sys.time() ) ) print(corpus)
Takes a structured sm_question() object, translates its fields into
database-specific query strings, fetches results from the requested
bibliographic sources, deduplicates on DOI, and returns an sm_corpus
with metadata$question_id linked to the question.
This function requires the appropriate API-client packages to be installed
for each source (openalexR for OpenAlex, rcrossref for Crossref,
rentrez for PubMed).
sm_corpus_for_question( question, sources = c("pubmed", "openalex", "crossref"), n_max = 1000L, verbose = TRUE, call = rlang::caller_env() )sm_corpus_for_question( question, sources = c("pubmed", "openalex", "crossref"), n_max = 1000L, verbose = TRUE, call = rlang::caller_env() )
question |
An |
sources |
Character vector of sources to query. Subset of
|
n_max |
Integer. Maximum total number of works to retrieve
(across all sources, before deduplication). Default |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus with metadata$question_id set to the
question's content-hash ID. Its provenance records each search
(query, n_max and, as engine, the client package that ran it), so
that sm_rebuild_from_cert() can run it again.
Other question:
is_sm_question(),
sm_question(),
sm_screen_against_question(),
sm_screen_regex(),
sm_screen_summary()
q <- sm_question( text = "Does immunotherapy improve survival in melanoma?", framework = "PICO", population = "melanoma", intervention = "immunotherapy", outcome = "survival" ) # Would query live APIs: # corpus <- sm_corpus_for_question(q)q <- sm_question( text = "Does immunotherapy improve survival in melanoma?", framework = "PICO", population = "melanoma", intervention = "immunotherapy", outcome = "survival" ) # Would query live APIs: # corpus <- sm_corpus_for_question(q)
A documented, validating constructor that builds an sm_corpus from a named
list of data frames – making non-OpenAlex/WoS tabular sources first-class
and side-stepping format-specific parsers. Required columns are validated
against the sm_corpus schema, column types are coerced (with informative
messages), absent optional tables are filled with correctly-typed 0-row
tibbles, and the result is validated before being returned. Legacy and
common spellings of concept vocabularies (such as "keywords-author" or
"Author Keywords") are renamed to the names of
sm_concept_vocabularies(), with a message. Values that name no known
vocabulary are kept as written and listed in the same message, each time
a corpus is built from them; name a vocabulary of your own "custom".
This is the recommended ingestion path for arbitrary tabular sources.
sm_corpus_from_tables( tables, schema = NULL, .coerce = TRUE, metadata = list(), inputs = NULL, call = rlang::caller_env() )sm_corpus_from_tables( tables, schema = NULL, .coerce = TRUE, metadata = list(), inputs = NULL, call = rlang::caller_env() )
tables |
A named list of data frames. Recognised names are |
schema |
Optional named list of empty template tibbles overriding the
built-in schema (advanced use). Defaults to the standard |
.coerce |
Logical (default |
metadata |
Optional list of corpus-level metadata. |
inputs |
|
call |
Caller environment for error reporting. |
A validated sm_corpus object.
Other reporting:
sm_figure_manifest()
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
works <- data.frame( work_id = c("W1", "W2"), title = c("First", "Second"), year = c("2020", "2021"), # character -> coerced to integer doi = c("10.1/a", "10.1/b") ) authorships <- data.frame( work_id = c("W1", "W1", "W2"), author_id = c("A1", "A2", "A1"), position = c(1, 2, 1) ) corpus <- sm_corpus_from_tables(list(works = works, authorships = authorships)) corpus # record the file the tables were read from snapshot <- tempfile(fileext = ".csv") utils::write.csv(works, snapshot, row.names = FALSE) corpus <- sm_corpus_from_tables( list(works = utils::read.csv(snapshot)), inputs = c("source table" = snapshot) ) corpus$metadata$local_inputs[, c("basename", "role", "sha256")]works <- data.frame( work_id = c("W1", "W2"), title = c("First", "Second"), year = c("2020", "2021"), # character -> coerced to integer doi = c("10.1/a", "10.1/b") ) authorships <- data.frame( work_id = c("W1", "W1", "W2"), author_id = c("A1", "A2", "A1"), position = c(1, 2, 1) ) corpus <- sm_corpus_from_tables(list(works = works, authorships = authorships)) corpus # record the file the tables were read from snapshot <- tempfile(fileext = ".csv") utils::write.csv(works, snapshot, row.names = FALSE) corpus <- sm_corpus_from_tables( list(works = utils::read.csv(snapshot)), inputs = c("source table" = snapshot) ) corpus$metadata$local_inputs[, c("basename", "role", "sha256")]
Attributes publication output (and citation impact) to entities using either full or fractional counting. Reviewers routinely ask for fractional counts so that a single multi-author / multi-institution paper is not counted in full for every contributor.
sm_count( corpus, method = c("full", "fractional"), level = c("institution", "author", "source"), call = rlang::caller_env() )sm_count( corpus, method = c("full", "fractional"), level = c("institution", "author", "source"), call = rlang::caller_env() )
corpus |
An |
method |
|
level |
Entity level: |
call |
Caller environment for error reporting. |
The fractional rule is the standard equal-share rule: a work with
distinct entities contributes of credit (and of its
citations) to each. For level = "source" each work has a single source, so
fractional and full counts coincide.
For level = "institution" each authorship row is labelled on its own, by
the first of these authorships columns that has a value in that row:
institution_match (from sm_affiliation_match()), institution_name,
institution_id (named by corpus$institutions$display_name when that
table lists the id) and raw_affiliation. A row labelled through
institution_id has that id as entity_id; every other row has its label
as both entity_id and entity_name. Rows labelled by their raw
affiliation string are counted un-disambiguated, with a warning, because
sm_count() applies no affiliation dictionary itself: run
sm_affiliation_match() with a dictionary of your institutions first to
count them.
The order is the one sm_coverage_audit() uses for its affiliation
levels, which however takes one column for all rows and falls back to
country codes instead of raw strings. Up to scimapR 0.4.0, sm_count()
used one column for every row (the first of institution_id,
institution_match and raw_affiliation with any value), so rows without
a value in it were left out, and it matched raw strings against the
shipped sm_affiliation_dict before counting them.
sm_attribute_institution() keeps institution_match, so its roll-up of
the matches to a controlled vocabulary (ROR ids and names in
institution_id and institution_name) is counted only for rows without
a match; up to scimapR 0.4.0 the counts followed institution_id. To
count by the vocabulary, put its names first:
a <- corpus$authorships corpus$authorships$institution_match <- dplyr::coalesce(a$institution_name, a$institution_match)
A tibble, one row per entity, sorted by credit descending:
Entity identifier.
Human-readable name (falls back to entity_id).
Number of distinct works the entity appears on (this is the full count and is identical for both methods).
Output credit: n_works under "full"; the sum of
per-work fractional shares under "fractional".
Citation impact attributed to the entity:
summed cited_by_count under "full"; summed fractional-share
weighted cited_by_count under "fractional".
Type-stable: an empty/inapplicable corpus returns a 0-row tibble with these columns.
sm_metric_summary(), sm_affiliation_match()
Other counting:
sm_citation_maturity(),
sm_citation_window(),
sm_metric_summary()
corpus <- sm_example_corpus(n_works = 30, seed = 1) sm_count(corpus, method = "fractional", level = "author") # Institutions: match the affiliation strings with a dictionary of your # institutions first, then count a <- corpus$authorships corpus$authorships$raw_affiliation <- ifelse( a$position == 1L, "Unit North, Northtown", "Department of Surgery, Unit South" ) dict <- list("Unit North" = "unit north", "Unit South" = "unit south") matched <- sm_affiliation_match(corpus, patterns = dict) sm_count(matched, method = "fractional", level = "institution")corpus <- sm_example_corpus(n_works = 30, seed = 1) sm_count(corpus, method = "fractional", level = "author") # Institutions: match the affiliation strings with a dictionary of your # institutions first, then count a <- corpus$authorships corpus$authorships$raw_affiliation <- ifelse( a$position == 1L, "Unit North, Northtown", "Department of Surgery, Unit South" ) dict <- list("Unit North" = "unit north", "Unit South" = "unit south") matched <- sm_affiliation_match(corpus, patterns = dict) sm_count(matched, method = "fractional", level = "institution")
Computes recall, precision, and F1 of an sm_corpus against an external
ground-truth reference (a manual tracker, an ORCID works set, an
institutional-repository export, or another sm_corpus). This is the core
primitive for a coverage / completeness audit: "how much of what we should
have did we actually capture, and how much of what we captured is real?"
Matching is by normalised DOI with a fuzzy-title fallback (see
sm_reconcile() for the shared matching engine). Full match provenance is
retained so every decision can be inspected.
sm_coverage_audit( corpus, reference, by = NULL, match = c("doi_then_title", "doi", "title"), threshold = 0.9, index_table = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_coverage' print(x, ...) ## S3 method for class 'sm_coverage' summary(object, ...) ## S3 method for class 'sm_coverage' autoplot(object, dim = NULL, ...)sm_coverage_audit( corpus, reference, by = NULL, match = c("doi_then_title", "doi", "title"), threshold = 0.9, index_table = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_coverage' print(x, ...) ## S3 method for class 'sm_coverage' summary(object, ...) ## S3 method for class 'sm_coverage' autoplot(object, dim = NULL, ...)
corpus |
An |
reference |
The ground truth: an |
by |
Optional character vector of breakdown dimensions. A name is
either one of the named dimensions |
match |
Matching strategy: |
threshold |
Minimum Jaro-Winkler title similarity ( |
index_table |
Optional journal index master list (same contract as
|
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
object |
An |
dim |
Which breakdown dimension to plot (defaults to the first available). If no breakdowns exist, a recall/precision summary bar is drawn. |
Named dimensions. "year" uses the publication years. "source" uses
the source names of an sm_corpus (sources$display_name, or
works$source_id when the corpus has no source table) and, in a data
frame, the first of the columns source, source_title, journal, so
and source_id, in this order (names matched case-insensitively); a
source_id column holds ids, whose levels do not join the names of the
corpus side. "affiliation" uses, in a data frame, the first of the
columns affiliation, affiliations, institution, country and
country_code, in this order. A data-frame cell holds one value, so an
item reported by two units takes two rows; a list column gives each of
its values. Missing and blank values have no level. A named dimension
that neither side can resolve gives a warning and no breakdown rows.
Only "affiliation" joins a tracker's units with the institutions of the
corpus: by = "institution" names the column itself, which the corpus
side has only if works has a column of that name.
Affiliation levels of a corpus. For an sm_corpus (the corpus under
audit, or a reference given as one), "affiliation" labels the
authorships from the first of these authorships columns that has a
value in at least one row: institution_match (from
sm_affiliation_match()), institution_name (from
sm_attribute_institution()), institution_id (named by
corpus$institutions$display_name when that table lists the id, else
the id itself) and, as a last resort, country_code. One column serves
every row, so the levels of a side are all of one kind, and an authorship
without a value in that column has no level. A message names the column
used on each side, and a warning says when a side falls back to country
codes, whose levels do not join institution names. Because
sm_attribute_institution() runs sm_affiliation_match() first,
institution_name is used only when institution_match is absent or
empty; the Details of sm_attribute_institution() show how to break the
coverage down by its vocabulary instead.
sm_count(level = "institution") reads the same columns in the
same order, but row by row, and counts the raw affiliation string where
none has a value instead of falling back to countries; up to scimapR
0.4.0 it used, for every row, the first of institution_id,
institution_match and the raw strings (matched with
sm_affiliation_dict) that had a value in any row.
Full counting. A record belongs to every level of its values, so a
work with authors at two institutions counts in both slices, and the
per-level counts of a dimension can add up to more than n_corpus or
n_reference. The overall recall and precision count each record once.
An sm_coverage S3 object (a list) with components:
Reference records found in the corpus / total reference records. Each reference row is looked up on its own, so rows that report the same item (one paper reported by two units) are all found.
Matched corpus records / total corpus records. Each corpus record pairs with at most one reference row.
Harmonic mean of recall and precision.
Integer record counts.
Reference records found in the corpus (the numerator of recall).
Corpus records paired with a reference record
(the numerator of precision); equal to n_matched unless reference
rows share a corpus record.
Unmatched counts.
Tibble with one row per corpus record: corpus_id,
reference_id, match_type ("doi"/"title"/"none"),
match_score. When index_table is supplied, also issn,
indexable (logical), and indexed_title.
Tibble with one row per reference record:
reference_id, corpus_id (the corpus record it was found in),
match_type and match_score; the recall-side counterpart of
matches.
Tibble of corpus records absent from the reference.
Tibble of reference records absent from the corpus.
A single flat tibble (dimension, level,
n_reference, n_matched, recall) across all by dimensions,
from the reference side. Use sm_coverage_breakdowns() to
access/filter it, also with the corpus-side counts and precision.
The legacy named list of per-dimension tibbles
(slice, n_reference, n_matched, recall). Retained for one
release; prefer breakdowns.
When index_table is supplied, a summary tibble of
indexable vs non-indexable record counts; otherwise NULL.
print returns x invisibly.
summary returns a one-row tibble of headline metrics: recall,
precision, f1, n_corpus, n_reference, n_matched,
n_corpus_only, n_reference_only and n_corpus_matched, so that
n_matched + n_reference_only gives n_reference and
n_corpus_matched + n_corpus_only gives n_corpus.
autoplot returns a ggplot object.
sm_reconcile(), sm_journal_in_index(),
sm_coverage_breakdowns(), sm_affiliation_match()
Other coverage:
sm_coverage_breakdowns(),
sm_journal_in_index(),
sm_match_types(),
sm_reconcile()
corpus <- sm_example_corpus(n_works = 30, seed = 1) # Pretend the reference is the corpus minus a few works, plus an extra one ref <- corpus$works[1:25, c("work_id", "doi", "title", "year")] cov <- sm_coverage_audit(corpus, ref, by = "year") cov # Any column of the reference or of the works is a dimension too ref$unit <- rep(c("Unit A", "Unit B"), length.out = nrow(ref)) cov <- sm_coverage_audit(corpus, ref, by = c("unit", "type"), match = "doi") sm_coverage_breakdowns(cov)corpus <- sm_example_corpus(n_works = 30, seed = 1) # Pretend the reference is the corpus minus a few works, plus an extra one ref <- corpus$works[1:25, c("work_id", "doi", "title", "year")] cov <- sm_coverage_audit(corpus, ref, by = "year") cov # Any column of the reference or of the works is a dimension too ref$unit <- rep(c("Unit A", "Unit B"), length.out = nrow(ref)) cov <- sm_coverage_audit(corpus, ref, by = c("unit", "type"), match = "doi") sm_coverage_breakdowns(cov)
Accessor returning the per-dimension coverage breakdowns of an
sm_coverage_audit() result as a single flat (long) tibble, optionally
filtered to one dimension.
sm_coverage_breakdowns(x, dimension = NULL, tidy = TRUE)sm_coverage_breakdowns(x, dimension = NULL, tidy = TRUE)
x |
An |
dimension |
Optional dimension name (e.g. |
tidy |
Logical (default |
This accessor honours the package's accessor return-type stability contract
(see scimapR-stability): with tidy = TRUE the column set is guaranteed
and will only change via a lifecycle deprecation.
The recall columns (n_reference, n_matched, recall) come from the
reference side and the precision columns (n_corpus, precision) from
the corpus side, joined on (dimension, level). A level found on the
reference side only has n_corpus, precision and f1 NA; a level
found on the corpus side only has n_reference, n_matched, recall and
f1 NA. Under full counting (see the Details of sm_coverage_audit())
the per-level counts of a dimension can add up to more than the record
totals. tidy = FALSE, print() and autoplot() show the same
reference-side counts.
A tibble (see tidy). Type-stable: a 0-row tibble with the
documented columns when there are no breakdowns. Slices present on only one
side (corpus vs reference) carry NA in the other side's counts/metric.
sm_coverage_audit(), scimapR-stability
Other coverage:
sm_coverage_audit(),
sm_journal_in_index(),
sm_match_types(),
sm_reconcile()
corpus <- sm_example_corpus(n_works = 30, seed = 1) ref <- corpus$works[1:25, c("work_id", "doi", "title", "year")] cov <- sm_coverage_audit(corpus, ref, by = "year") sm_coverage_breakdowns(cov, dimension = "year")corpus <- sm_example_corpus(n_works = 30, seed = 1) ref <- corpus$works[1:25, c("work_id", "doi", "title", "year")] cov <- sm_coverage_audit(corpus, ref, by = "year") sm_coverage_breakdowns(cov, dimension = "year")
Remove duplicate works, preferring the record with more complete data. Ties keep the first record. Kept records stay in their input order, with only their child rows and referenced lookup records.
sm_dedupe(corpus, by = "doi", verbose = TRUE)sm_dedupe(corpus, by = "doi", verbose = TRUE)
corpus |
An |
by |
Non-empty character vector of works columns that together
identify duplicates. Default |
verbose |
Logical; show progress? |
An sm_corpus object.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
Fits a difference-in-differences (DiD) model comparing the yearly outcome
series of a treated institution set against a control set, before and after
intervention_year. Group membership is resolved via the
institution-attribution columns from sm_affiliation_match() /
sm_attribute_institution().
sm_did( corpus, treated, control, intervention_year, outcome = c("count", "share_q1", "cnci", "leadership"), family = NULL, lag = NULL, institution_aware = FALSE, lead_role = c("corresponding", "first", "last", "first_or_last", "any_lead"), shared = c("keep", "exclude", "lead"), min_cell = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_did' print(x, ...) ## S3 method for class 'sm_did' summary(object, ...) ## S3 method for class 'sm_did' autoplot(object, ...)sm_did( corpus, treated, control, intervention_year, outcome = c("count", "share_q1", "cnci", "leadership"), family = NULL, lag = NULL, institution_aware = FALSE, lead_role = c("corresponding", "first", "last", "first_or_last", "any_lead"), shared = c("keep", "exclude", "lead"), min_cell = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_did' print(x, ...) ## S3 method for class 'sm_did' summary(object, ...) ## S3 method for class 'sm_did' autoplot(object, ...)
corpus |
An |
treated, control
|
Character vectors of institution labels (matched
against |
intervention_year |
Year the intervention takes effect: one whole
number such as |
outcome |
One of |
family |
Optional GLM family; defaults to Gaussian (DiD is an additive contrast). Override for, e.g., a Poisson count model; a Poisson fit warns when the counts are overdispersed. |
lag |
Citation-maturity lag in years (default |
institution_aware |
For |
lead_role |
The author role that leads a work, as in |
shared |
What to do with works that belong to both groups, such as
works co-authored by a treated and a control institution: |
min_cell |
For |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
object |
An |
Each group's yearly series is built as in sm_its(). For "count" a
group's years without works count 0, over every year from the first to
the last publication year of the corpus. For the other outcomes a year in
which a group has works but no usable value is dropped from that group's
series, with a warning, and listed in dropped_years. For "cnci" the
citation-immature years are then excluded from both series, by the same
rule as in sm_its(), and listed in provisional_years; up to scimapR
0.4.0 sm_did() kept them. Each group needs 4 or more years, with at
least one before intervention_year and one from it on; otherwise
sm_did() aborts. The confidence interval uses the reference
distribution of the p-value (t with the residual degrees of freedom for
the default Gaussian family); no correction for autocorrelation is
applied.
A work belongs to a group when one of its authorships carries one of the
group's labels, so a work co-authored by both groups belongs to both.
Such shared works pull the two series together and the estimate toward
0. sm_did() warns when there are shared works and shared is not
set, and records them in shared_works. For "leadership" the
denominators leave out the works whose lead is unknown, as in
sm_its().
An sm_did S3 object with components: model, did_estimate
(a one-row tibble: estimate, std.error, conf.low, conf.high,
p.value), series (group-by-year tibble: year, group, value,
n_works), dropped_years (tibble: group, year; the group-years
left out for lack of a usable value), provisional_years (integer;
the citation-immature years excluded for "cnci"), shared_works
(tibble: work_id, series; the works of both groups and where they
went: "both", "treated", "control" or "none"), shared (the
policy), leadership (the leadership definition, as in sm_its(),
with institutions the labels of each group when institution_aware;
NULL for the other outcomes), impact_source (where the impact
values of "cnci" come from, as in sm_its(); NA for the other
outcomes), and metadata (outcome, intervention_year, family,
lag).
print returns x invisibly.
summary returns the DiD estimate tibble.
autoplot returns a ggplot (parallel trends + divergence).
Other causal:
sm_its(),
sm_synth()
corpus <- sm_example_corpus(n_works = 120, seed = 1) # Tag two institution groups for illustration: each work belongs to one a <- corpus$authorships group <- rep(c("Inst A", "Inst B"), length.out = nrow(corpus$works)) corpus$authorships$institution_name <- group[match(a$work_id, corpus$works$work_id)] did <- sm_did(corpus, treated = "Inst A", control = "Inst B", intervention_year = 2020, outcome = "count") didcorpus <- sm_example_corpus(n_works = 120, seed = 1) # Tag two institution groups for illustration: each work belongs to one a <- corpus$authorships group <- rep(c("Inst A", "Inst B"), length.out = nrow(corpus$works)) corpus$authorships$institution_name <- group[match(a$work_id, corpus$works$work_id)] did <- sm_did(corpus, treated = "Inst A", control = "Inst B", intervention_year = 2020, outcome = "count") did
Produces a detailed comparison between two sm_corpus objects, reporting
works added, removed, and changed; author differences; reference
differences; and screening changes. This is useful for auditing how a
corpus evolved between snapshots or after a refresh.
This function compares strictly by internal work_id. For content-based
reconciliation across corpora that do not share identifiers (matching by
normalised DOI with a fuzzy title fallback), use sm_reconcile().
sm_diff_corpora(corpus1, corpus2, call = rlang::caller_env()) ## S3 method for class 'sm_corpus_diff' print(x, ...)sm_diff_corpora(corpus1, corpus2, call = rlang::caller_env()) ## S3 method for class 'sm_corpus_diff' print(x, ...)
corpus1 |
An |
corpus2 |
An |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
An sm_corpus_diff S3 object (a list) with components:
Tibble of works in corpus2 but not corpus1.
Tibble of works in corpus1 but not corpus2.
Tibble of works present in both but with differing fields.
A one-row summary tibble with counts.
Hash of corpus1.
Hash of corpus2.
Other reproducibility:
sm_certificate(),
sm_cite_corpus(),
sm_hash_corpus(),
sm_provenance(),
sm_register_input(),
sm_snapshot()
c1 <- sm_example_corpus(seed = 1L) c2 <- sm_example_corpus(seed = 2L) d <- sm_diff_corpora(c1, c2) print(d)c1 <- sm_example_corpus(seed = 1L) c2 <- sm_example_corpus(seed = 2L) d <- sm_diff_corpora(c1, c2) print(d)
Loads a previously saved embeddings matrix from an RDS file and attaches it to the corpus. Only embeddings for works present in the corpus are loaded; mismatches are reported.
The loaded embeddings replace any the corpus had, and so does the record
of their model (corpus$metadata$embedding, see sm_corpus()): the
cache's record when it describes the cached matrix, else none, so the
model of embeddings from an older cache or a bare matrix counts as not
recorded ("unknown" in sm_certificate()).
sm_embed_load(corpus, path, call = rlang::caller_env())sm_embed_load(corpus, path, call = rlang::caller_env())
corpus |
An sm_corpus object. |
path |
Character; file path to read embeddings from (an |
call |
Caller environment for error reporting. |
The input corpus with corpus$embeddings and
corpus$metadata$embedding updated. Works without a cached row get no
row, so the clustering functions give them no cluster (NA).
Other embedding:
sm_embed_save(),
sm_embed_works()
## Not run: corpus <- sm_example_corpus(with_embeddings = FALSE) corpus <- sm_embed_load(corpus, "my_embeddings.rds") dim(corpus$embeddings) ## End(Not run)## Not run: corpus <- sm_example_corpus(with_embeddings = FALSE) corpus <- sm_embed_load(corpus, "my_embeddings.rds") dim(corpus$embeddings) ## End(Not run)
Saves the embeddings matrix from an sm_corpus to an RDS file on disk.
This allows caching expensive embedding computations for later reuse
with sm_embed_load(). The cache keeps the record of the model behind
the embeddings (corpus$metadata$embedding, see sm_corpus()) when the
corpus has one.
sm_embed_save(corpus, path, call = rlang::caller_env())sm_embed_save(corpus, path, call = rlang::caller_env())
corpus |
An sm_corpus object with a non- |
path |
Character; file path to write the embeddings to. Should end in
|
call |
Caller environment for error reporting. |
The input corpus (invisibly).
Other embedding:
sm_embed_load(),
sm_embed_works()
## Not run: corpus <- sm_example_corpus(with_embeddings = TRUE) sm_embed_save(corpus, "my_embeddings.rds") ## End(Not run)## Not run: corpus <- sm_example_corpus(with_embeddings = TRUE) sm_embed_save(corpus, "my_embeddings.rds") ## End(Not run)
Generates dense vector embeddings for works in an sm_corpus using
pretrained transformer models in Python. The default model, specter2,
loads the SPECTER2 adapter with the Python package adapters; the other
models load with sentence-transformers. Requires reticulate and
a Python installation with the packages the chosen model needs.
sm_embed_works( corpus, model = c("specter2", "scincl", "scibert", "minilm-l6", "mpnet", "multilingual-mpnet", "multilingual-e5"), text = c("title_abstract", "title", "abstract"), batch_size = 32L, cache_dir = tools::R_user_dir("scimapR", "cache"), python = NULL, install_deps = FALSE, verbose = TRUE, call = rlang::caller_env() )sm_embed_works( corpus, model = c("specter2", "scincl", "scibert", "minilm-l6", "mpnet", "multilingual-mpnet", "multilingual-e5"), text = c("title_abstract", "title", "abstract"), batch_size = 32L, cache_dir = tools::R_user_dir("scimapR", "cache"), python = NULL, install_deps = FALSE, verbose = TRUE, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
model |
Character; the embedding model to use. One of
|
text |
Character; which text field(s) to embed. One of
|
batch_size |
Integer; batch size for the transformer model. Defaults
to |
cache_dir |
Character; directory to cache downloaded models. Defaults to the scimapR user cache directory. |
python |
Character or |
install_deps |
Logical; if |
verbose |
Logical; if |
call |
Caller environment for error reporting. |
Models and their Hugging Face models:
"specter2": allenai/specter2, an adapter on allenai/specter2_base;
"scincl": malteos/scincl;
"scibert": allenai/scibert_scivocab_uncased;
"minilm-l6": sentence-transformers/all-MiniLM-L6-v2;
"mpnet": sentence-transformers/all-mpnet-base-v2;
"multilingual-mpnet":
sentence-transformers/paraphrase-multilingual-mpnet-base-v2, trained
on more than 50 languages;
"multilingual-e5": intfloat/multilingual-e5-base, trained on about
100 languages.
The first five are trained on English text. "specter2" loads with the
Python package adapters, the others with sentence-transformers.
"specter2" follows its model card: the base model with the proximity
adapter, title and abstract joined by the separator token [SEP]
without spaces, at most 512 tokens, and the first token of the last
layer as the embedding. It needs the Python packages torch,
transformers and adapters; the other models need
sentence-transformers. A missing package stops the function with an
error naming it, and so does a model that does not load; it never falls
back to another model.
The other models join title and abstract with a space. For
"multilingual-e5" every text starts with "query: ", the prefix its
model card gives for clustering and for using the embeddings as
features; its embeddings have unit length. "multilingual-mpnet" reads
at most 128 tokens, so it sees the title and the start of a long
abstract only. "scibert" has no sentence-transformers configuration,
so sentence-transformers averages its token vectors, with a warning.
When more than 10% of the works with a known language
(works$language: names such as "German", ISO 639-1 codes such as
"de" or ISO 639-2 codes such as "ger") are not in English and an
English-only model is chosen, a message (class
scimapR_embed_multilingual) suggests a multilingual model; it changes
nothing else.
Works without text to embed (with "title_abstract" neither a title
nor an abstract, with "title" or "abstract" no such field; blank
text counts as none) would all get the same vector and become each
other's nearest neighbours. They are left out, with one warning (class
scimapR_embed_no_text) giving their number: they get no row in
corpus$embeddings, so the clustering functions give them no cluster
(NA), and sm_network_semantic() and sm_plot_landscape() leave
them out, as they do for works missing from a cache loaded with
sm_embed_load().
corpus$metadata$embedding records what produced the embeddings: the
model, its Hugging Face id and, when the local model snapshot in
cache_dir shows it, its revision, the text used, the input prefix and
the versions of the Python packages (see sm_corpus()).
sm_certificate() reports it in embedding_info.
Python and its packages are soft dependencies (reticulate is in Suggests). If they are unavailable, the function aborts with an informative message.
The input corpus with corpus$embeddings replaced by a numeric
matrix with one row per embedded work and one column per dimension, and
with corpus$metadata$embedding recording the model. Row names are the
work_ids of the embedded works: every work that has text to embed
(see Details).
Other embedding:
sm_embed_load(),
sm_embed_save()
## Not run: corpus <- sm_example_corpus(with_embeddings = FALSE) corpus <- sm_embed_works(corpus, model = "minilm-l6") dim(corpus$embeddings) corpus$metadata$embedding$model_id # a corpus with works in several languages corpus <- sm_embed_works(corpus, model = "multilingual-mpnet") ## End(Not run)## Not run: corpus <- sm_example_corpus(with_embeddings = FALSE) corpus <- sm_embed_works(corpus, model = "minilm-l6") dim(corpus$embeddings) corpus$metadata$embedding$model_id # a corpus with works in several languages corpus <- sm_embed_works(corpus, model = "multilingual-mpnet") ## End(Not run)
Look up DOIs in the corpus via the
Altmetric API and add attention scores,
Mendeley reader counts, and social media metrics to the works table.
This enricher is idempotent: re-running it updates existing Altmetric data rather than duplicating it.
sm_enrich_altmetric( corpus, api_key = Sys.getenv("ALTMETRIC_API_KEY"), verbose = TRUE, call = rlang::caller_env() )sm_enrich_altmetric( corpus, api_key = Sys.getenv("ALTMETRIC_API_KEY"), verbose = TRUE, call = rlang::caller_env() )
corpus |
An |
api_key |
Altmetric API key. Read from |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object with added altmetric_score,
mendeley_readers, twitter_count, and news_count columns in the
works table, plus new provenance rows.
Other enrichers:
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_altmetric(corpus) ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_altmetric(corpus) ## End(Not run)
Look up works in the corpus via OpenAlex or PubMed to retrieve
associated concepts, topics, or MeSH terms and add them to the
concepts table.
This enricher is idempotent: re-running it updates existing concept data rather than duplicating rows.
sm_enrich_concepts( corpus, source = c("openalex", "mesh"), verbose = TRUE, api_key = Sys.getenv("OPENALEX_API_KEY"), call = rlang::caller_env() )sm_enrich_concepts( corpus, source = c("openalex", "mesh"), verbose = TRUE, api_key = Sys.getenv("OPENALEX_API_KEY"), call = rlang::caller_env() )
corpus |
An |
source |
One of |
verbose |
Print progress messages? |
api_key |
OpenAlex API key, sent in the authorization header. |
call |
Caller environment for error reporting. |
An sm_corpus object with updated concepts table and new
provenance rows for enriched works. Failed OpenAlex lookups warn and
stop at a rate limit, preserving earlier successful results.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_concepts(corpus, source = "openalex") ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_concepts(corpus, source = "openalex") ## End(Not run)
Looks the works of a corpus up on OpenAlex by
identifier and adds OpenAlex's impact fields: the field-weighted
citation impact (fwci), the citation percentile
(citation_percentile, 0 to 1, with the flags is_top_10pct and
is_top_1pct), the date of these values (citations_as_of) and the
yearly citation counts (the corpus element citations_by_year). The
corpus can come from any source, such as Web of Science files or
sm_corpus_from_tables(); only identifiers are sent.
sm_enrich_openalex_impact( corpus, by = c("openalex_id", "doi"), batch_size = 50L, api_key = Sys.getenv("OPENALEX_API_KEY"), verbose = TRUE, call = rlang::caller_env() )sm_enrich_openalex_impact( corpus, by = c("openalex_id", "doi"), batch_size = 50L, api_key = Sys.getenv("OPENALEX_API_KEY"), verbose = TRUE, call = rlang::caller_env() )
corpus |
An |
by |
The identifiers to look works up by, in order of preference:
one or both of |
batch_size |
Number of identifiers per request, from 1 to 100
(default |
api_key |
OpenAlex API key. Read from the |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
Each work is looked up by the first identifier of by that it has: its
OpenAlex id (works$openalex_id, shortened to the W number) or its
DOI (normalised on both sides, so https://doi.org/ prefixes and case
do not matter). Works without either are counted and left as they are.
A DOI that contains , or | cannot be written into an OpenAlex
filter; such works are left out and counted too. The identifiers are
sent in batches of batch_size (at most 100, the number of values an
OpenAlex filter takes), with only the fields needed (select). When
OpenAlex has several records for one DOI, the record with the most
yearly citations is used, then the one with the lowest OpenAlex id.
The fields are joined with sm_materialise() (overwrite = TRUE). All
five values of a work found come from its OpenAlex record, NA where
the record has none, so a new run replaces the values of an earlier run
or of another source, and the values, their date (citations_as_of)
and the yearly counts of a work always belong to one record. The works
not found keep their values. Only these five works columns change;
cited_by_count and the other columns keep the values of their source.
The citations_by_year rows of every work found are replaced by the
counts OpenAlex has now; the rows of the other works stay.
metadata$last_refresh is not changed.
Every work found gets a provenance row with the source
"openalex_impact", its OpenAlex id in source_id_external, a short
description of the call in query and the kind "enrichment", so
sm_rebuild_from_cert() never replays the step as a search. Neither
the API key nor any request URL or filter is stored.
On HTTP 429 (the request budget is used up) the function stops at once
and joins nothing, not even the batches fetched before: a partial
enrichment would compare works with OpenAlex values against works
without them. The error
(class scimapR_openalex_rate_limit) names the reset time, the
remaining budget OpenAlex reports and the key setting. Any other failed
request stops it too (class scimapR_openalex_failed).
The corpus with the works columns fwci,
citation_percentile, is_top_10pct, is_top_1pct and
citations_as_of (see sm_corpus()), the element citations_by_year
and a provenance row for each work found. The columns and the element
are added even when OpenAlex has none of the works looked up (with
NA and no rows where the corpus had none). A corpus without works,
or without works that have an identifier, is returned unchanged.
OpenAlex expects a free API key. Requests without one share one small
daily budget with everyone on the same network address; once it is used
up, OpenAlex answers HTTP 429 (too many requests) until the budget
resets at midnight UTC. Set the key in the
OPENALEX_API_KEY environment variable (for example in ~/.Renviron)
or pass it as api_key. It is sent in the Authorization header,
never in the URL, and it is stored nowhere in the corpus.
On HTTP 429 the function stops at once with an error of class
scimapR_openalex_rate_limit instead of waiting: the message gives the
reset time and the remaining budget that OpenAlex reports (the fields
reset_at and remaining of the condition) and says whether a key was
sent. Only a wait of up to two seconds (OpenAlex also limits requests per
second) is waited out, once. Other failed requests stop with class
scimapR_openalex_failed; both classes inherit from
scimapR_openalex_error. With engine = "openalexR" the
openalexR package (version 3.1.1 or later) sends the requests: it
gets the key too and its own retries are switched off, but it does not
pass on the response headers, so the reset time and the budget are not
known.
sm_fetch_openalex(), sm_citation_window()
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) fix <- sm_enrich_openalex_impact(fix, by = "doi") sm_citation_window(fix, window = 3) ## End(Not run)## Not run: fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) fix <- sm_enrich_openalex_impact(fix, by = "doi") sm_citation_window(fix, window = 3) ## End(Not run)
Look up DOIs using the
OpenCitations Index API
and add outgoing references. DOI and OpenAlex identifiers link these
references to works in the corpus. The separate incoming citation count
replaces works$cited_by_count.
This enricher is idempotent: re-running it updates existing citation data rather than duplicating it.
sm_enrich_opencitations(corpus, verbose = TRUE, call = rlang::caller_env())sm_enrich_opencitations(corpus, verbose = TRUE, call = rlang::caller_env())
corpus |
An |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object with updated cited_by_count in the
works table, outgoing references linked where possible, and new
provenance rows for works with usable responses.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_opencitations(corpus) ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_opencitations(corpus) ## End(Not run)
Look up authors in the corpus who have ORCID identifiers and fetch
additional profile information from the
ORCID Public API. Updates the authors table
with display name alternatives and the authorships table with
affiliation data.
This enricher is idempotent: re-running it updates existing ORCID data rather than duplicating it.
sm_enrich_orcid(corpus, verbose = TRUE, call = rlang::caller_env())sm_enrich_orcid(corpus, verbose = TRUE, call = rlang::caller_env())
corpus |
An |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object with updated authors and authorships
tables, plus new provenance rows.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_orcid(corpus) ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_orcid(corpus) ## End(Not run)
Check works in the corpus against the Retraction Watch database (via the Crossref update notices and OpenAlex) to identify retracted publications.
Updates the is_retracted and retraction_date columns in the
works table.
This enricher is idempotent: re-running it updates existing retraction data rather than duplicating it.
sm_enrich_retraction( corpus, verbose = TRUE, api_key = Sys.getenv("OPENALEX_API_KEY"), mailto = Sys.getenv("SCIMAPR_MAILTO"), call = rlang::caller_env() )sm_enrich_retraction( corpus, verbose = TRUE, api_key = Sys.getenv("OPENALEX_API_KEY"), mailto = Sys.getenv("SCIMAPR_MAILTO"), call = rlang::caller_env() )
corpus |
An |
verbose |
Print progress messages? |
api_key |
OpenAlex API key, sent in the authorization header. |
mailto |
Contact address sent only to Crossref. |
call |
Caller environment for error reporting. |
An sm_corpus object with updated is_retracted and
retraction_date columns in the works table, plus new provenance
rows for checked works only. Failed requests warn; when no work could
be checked they error without changing the corpus. Missing records are
reported separately. Existing retraction flags are never cleared.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_ror(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_retraction(corpus) ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_retraction(corpus) ## End(Not run)
Look up institutions in the corpus via the
Research Organization Registry (ROR) API and enrich
the institutions table with standardized metadata including country,
region, type, and income tier.
Works by matching raw affiliation strings against the ROR affiliation matching API, or by directly resolving existing ROR identifiers.
This enricher is idempotent: re-running it updates existing ROR data rather than duplicating it.
sm_enrich_ror(corpus, verbose = TRUE, call = rlang::caller_env())sm_enrich_ror(corpus, verbose = TRUE, call = rlang::caller_env())
corpus |
An |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object with updated institutions and
authorships tables, plus new provenance rows.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_specter(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_ror(corpus) ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_ror(corpus) ## End(Not run)
Fetch pre-computed SPECTER v2 embeddings from the
Semantic Scholar API for works in
the corpus and attach them as the embeddings matrix.
SPECTER embeddings are 768-dimensional vectors useful for computing document similarity, clustering, and visualisation. No Python installation is required – this function retrieves pre-computed vectors from the API.
This enricher is idempotent: works that already have embeddings from the same source are skipped, so a second call fills only the gaps.
sm_enrich_specter( corpus, api_key = Sys.getenv("SEMANTIC_SCHOLAR_API_KEY"), replace = FALSE, mix_models = FALSE, verbose = TRUE, call = rlang::caller_env() )sm_enrich_specter( corpus, api_key = Sys.getenv("SEMANTIC_SCHOLAR_API_KEY"), replace = FALSE, mix_models = FALSE, verbose = TRUE, call = rlang::caller_env() )
corpus |
An |
api_key |
Semantic Scholar API key. Read from
|
replace |
Logical; replace existing embeddings of another model
(or of an unrecorded one) by SPECTER v2 vectors? Defaults to |
mix_models |
Logical; keep existing embeddings of another model
(or of an unrecorded one) and add SPECTER v2 vectors for the other
works? Defaults to |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
The function asks Semantic Scholar for its SPECTER v2 vectors (the API
field embedding.specter_v2; the API's default is SPECTER v1) and
records them in corpus$metadata$embedding (see sm_corpus()) with
the model id "embedding.specter_v2" and, as revision, the model
version that Semantic Scholar reports. Semantic Scholar does not say
which revision of the model or which text it used, so these vectors
count as a model of their own, also next to embeddings from
sm_embed_works(model = "specter2").
A corpus that already has embeddings of another model, or of a model
that is not recorded (such as embeddings made by earlier versions of
scimapR), would get a matrix whose rows come from two models, and
similarities between such rows mean nothing. The function therefore
stops (class scimapR_embedding_model_mismatch) unless you choose:
replace = TRUE discards the existing embeddings, with a message, and
fetches SPECTER v2 vectors for every work with a DOI; mix_models = TRUE keeps them and adds SPECTER v2 vectors for the works without an
embedding, and the record then names the model "mixed". A mix needs
embeddings of the same width.
Requests go out in batches of 100 DOIs. A batch that Semantic Scholar
answers with HTTP 429 (too many requests) is tried once more after a
short wait; a batch that still fails is skipped, and one warning (class
scimapR_specter_failed) gives the number of failed batches.
An sm_corpus object with an embeddings matrix, its record
in metadata$embedding, plus new provenance rows of the kind
"enrichment", which sm_rebuild_from_cert() does not replay.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_unpaywall()
## Not run: corpus <- sm_example_corpus(with_embeddings = FALSE) corpus <- sm_enrich_specter(corpus) corpus$metadata$embedding$model_id # a corpus with embeddings of another model corpus <- sm_embed_works(corpus, model = "mpnet") corpus <- sm_enrich_specter(corpus, replace = TRUE) ## End(Not run)## Not run: corpus <- sm_example_corpus(with_embeddings = FALSE) corpus <- sm_enrich_specter(corpus) corpus$metadata$embedding$model_id # a corpus with embeddings of another model corpus <- sm_embed_works(corpus, model = "mpnet") corpus <- sm_enrich_specter(corpus, replace = TRUE) ## End(Not run)
Look up DOIs in the corpus via the
Unpaywall API and add open-access status
and best OA URL to the works table.
This enricher is idempotent: re-running it updates existing OA data
rather than duplicating it. Requires a mailto address for API access.
sm_enrich_unpaywall( corpus, mailto = Sys.getenv("SCIMAPR_MAILTO"), verbose = TRUE, call = rlang::caller_env() )sm_enrich_unpaywall( corpus, mailto = Sys.getenv("SCIMAPR_MAILTO"), verbose = TRUE, call = rlang::caller_env() )
corpus |
An |
mailto |
Email address for the Unpaywall API. Read from
|
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object with updated oa_status and oa_url
columns in the works table, plus new provenance rows.
Other enrichers:
sm_enrich_altmetric(),
sm_enrich_concepts(),
sm_enrich_openalex_impact(),
sm_enrich_opencitations(),
sm_enrich_orcid(),
sm_enrich_retraction(),
sm_enrich_ror(),
sm_enrich_specter()
## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_unpaywall(corpus) ## End(Not run)## Not run: corpus <- sm_example_corpus() corpus <- sm_enrich_unpaywall(corpus) ## End(Not run)
Creates a realistic synthetic corpus for examples, testing, and demonstration. Works have plausible titles, DOIs, years, and citations. Authors are clustered into collaborating groups.
sm_example_corpus( n_works = 200L, n_authors = 80L, year_range = c(2015L, 2024L), n_clusters = 5L, with_embeddings = TRUE, with_screening = FALSE, with_trajectory_seed = TRUE, seed = 42L )sm_example_corpus( n_works = 200L, n_authors = 80L, year_range = c(2015L, 2024L), n_clusters = 5L, with_embeddings = TRUE, with_screening = FALSE, with_trajectory_seed = TRUE, seed = 42L )
n_works |
Number of works to generate. |
n_authors |
Number of authors to generate. |
year_range |
Two-element integer vector of year range. |
n_clusters |
Number of topic clusters. |
with_embeddings |
Logical; generate random embeddings? |
with_screening |
Logical; generate screening decisions? |
with_trajectory_seed |
Logical; create a prolific author for trajectory demonstration? |
seed |
Random seed for reproducibility. |
An sm_corpus object.
Other example:
sm_example_files()
corpus <- sm_example_corpus() print(corpus)corpus <- sm_example_corpus() print(corpus)
A pre-built synthetic corpus with 200 works, 80 authors, 5 clusters,
and embeddings. Created with sm_example_corpus(seed = 42).
sm_example_dbsm_example_db
An sm_corpus S3 object.
Synthetically generated.
data(sm_example_db) print(sm_example_db)data(sm_example_db) print(sm_example_db)
Returns the path to bundled example bibliographic files in the package's
inst/extdata directory.
sm_example_files(name = NULL)sm_example_files(name = NULL)
name |
Name of the example file (without path). If |
A character string path, or a character vector of available files.
Three files form a synthetic fixture shaped like a corpus that was built
from a Web of Science export of three military-medical units and their
civilian partners. Everything in it is invented: units, cities, people,
journals, titles and DOIs (10.9999/...). The script
data-raw/realuse-fixture.R in the package sources rebuilds the files.
example_realuse_corpus.rdsAn sm_corpus built with
sm_corpus_from_tables(): 150 works from 2016 to 2026, with authors,
Web of Science style addresses (ten of them German strings with
non-ASCII letters), one corresponding author per work, sources, Web
of Science style cited references and keyword and category concepts.
Extra works columns are quartile (NA for every 2026 work),
fwci, field (the first Web of Science category), volume,
first_page and citations_as_of; work_id holds the Web of
Science accession number. citations_by_year holds yearly citation
counts shaped like OpenAlex's, dated 2026-10-17 (citations_as_of)
for the works with a DOI: the years 2017 to 2026, without the years
that have no citations (see sm_citation_window()).
metadata$last_refresh is a numeric time stamp in seconds since
1970-01-01 UTC.
example_realuse_reference.csvA reference list in the style of
a publication tracker, with columns id, doi, title, year,
institution (the reporting unit) and category (a publication
class code). It holds 120 of the corpus works and 40 items that are
not in the corpus.
example_realuse_affiliation_dict.csvAn affiliation dictionary
in the sm_affiliation_dict format (institution, pattern,
email_domain) for sm_affiliation_match(): institutes first, the
hospital patterns anchored on its city next, a generic fallback last.
Its institution names are the institution values of the reference
list.
The CSV files are UTF-8 encoded.
Other example:
sm_example_corpus()
sm_example_files() sm_example_files("example.bib") # The real-use fixture fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) ref <- readr::read_csv(sm_example_files("example_realuse_reference.csv"), col_types = "cccicc") dict <- readr::read_csv( sm_example_files("example_realuse_affiliation_dict.csv"), col_types = "ccc" ) matched <- sm_affiliation_match(fix, dict) table(matched$authorships$institution_match, useNA = "ifany")sm_example_files() sm_example_files("example.bib") # The real-use fixture fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) ref <- readr::read_csv(sm_example_files("example_realuse_reference.csv"), col_types = "cccicc") dict <- readr::read_csv( sm_example_files("example_realuse_affiliation_dict.csv"), col_types = "ccc" ) matched <- sm_affiliation_match(fix, dict) table(matched$authorships$institution_match, useNA = "ifany")
Write corpus works in RIS format for Covidence import.
sm_export_covidence(corpus, path)sm_export_covidence(corpus, path)
corpus |
An |
path |
Output file path. |
path invisibly.
Other screening:
sm_export_rayyan(),
sm_import_rayyan(),
sm_merge_screening_decisions(),
sm_screen_prisma()
Write each corpus table as a separate CSV file in a directory, the
yearly citation counts (citations_by_year.csv) too when the corpus
has them.
sm_export_csv(corpus, dir, ...)sm_export_csv(corpus, dir, ...)
corpus |
An |
dir |
Output directory. |
... |
Additional arguments passed to |
dir invisibly.
Other export:
sm_export_cytoscape(),
sm_export_figure(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_table(),
sm_export_vosviewer(),
sm_export_zip()
corpus <- sm_example_corpus(n_works = 10) dir <- tempdir() sm_export_csv(corpus, dir)corpus <- sm_example_corpus(n_works = 10) dir <- tempdir() sm_export_csv(corpus, dir)
Write network to Cytoscape JSON format.
sm_export_cytoscape(network, path)sm_export_cytoscape(network, path)
network |
A |
path |
Output file path. |
path invisibly.
Other export:
sm_export_csv(),
sm_export_figure(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_table(),
sm_export_vosviewer(),
sm_export_zip()
corpus <- sm_example_corpus() net <- sm_network_cocitation(corpus) sm_export_cytoscape(net, tempfile(fileext = ".json"))corpus <- sm_example_corpus() net <- sm_network_cocitation(corpus) sm_export_cytoscape(net, tempfile(fileext = ".json"))
Save a ggplot to disk in multiple formats and resolutions. By default, raster formats are saved at both 300 and 600 dpi.
sm_export_figure( plot, path, format = c("png", "pdf", "svg", "tiff", "eps"), dpi = c(300, 600), width = 7, height = 5, units = c("in", "cm", "mm"), background = c("white", "transparent"), multi_dpi = TRUE )sm_export_figure( plot, path, format = c("png", "pdf", "svg", "tiff", "eps"), dpi = c(300, 600), width = 7, height = 5, units = c("in", "cm", "mm"), background = c("white", "transparent"), multi_dpi = TRUE )
plot |
A |
path |
Output file path (extension determines format if |
format |
Output format. |
dpi |
Resolution for raster formats. |
width |
Plot width. |
height |
Plot height. |
units |
Size units. |
background |
Background colour. |
multi_dpi |
Logical; write both 300 and 600 dpi versions for raster formats? |
Character vector of paths actually written, invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_table(),
sm_export_vosviewer(),
sm_export_zip()
corpus <- sm_example_corpus(n_works = 20) p <- sm_plot_production(corpus) path <- tempfile(fileext = ".png") sm_export_figure(p, path, multi_dpi = FALSE)corpus <- sm_example_corpus(n_works = 20) p <- sm_plot_production(corpus) path <- tempfile(fileext = ".png") sm_export_figure(p, path, multi_dpi = FALSE)
Write a tidygraph network to GEXF XML format for use with Gephi. Labels are escaped as XML, and directed networks retain their direction.
sm_export_gephi(network, path)sm_export_gephi(network, path)
network |
A |
path |
Output file path (should end in |
path invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_figure(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_table(),
sm_export_vosviewer(),
sm_export_zip()
corpus <- sm_example_corpus() net <- sm_network_cocitation(corpus) sm_export_gephi(net, tempfile(fileext = ".gexf"))corpus <- sm_example_corpus() net <- sm_network_cocitation(corpus) sm_export_gephi(net, tempfile(fileext = ".gexf"))
Generate a comprehensive bibliometric report from a corpus using a Quarto template.
sm_export_quarto_report( corpus, path, template = c("standard", "minimal", "thesis"), include_chat = FALSE, include_audit = TRUE, include_trajectory = FALSE )sm_export_quarto_report( corpus, path, template = c("standard", "minimal", "thesis"), include_chat = FALSE, include_audit = TRUE, include_trajectory = FALSE )
corpus |
An |
path |
Output path for the rendered report. |
template |
Report template. |
include_chat |
Logical; include chat session? |
include_audit |
Logical; include equity audit? |
include_trajectory |
Logical; include trajectory analysis? |
path invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_figure(),
sm_export_gephi(),
sm_export_rds(),
sm_export_table(),
sm_export_vosviewer(),
sm_export_zip()
corpus <- sm_example_corpus() sm_export_quarto_report(corpus, tempfile(fileext = ".html"))corpus <- sm_example_corpus() sm_export_quarto_report(corpus, tempfile(fileext = ".html"))
Write corpus works in RIS format for import into Rayyan. Work IDs appear in the RIS ID field and a note; missing fields are omitted.
sm_export_rayyan(corpus, path)sm_export_rayyan(corpus, path)
corpus |
An |
path |
Output file path. |
Keep the ID or the scimapR work_id note when exporting decisions.
path invisibly.
Other screening:
sm_export_covidence(),
sm_import_rayyan(),
sm_merge_screening_decisions(),
sm_screen_prisma()
path <- tempfile(fileext = ".ris") sm_export_rayyan(sm_example_corpus(n_works = 2), path) readLines(path, n = 3) unlink(path)path <- tempfile(fileext = ".ris") sm_export_rayyan(sm_example_corpus(n_works = 2), path) readLines(path, n = 3) unlink(path)
Save the full corpus including embeddings as a compressed RDS file.
sm_export_rds(corpus, path, compress = "xz")sm_export_rds(corpus, path, compress = "xz")
corpus |
An |
path |
Output file path. |
compress |
Compression type. Default |
path invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_figure(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_table(),
sm_export_vosviewer(),
sm_export_zip()
corpus <- sm_example_corpus(n_works = 10) path <- tempfile(fileext = ".rds") sm_export_rds(corpus, path)corpus <- sm_example_corpus(n_works = 10) path <- tempfile(fileext = ".rds") sm_export_rds(corpus, path)
Save a tibble or data frame with publication-ready formatting. XLSX output includes bold headers, frozen top row, autofilter, and optional title/caption/source note.
sm_export_table( table, path, format = c("xlsx", "csv", "tsv"), title = NULL, caption = NULL, source_note = NULL, style = c("scimapr", "minimal", "publication") )sm_export_table( table, path, format = c("xlsx", "csv", "tsv"), title = NULL, caption = NULL, source_note = NULL, style = c("scimapr", "minimal", "publication") )
table |
A data frame or tibble. |
path |
Output file path. |
format |
Output format. |
title |
Optional title row (merged, larger font). |
caption |
Optional caption row beneath header. |
source_note |
Optional source note in footer. |
style |
Formatting style. |
path invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_figure(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_vosviewer(),
sm_export_zip()
dat <- data.frame(Author = "Smith J", Works = 10L, Citations = 150L) path <- tempfile(fileext = ".xlsx") sm_export_table(dat, path)dat <- data.frame(Author = "Smith J", Works = 10L, Citations = 150L) path <- tempfile(fileext = ".xlsx") sm_export_table(dat, path)
Write a headerless network file with integer node ids and positive link
strengths, plus a tab-separated map file with id and label columns.
Missing weights become 1. Directed edges are written as supplied, but
VOSviewer treats the links as undirected.
sm_export_vosviewer(network, path, map_path = NULL)sm_export_vosviewer(network, path, map_path = NULL)
network |
A |
path |
Output file path. |
map_path |
Map file path. By default, |
path invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_figure(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_table(),
sm_export_zip()
corpus <- sm_example_corpus() net <- sm_network_cocitation(corpus) sm_export_vosviewer(net, tempfile(fileext = ".tsv")) # The matching map file is written beside the network file.corpus <- sm_example_corpus() net <- sm_network_cocitation(corpus) sm_export_vosviewer(net, tempfile(fileext = ".tsv")) # The matching map file is written beside the network file.
Build a ZIP archive containing the corpus RDS, certificate, figures, tables, and an auto-generated README. This is the "send everything to a collaborator" workflow.
sm_export_zip( corpus, path, include = c("rds", "certificate", "tables"), figure_formats = c("png"), figure_dpi = c(300), report_template = "standard" )sm_export_zip( corpus, path, include = c("rds", "certificate", "tables"), figure_formats = c("png"), figure_dpi = c(300), report_template = "standard" )
corpus |
An |
path |
Output ZIP file path. |
include |
Components to include in the bundle. |
figure_formats |
Figure output formats. |
figure_dpi |
Figure resolutions. |
report_template |
Quarto report template. |
path invisibly.
Other export:
sm_export_csv(),
sm_export_cytoscape(),
sm_export_figure(),
sm_export_gephi(),
sm_export_quarto_report(),
sm_export_rds(),
sm_export_table(),
sm_export_vosviewer()
corpus <- sm_example_corpus(n_works = 10) path <- tempfile(fileext = ".zip") sm_export_zip(corpus, path, include = c("rds", "certificate"))corpus <- sm_example_corpus(n_works = 10) path <- tempfile(fileext = ".zip") sm_export_zip(corpus, path, include = c("rds", "certificate"))
Query the arXiv API (Atom feed) for preprints and
return the results as an sm_corpus.
Uses start/max_results pagination to retrieve up to n_max results.
The arXiv API is free and requires no authentication.
sm_fetch_arxiv(query, n_max = 200L, verbose = TRUE, call = rlang::caller_env())sm_fetch_arxiv(query, n_max = 200L, verbose = TRUE, call = rlang::caller_env())
query |
arXiv search query string using the arXiv query syntax.
Supports |
n_max |
Maximum number of results to return (default 200). |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
Other fetchers:
sm_fetch_biorxiv(),
sm_fetch_crossref(),
sm_fetch_openalex(),
sm_fetch_orcid(),
sm_fetch_overton(),
sm_fetch_pubmed(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_arxiv(query = "all:bibliometrics", n_max = 10) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_arxiv(query = "all:bibliometrics", n_max = 10) print(corpus) ## End(Not run)
Query the bioRxiv or
medRxiv API for preprints and return the
results as an sm_corpus.
The API provides date-range-based content retrieval. If query is
supplied, results are filtered locally by title/abstract matching.
sm_fetch_biorxiv( query = NULL, server = c("biorxiv", "medrxiv"), from_date = NULL, to_date = NULL, n_max = 200L, verbose = TRUE, call = rlang::caller_env() )sm_fetch_biorxiv( query = NULL, server = c("biorxiv", "medrxiv"), from_date = NULL, to_date = NULL, n_max = 200L, verbose = TRUE, call = rlang::caller_env() )
query |
Optional search string for local filtering of results. |
server |
One of |
from_date |
Start date in |
to_date |
End date in |
n_max |
Maximum number of results to return (default 200). |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
Native requests retry transient server and network failures, and short
announced rate-limit waits. Long waits abort immediately. If a later
page fails, the error inherits from scimapR_fetch_incomplete and holds
the earlier parsed works in rlang::last_error()$partial. Their metadata
records fetch_incomplete with the source, received and expected counts,
and failure reason. If those pages cannot be parsed, partial is NULL.
The default remains an error; an incomplete fetch is never returned as
an ordinary successful result.
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_crossref(),
sm_fetch_openalex(),
sm_fetch_orcid(),
sm_fetch_overton(),
sm_fetch_pubmed(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_biorxiv( from_date = "2024-01-01", to_date = "2024-01-07", n_max = 10 ) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_biorxiv( from_date = "2024-01-01", to_date = "2024-01-07", n_max = 10 ) print(corpus) ## End(Not run)
Query the Crossref API for scholarly works
and return the results as an sm_corpus.
Uses cursor pagination to retrieve up to n_max works. Providing
a mailto address enables the polite pool (faster rate limits).
sm_fetch_crossref( query = NULL, filter = NULL, n_max = 200L, mailto = Sys.getenv("SCIMAPR_MAILTO"), engine = c("native", "rcrossref", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_fetch_crossref( query = NULL, filter = NULL, n_max = 200L, mailto = Sys.getenv("SCIMAPR_MAILTO"), engine = c("native", "rcrossref", "auto"), verbose = TRUE, call = rlang::caller_env() )
query |
Free-text search query. If |
filter |
Crossref filter syntax, e.g.
|
n_max |
Maximum number of works to return (default 200). |
mailto |
Email address for the polite pool. Read from
|
engine |
One of |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object. Its provenance records the search
(query, filter, n_max and engine), so that
sm_rebuild_from_cert() can run it again.
Native requests retry transient server and network failures, and short
announced rate-limit waits. Long waits abort immediately. If a later
page fails, the error inherits from scimapR_fetch_incomplete and holds
the earlier parsed works in rlang::last_error()$partial. Their metadata
records fetch_incomplete with the source, received and expected counts,
and failure reason. If those pages cannot be parsed, partial is NULL.
The default remains an error; an incomplete fetch is never returned as
an ordinary successful result.
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_biorxiv(),
sm_fetch_openalex(),
sm_fetch_orcid(),
sm_fetch_overton(),
sm_fetch_pubmed(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_crossref(query = "bibliometrics", n_max = 10) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_crossref(query = "bibliometrics", n_max = 10) print(corpus) ## End(Not run)
Query the OpenAlex API for scholarly works and
return the results as an sm_corpus.
Supports both free-text search (query) and structured filter syntax
(filter). Uses cursor-based pagination to retrieve up to n_max works.
Set an OpenAlex API key (see "API key and rate limits").
Besides cited_by_count, the works keep OpenAlex's field-weighted
citation impact (fwci), its citation percentile (citation_percentile,
0 to 1, with the flags is_top_10pct and is_top_1pct) and the date of
these values (citations_as_of), and the corpus gets the yearly citation
counts as citations_by_year (see sm_corpus() and
sm_citation_window()). The openalexR engine has no percentile, so its
three percentile columns are NA. To add these fields to a corpus from
another source, use sm_enrich_openalex_impact().
sm_fetch_openalex( query = NULL, filter = NULL, n_max = 200L, per_page = 200L, mailto = Sys.getenv("SCIMAPR_MAILTO"), api_key = Sys.getenv("OPENALEX_API_KEY"), engine = c("native", "openalexR", "auto"), batch_size = 50L, verbose = TRUE, call = rlang::caller_env() )sm_fetch_openalex( query = NULL, filter = NULL, n_max = 200L, per_page = 200L, mailto = Sys.getenv("SCIMAPR_MAILTO"), api_key = Sys.getenv("OPENALEX_API_KEY"), engine = c("native", "openalexR", "auto"), batch_size = 50L, verbose = TRUE, call = rlang::caller_env() )
query |
Free-text search query passed to the |
filter |
A character string using OpenAlex filter syntax, e.g.
|
n_max |
Maximum number of works to return (default 200). |
per_page |
Number of results per page (max 200). |
mailto |
Contact address sent with each request as the |
api_key |
OpenAlex API key. Read from the |
engine |
One of |
batch_size |
Integer; maximum number of |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object. Its provenance records the search
(query, filter, n_max and engine), so that
sm_rebuild_from_cert() can run it again.
OpenAlex expects a free API key. Requests without one share one small
daily budget with everyone on the same network address; once it is used
up, OpenAlex answers HTTP 429 (too many requests) until the budget
resets at midnight UTC. Set the key in the
OPENALEX_API_KEY environment variable (for example in ~/.Renviron)
or pass it as api_key. It is sent in the Authorization header,
never in the URL, and it is stored nowhere in the corpus.
On HTTP 429 the function stops at once with an error of class
scimapR_openalex_rate_limit instead of waiting: the message gives the
reset time and the remaining budget that OpenAlex reports (the fields
reset_at and remaining of the condition) and says whether a key was
sent. Only a wait of up to two seconds (OpenAlex also limits requests per
second) is waited out, once. Other failed requests stop with class
scimapR_openalex_failed; both classes inherit from
scimapR_openalex_error. With engine = "openalexR" the
openalexR package (version 3.1.1 or later) sends the requests: it
gets the key too and its own retries are switched off, but it does not
pass on the response headers, so the reset time and the budget are not
known.
Native requests retry transient server and network failures, and short
announced rate-limit waits. Long waits abort immediately. If a later
page fails, the error inherits from scimapR_fetch_incomplete and holds
the earlier parsed works in rlang::last_error()$partial. Their metadata
records fetch_incomplete with the source, received and expected counts,
and failure reason. If those pages cannot be parsed, partial is NULL.
The default remains an error; an incomplete fetch is never returned as
an ordinary successful result.
sm_enrich_openalex_impact(), sm_citation_window()
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_biorxiv(),
sm_fetch_crossref(),
sm_fetch_orcid(),
sm_fetch_overton(),
sm_fetch_pubmed(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_openalex(query = "bibliometrics", n_max = 10) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_openalex(query = "bibliometrics", n_max = 10) print(corpus) ## End(Not run)
Retrieve works associated with an ORCID identifier
via the ORCID Public API and return the results as an sm_corpus.
This fetches work summaries from the public API endpoint
https://pub.orcid.org/v3.0/{orcid}/works.
sm_fetch_orcid(orcid, verbose = TRUE, call = rlang::caller_env())sm_fetch_orcid(orcid, verbose = TRUE, call = rlang::caller_env())
orcid |
An ORCID identifier (e.g., |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_biorxiv(),
sm_fetch_crossref(),
sm_fetch_openalex(),
sm_fetch_overton(),
sm_fetch_pubmed(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_orcid("0000-0001-8006-9742") print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_orcid("0000-0001-8006-9742") print(corpus) ## End(Not run)
Search the Overton API for policy document
citations of scholarly works and return the results as an sm_corpus.
Requires an Overton API key (set via the OVERTON_API_KEY environment
variable).
sm_fetch_overton( query, api_key = Sys.getenv("OVERTON_API_KEY"), n_max = 100L, verbose = TRUE, call = rlang::caller_env() )sm_fetch_overton( query, api_key = Sys.getenv("OVERTON_API_KEY"), n_max = 100L, verbose = TRUE, call = rlang::caller_env() )
query |
Search query string. |
api_key |
Overton API key. Read from |
n_max |
Maximum number of results to return (default 100). |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_biorxiv(),
sm_fetch_crossref(),
sm_fetch_openalex(),
sm_fetch_orcid(),
sm_fetch_pubmed(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_overton(query = "climate change policy", n_max = 10) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_overton(query = "climate change policy", n_max = 10) print(corpus) ## End(Not run)
Query PubMed via the NCBI E-utilities
(esearch + efetch) and return the results as an sm_corpus.
Uses retmax/retstart pagination to retrieve up to n_max records.
Providing an NCBI API key allows up to 10 requests/second instead of 3.
MeSH headings become concepts in the "mesh" vocabulary. Their score
is 1 for a major topic of the article (MajorTopicYN on the descriptor or
one of its qualifiers) and 0 otherwise.
sm_fetch_pubmed( query, n_max = 200L, api_key = Sys.getenv("NCBI_API_KEY"), engine = c("native", "rentrez", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_fetch_pubmed( query, n_max = 200L, api_key = Sys.getenv("NCBI_API_KEY"), engine = c("native", "rentrez", "auto"), verbose = TRUE, call = rlang::caller_env() )
query |
PubMed search query string (required). |
n_max |
Maximum number of records to return (default 200). |
api_key |
NCBI API key. Read from |
engine |
One of |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object. Its provenance records the search
(query, n_max and engine), so that sm_rebuild_from_cert() can
run it again.
Native requests retry transient server and network failures, and short
announced rate-limit waits. Long waits abort immediately. If a later
page fails, the error inherits from scimapR_fetch_incomplete and holds
the earlier parsed works in rlang::last_error()$partial. Their metadata
records fetch_incomplete with the source, received and expected counts,
and failure reason. If those pages cannot be parsed, partial is NULL.
The default remains an error; an incomplete fetch is never returned as
an ordinary successful result.
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_biorxiv(),
sm_fetch_crossref(),
sm_fetch_openalex(),
sm_fetch_orcid(),
sm_fetch_overton(),
sm_fetch_semantic_scholar()
## Not run: corpus <- sm_fetch_pubmed(query = "bibliometrics[tiab]", n_max = 10) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_pubmed(query = "bibliometrics[tiab]", n_max = 10) print(corpus) ## End(Not run)
Query the Semantic Scholar Academic
Graph API for papers and return the results as an sm_corpus.
Supports both keyword search (query) and batch paper lookup
(paper_ids). Optionally fetches pre-computed SPECTER embeddings.
sm_fetch_semantic_scholar( query = NULL, paper_ids = NULL, n_max = 100L, api_key = Sys.getenv("SEMANTIC_SCHOLAR_API_KEY"), include_embeddings = FALSE, verbose = TRUE, call = rlang::caller_env() )sm_fetch_semantic_scholar( query = NULL, paper_ids = NULL, n_max = 100L, api_key = Sys.getenv("SEMANTIC_SCHOLAR_API_KEY"), include_embeddings = FALSE, verbose = TRUE, call = rlang::caller_env() )
query |
Free-text search query. If |
paper_ids |
Character vector of Semantic Scholar paper IDs, DOIs
(prefixed |
n_max |
Maximum number of papers to return (default 100). |
api_key |
Semantic Scholar API key. Read from
|
include_embeddings |
Logical; if |
verbose |
Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
Native requests retry transient server and network failures, and short
announced rate-limit waits. Long waits abort immediately. If a later
page fails, the error inherits from scimapR_fetch_incomplete and holds
the earlier parsed works in rlang::last_error()$partial. Their metadata
records fetch_incomplete with the source, received and expected counts,
and failure reason. If those pages cannot be parsed, partial is NULL.
The default remains an error; an incomplete fetch is never returned as
an ordinary successful result.
Other fetchers:
sm_fetch_arxiv(),
sm_fetch_biorxiv(),
sm_fetch_crossref(),
sm_fetch_openalex(),
sm_fetch_orcid(),
sm_fetch_overton(),
sm_fetch_pubmed()
## Not run: corpus <- sm_fetch_semantic_scholar(query = "bibliometrics", n_max = 10) print(corpus) ## End(Not run)## Not run: corpus <- sm_fetch_semantic_scholar(query = "bibliometrics", n_max = 10) print(corpus) ## End(Not run)
Identify works in the corpus that are linked to clinical trials based on PMIDs or DOIs.
sm_field_clinical_trials(corpus, ..., call = rlang::caller_env())sm_field_clinical_trials(corpus, ..., call = rlang::caller_env())
corpus |
An |
... |
Additional arguments (currently unused). |
call |
Caller environment. |
A tibble with work_id and trial-related information.
Other field-helpers:
sm_field_funder(),
sm_field_pubmed_mesh()
corpus <- sm_example_corpus() sm_field_clinical_trials(corpus)corpus <- sm_example_corpus() sm_field_clinical_trials(corpus)
Summarise funding sources from corpus metadata.
sm_field_funder( corpus, source = c("crossref", "openalex"), call = rlang::caller_env() )sm_field_funder( corpus, source = c("crossref", "openalex"), call = rlang::caller_env() )
corpus |
An |
source |
Data source for funding info. |
call |
Caller environment. |
A tibble of funders with counts.
Other field-helpers:
sm_field_clinical_trials(),
sm_field_pubmed_mesh()
corpus <- sm_example_corpus() sm_field_funder(corpus)corpus <- sm_example_corpus() sm_field_funder(corpus)
Summarise MeSH (Medical Subject Headings) terms from the concepts table.
sm_field_pubmed_mesh(corpus, call = rlang::caller_env())sm_field_pubmed_mesh(corpus, call = rlang::caller_env())
corpus |
An |
call |
Caller environment. |
A tibble with mesh_term, count, and proportion.
Other field-helpers:
sm_field_clinical_trials(),
sm_field_funder()
corpus <- sm_example_corpus() sm_field_pubmed_mesh(corpus)corpus <- sm_example_corpus() sm_field_pubmed_mesh(corpus)
Scans a directory of figures and produces a tidy manifest of captions,
alt-text, and (where readable) pixel dimensions and DPI. This replaces the
hand-maintained figure_captions.R scripts that accompany many manuscripts.
Captions and alt-text are pulled from a sidecar file if one is present
(captions.csv, captions.yml, or captions.yaml in dir), otherwise the
columns are left empty for the user to fill in.
sm_figure_manifest( dir, pattern = NULL, output = NULL, call = rlang::caller_env() )sm_figure_manifest( dir, pattern = NULL, output = NULL, call = rlang::caller_env() )
dir |
Directory to scan. |
pattern |
Optional regular expression to filter file names. Defaults to
common figure extensions ( |
output |
Optional path to write the manifest to. A |
call |
Caller environment for error reporting. |
A tibble with one row per figure file and columns file, caption,
alt_text, width, height, dpi. Pixel dimensions / DPI are read from
the image where feasible (using the optional magick or png
packages) and are NA otherwise. Type-stable: a missing or empty directory
returns a 0-row tibble (with a cli warning), never an error.
Other reporting:
sm_corpus_from_tables()
d <- withr::local_tempdir() ggplot2::ggsave(file.path(d, "fig1.png"), ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point(), width = 4, height = 3, dpi = 150) sm_figure_manifest(d)d <- withr::local_tempdir() ggplot2::ggsave(file.path(d, "fig1.png"), ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point(), width = 4, height = 3, dpi = 150) sm_figure_manifest(d)
Subset a corpus by filtering works on year, type, OA status, or custom expressions.
sm_filter_works(corpus, ..., year_range = NULL, types = NULL, oa_only = FALSE)sm_filter_works(corpus, ..., year_range = NULL, types = NULL, oa_only = FALSE)
corpus |
An |
... |
Filtering expressions passed to |
year_range |
Optional two-element integer vector for year filtering. |
types |
Optional character vector of document types to keep. |
oa_only |
Logical; keep every status in |
An sm_corpus with the filtered subset.
Other filters:
sm_query()
corpus <- sm_example_corpus() filtered <- sm_filter_works(corpus, year_range = c(2020, 2024)) nrow(filtered$works)corpus <- sm_example_corpus() filtered <- sm_filter_works(corpus, year_range = c(2020, 2024)) nrow(filtered$works)
Computes a content-addressable SHA-256 hash of the corpus. The hash
covers the works (without last_refreshed), authors, authorships,
institutions, sources, references, concepts, screening decisions
(without decided_at) and embeddings, and the yearly citation counts
(citations_by_year) when the corpus has any, so a corpus without them
hashes as it did before the element existed.
Two corpora with identical scientific content will produce the same hash, regardless of when they were built and of the order of their rows and columns.
The hash leaves out the provenance table, which records when each record
was fetched, and the metadata (timestamps, the lock, the embedding model
and the local input files of sm_register_input()): changing them does
not change the hash. sm_verify_certificate() checks the local input
files on their own.
sm_hash_corpus(corpus, version = 2L)sm_hash_corpus(corpus, version = 2L)
corpus |
An |
version |
The hash algorithm: |
version selects the algorithm. Version 2, the default from scimapR
0.5.0 on, hashes canonical UTF-8 text, independently of R's binary
serialisation. Table columns are sorted by name and rows by their text
values, using radix ordering. Missing values are distinct from the
string "NA". Numbers use 15 significant digits, signed zero becomes
zero, dates use ISO dates and timestamps use UTC with six decimal places
for seconds. List cells use JSON. Column classes are included in the
text. Embeddings are sorted by work id and rounded to eight decimal
places. These rules make version 2 independent of the R version,
platform, time zone and collation locale.
Hashes of version 2 therefore differ from the hashes that earlier
versions of scimapR computed for the same content. Version 1 is the
algorithm of these earlier versions. Certificates (sm_certificate())
and snapshots (sm_snapshot()) record the version they used as
hash_version; those written before it existed are checked with
version 1. Version 1 uses R's binary serialisation and depends on the
R version: older certificates and snapshots remain verifiable on the
R version that wrote them.
A single character string containing the hex-encoded SHA-256 hash.
Other reproducibility:
sm_certificate(),
sm_cite_corpus(),
sm_diff_corpora(),
sm_provenance(),
sm_register_input(),
sm_snapshot()
corpus <- sm_example_corpus() sm_hash_corpus(corpus) # the order of the columns does not matter reordered <- corpus reordered$works <- reordered$works[, rev(names(reordered$works))] identical(sm_hash_corpus(reordered), sm_hash_corpus(corpus))corpus <- sm_example_corpus() sm_hash_corpus(corpus) # the order of the columns does not matter reordered <- corpus reordered$works <- reordered$works[, rev(names(reordered$works))] identical(sm_hash_corpus(reordered), sm_hash_corpus(corpus))
Read Rayyan CSV export and parse screening decisions.
sm_import_rayyan(path, corpus = NULL, id_col = NULL)sm_import_rayyan(path, corpus = NULL, id_col = NULL)
path |
Path to Rayyan CSV export. |
corpus |
Optional corpus for DOI matching and ID validation. |
id_col |
Optional name of the CSV column containing work IDs. |
IDs come from the named column, otherwise from the scimapR work_id note, or from unique matching corpus DOIs. Decisions and reasons come from their columns or Rayyan inclusion and exclusion notes. Disagreeing reviewers give an uncertain decision.
One screening row per CSV record. Unmatched work IDs remain missing and warn. Decisions use include, exclude and uncertain.
Other screening:
sm_export_covidence(),
sm_export_rayyan(),
sm_merge_screening_decisions(),
sm_screen_prisma()
path <- tempfile(fileext = ".csv") utils::write.csv(data.frame(id = "paper-a", decision = "Included"), path, row.names = FALSE) sm_import_rayyan(path, id_col = "id") unlink(path)path <- tempfile(fileext = ".csv") utils::write.csv(data.frame(id = "paper-a", decision = "Included"), path, row.names = FALSE) sm_import_rayyan(path, id_col = "id") unlink(path)
A turnkey interrupted time series (ITS): aggregates the chosen outcome to a
yearly series, fits a segmented regression with a level shift and a slope
change at intervention_year, and derives the counterfactual (the
pre-intervention trend projected forward).
sm_its( corpus, intervention_year, outcome = c("count", "share_q1", "cnci", "leadership"), family = NULL, lag = NULL, institutions = NULL, lead_role = c("corresponding", "first", "last", "first_or_last", "any_lead"), min_cell = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_its' print(x, ...) ## S3 method for class 'sm_its' summary(object, ...) ## S3 method for class 'sm_its' autoplot(object, ...)sm_its( corpus, intervention_year, outcome = c("count", "share_q1", "cnci", "leadership"), family = NULL, lag = NULL, institutions = NULL, lead_role = c("corresponding", "first", "last", "first_or_last", "any_lead"), min_cell = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_its' print(x, ...) ## S3 method for class 'sm_its' summary(object, ...) ## S3 method for class 'sm_its' autoplot(object, ...)
corpus |
An |
intervention_year |
Year at which the intervention takes effect: one
whole number such as |
outcome |
One of
|
family |
Optional GLM family (a |
lag |
Citation-maturity lag in years (default |
institutions |
For |
lead_role |
For |
min_cell |
For |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
object |
An |
The yearly series is built first. For "count" it runs over every year
from the first to the last publication year of the corpus, and a year
without works counts 0, so filter the corpus to the analysis window
beforehand. For the other outcomes a year with works but no usable value
is dropped with a warning and listed in dropped_years: no work of the
year has a journal quartile ("share_q1") or a finite impact value
("cnci"). For "cnci" the citation-immature years are excluded after
that and listed in provisional_years, so no year is in both.
For "leadership" the share of a year is over the works whose lead is
known, and that number of works weights the year in the fit. A work
whose authorships all have is_corresponding = NA (unknown, as the
readers of sources without corresponding-author data record it; see
sm_corpus()) leaves the denominator for
lead_role = "corresponding", with or without institutions; up to
scimapR 0.4.0 it counted as not led. So does a work without
authorships, and for the position roles a work whose positions are all
NA. For "any_lead" an unknown corresponding author counts as not
leading, so the work stays in the denominator. A year in which no work
has a known lead is dropped with a warning, and sm_its() aborts when
no work has one. With institutions the denominator is every work of
the year with a known lead, not only the works with an authorship at
the focal institutions.
A focal label is compared with every label column of an authorship row
(institution_match, institution_name, institution_id and the name
of that id), as group membership in sm_did() and sm_synth() is, so
a row with institution_match "A" and institution_name "B" belongs to
both "A" and "B". sm_count() instead labels each row by the first of
these columns with a value. The print method names the column that
labels the institutions of the corpus (leadership$label_column).
At least 4 years must remain after these exclusions, with 2 or more before
intervention_year and 2 or more from it on, so that the pre-trend, the
level change and the slope change can all be estimated; otherwise
sm_its() aborts. With only 4 years the 4 coefficients fit the series
exactly: a family that estimates its dispersion (Gaussian, quasi
families) then has no residual degrees of freedom, so its standard
errors, intervals and p-values are NaN and sm_its() warns. Families
that fix the dispersion, such as the Poisson default for "count", keep
their standard errors.
The confidence intervals are Wald intervals on the reference distribution of the p-values: t with the residual degrees of freedom when the family estimates its dispersion (Gaussian and quasi families), normal when the family fixes it (Poisson, binomial). The standard errors treat the yearly values as independent; no correction for autocorrelation is applied.
An sm_its S3 object with components: model (the fitted glm),
coefficients (tidy tibble: term, estimate, std.error, conf.low,
conf.high, statistic, p.value), series (tibble: year,
observed, fitted, counterfactual), and metadata (outcome,
intervention_year, family, lag, provisional_years,
dropped_years, leadership). dropped_years (integer) lists the
years left out for lack of a usable value, provisional_years
(integer) the citation-immature years excluded after them.
leadership holds the definition used for outcome = "leadership", a
list of lead_role, institution_aware (FALSE), institutions
(the focal labels or NULL) and label_column (the column that
labels the institutions of the corpus, institution_match,
institution_name or institution_id, the first with a value; NA
without institutions), and is NULL for the other outcomes.
impact_source (character) names where the impact values of
outcome = "cnci" come from, such as "works$fwci (OpenAlex)" or
"works$cited_by_count (field-normalised)"; NA for the other
outcomes.
print returns x invisibly.
summary returns the tidy coefficient tibble.
autoplot returns a ggplot.
sm_did(), sm_citation_maturity()
Other causal:
sm_did(),
sm_synth()
corpus <- sm_example_corpus(n_works = 200, seed = 1) its <- sm_its(corpus, intervention_year = 2020, outcome = "count") its ggplot2::autoplot(its) # share of works whose corresponding author is at a focal unit fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) dict <- readr::read_csv( sm_example_files("example_realuse_affiliation_dict.csv"), col_types = "ccc" ) fix <- sm_affiliation_match(fix, dict) lead <- sm_its(fix, intervention_year = 2021, outcome = "leadership", institutions = "Armed Forces Hospital Northtown") lead$seriescorpus <- sm_example_corpus(n_works = 200, seed = 1) its <- sm_its(corpus, intervention_year = 2020, outcome = "count") its ggplot2::autoplot(its) # share of works whose corresponding author is at a focal unit fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) dict <- readr::read_csv( sm_example_files("example_realuse_affiliation_dict.csv"), col_types = "ccc" ) fix <- sm_affiliation_match(fix, dict) lead <- sm_its(fix, intervention_year = 2021, outcome = "leadership", institutions = "Armed Forces Hospital Northtown") lead$series
Checks whether each supplied ISSN appears in a journal master list (e.g. the Web of Science Master Journal List, a Scopus source list, or a DOAJ export). Matching is by normalised ISSN and checks both print and electronic ISSNs.
This function is deliberately offline and data-driven: it never scrapes or
hardcodes proprietary lists. You supply the master list via reference_list,
so the function is fully testable and reproducible.
sm_journal_in_index( issn, index = c("wos", "scopus", "doaj"), reference_list = NULL, call = rlang::caller_env() )sm_journal_in_index( issn, index = c("wos", "scopus", "doaj"), reference_list = NULL, call = rlang::caller_env() )
issn |
Character vector of ISSNs to check. Hyphenation and case are
normalised automatically (e.g. |
index |
Which index the master list represents: |
reference_list |
A data frame master list. Expected columns (matched case-insensitively):
A small synthetic example list ships at
|
call |
Caller environment for error reporting. |
A tibble with one row per input issn and columns:
issn (the normalised input), index, in_index (logical),
matched_title (character or NA), matched_issn_type
("print"/"electronic"/"unknown"/NA). Type-stable: an empty issn
vector returns a 0-row tibble with these columns.
Other coverage:
sm_coverage_audit(),
sm_coverage_breakdowns(),
sm_match_types(),
sm_reconcile()
ref <- utils::read.csv( system.file("extdata", "example_journal_index.csv", package = "scimapR"), stringsAsFactors = FALSE ) sm_journal_in_index(c("1078-8956", "1546-170X", "9999-9999"), index = "doaj", reference_list = ref)ref <- utils::read.csv( system.file("extdata", "example_journal_index.csv", package = "scimapR"), stringsAsFactors = FALSE ) sm_journal_in_index(c("1078-8956", "1546-170X", "9999-9999"), index = "doaj", reference_list = ref)
Resolves cited DOIs and OpenAlex identifiers against the works in the corpus. Use this for corpora built from tables or saved before references were linked automatically. No network request is made.
sm_link_references( corpus, by = c("doi", "openalex_id"), verbose = TRUE, call = rlang::caller_env() )sm_link_references( corpus, by = c("doi", "openalex_id"), verbose = TRUE, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
by |
Identifiers to match, in priority order: |
verbose |
Report the numbers of linked and unresolved references. |
call |
Caller environment for error reporting. |
Links to existing corpus works are preserved. Missing or dangling links
are resolved by a normalised DOI in cited_doi or a Web of Science DOI
field in cited_raw, including bracketed DOI lists. OpenAlex matching
accepts a whole short work id or its URL in cited_raw. A match must
identify exactly one work and must not point to the citing work itself.
The first identifier type in by giving a unique, non-self match wins.
A dangling link without a match is retained. The source identifiers and
provenance are unchanged. Locked corpora must first be unlocked.
Readers and fetchers that build references, sm_bind_corpora() and
sm_dedupe() link automatically. sm_corpus(), sm_corpus_from_tables(),
new_sm_corpus(), sm_materialise(), subsetting and sm_example_corpus()
take the supplied links as given.
The corpus with references$cited_work_id filled where possible.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
corpus <- readRDS(sm_example_files("example_realuse_corpus.rds")) linked <- sm_link_references(corpus) sum(linked$references$cited_work_id %in% linked$works$work_id)corpus <- readRDS(sm_example_files("example_realuse_corpus.rds")) linked <- sm_link_references(corpus) sum(linked$references$cited_work_id %in% linked$works$work_id)
Locking a corpus prevents mutation by sm_refresh() and other modifying
operations. This is useful for archival or when you want to guarantee
reproducibility after generating a sm_certificate().
sm_lock() sets metadata$is_locked = TRUE with an optional reason.
sm_unlock() reverses the lock but requires explicit confirmation to
prevent accidental unlocking of archived corpora.
sm_lock(corpus, reason = NULL, call = rlang::caller_env()) sm_unlock(corpus, confirm = FALSE, call = rlang::caller_env())sm_lock(corpus, reason = NULL, call = rlang::caller_env()) sm_unlock(corpus, confirm = FALSE, call = rlang::caller_env())
corpus |
An |
reason |
Optional character string documenting why the corpus was
locked (e.g., |
call |
Caller environment for error reporting. |
confirm |
Logical. Must be |
Merging, deduplication, materialisation, citation maturity, enrichment, embedding changes, clustering, labelling, affiliation matching and screening refuse locked inputs, as do refreshing and input registration. Subsets remain locked but lose their stored lock hash. Merged corpora are unlocked; unlock the inputs explicitly before merging.
The lock records a content hash. sm_certificate() warns and
sm_validate() reports when direct edits changed that content.
Printing shows only the lock status and does not hash the corpus.
Earlier locked corpora without a stored hash are not checked.
A modified sm_corpus with updated lock status.
Other refresh:
sm_refresh(),
sm_staleness()
corpus <- sm_example_corpus() locked <- sm_lock(corpus, reason = "archival snapshot") locked$metadata$is_locked unlocked <- sm_unlock(locked, confirm = TRUE) unlocked$metadata$is_lockedcorpus <- sm_example_corpus() locked <- sm_lock(corpus, reason = "archival snapshot") locked$metadata$is_locked unlocked <- sm_unlock(locked, confirm = TRUE) unlocked$metadata$is_locked
The set of values the match_type column of sm_coverage_audit()'s
matches tibble (and sm_reconcile()'s) can take.
sm_match_types(describe = FALSE)sm_match_types(describe = FALSE)
describe |
Logical; if |
A character vector or a tibble.
sm_coverage_audit(), sm_reconcile()
Other coverage:
sm_coverage_audit(),
sm_coverage_breakdowns(),
sm_journal_in_index(),
sm_reconcile()
sm_match_types()sm_match_types()
Joins cached enrichment data into the matching sm_corpus sub-tibbles by
their key columns, returning an updated, schema-valid sm_corpus. This
replaces hand-written cache-to-corpus joins (which are easy to get wrong —
e.g. a bind_rows() that coerces a NULL element to a logical column).
sm_materialise( corpus, sources, .by = NULL, overwrite = FALSE, call = rlang::caller_env() )sm_materialise( corpus, sources, .by = NULL, overwrite = FALSE, call = rlang::caller_env() )
corpus |
An |
sources |
Either a named list whose names are corpus sub-tables
( |
.by |
Optional named list mapping table name to its join key column(s).
Missing or empty key values never match. Defaults: |
overwrite |
Logical (default |
call |
Caller environment for error reporting. |
Missing key columns warn and skip the source. Enrichment rows with a
missing key value are dropped with scimapR_materialise_na_keys;
corpus rows with missing keys keep their values and produce a
scimapR_materialise_unmatched_rows message. A repeated complete key in
works, authors, institutions, sources, references or concepts
aborts with scimapR_materialise_key_not_unique. The group keys in
authorships, provenance and screening can repeat: every matching row
receives the enrichment. Duplicate enrichment keys warn and keep the first.
Concept vocabularies used as keys are normalised on both sides before
matching. After a merge into concepts,
legacy spellings in its vocabulary column are renamed to the names of
sm_concept_vocabularies(), without a message.
An updated, validated sm_corpus with the enrichment columns merged
into the relevant sub-tables. New columns are added; existing rows are
preserved (this is a column-enrichment join, not a row append).
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
corpus <- sm_example_corpus(n_works = 10, seed = 1) metrics <- tibble::tibble(work_id = corpus$works$work_id, cnci = runif(10, 0.5, 2)) corpus2 <- sm_materialise(corpus, sources = list(works = metrics)) "cnci" %in% names(corpus2$works)corpus <- sm_example_corpus(n_works = 10, seed = 1) metrics <- tibble::tibble(work_id = corpus$works$work_id, cnci = runif(10, 0.5, 2)) corpus2 <- sm_materialise(corpus, sources = list(works = metrics)) "cnci" %in% names(corpus2$works)
Add external screening decisions (from Rayyan, Covidence, or manual spreadsheet) into the corpus screening table.
sm_merge_screening_decisions(corpus, decisions)sm_merge_screening_decisions(corpus, decisions)
corpus |
An |
decisions |
A tibble with screening decisions. |
An sm_corpus with updated screening.
Other screening:
sm_export_covidence(),
sm_export_rayyan(),
sm_import_rayyan(),
sm_screen_prisma()
Computes several collaboration indicators for the corpus, including the Collaboration Index (CI), mean authors per paper, proportion of multi-authored papers, and international collaboration rate.
sm_metric_collab_index(corpus, call = rlang::caller_env())sm_metric_collab_index(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object with a populated |
call |
Caller environment for error reporting. |
The Collaboration Index (CI) for a set of works is typically defined as
the mean number of authors per paper. This function returns per-work
data, from which aggregate CI can be computed by the user.
Country and institution counts are NA when no non-blank value is known.
With one known country, is_international is FALSE, even if some
affiliations are missing. Institutions are counted by institution_id
only; names and affiliation text are not matched.
A tibble with one row per work and columns:
work_idThe work identifier.
n_authorsNumber of authors on the work.
n_countriesDistinct known countries; NA if none are known.
n_institutionsDistinct known institution_id values; NA
if none are known. Affiliation match labels are not counted.
is_internationalLogical; TRUE if more than one
country is known; FALSE if exactly one is known, and NA if none
are known. Missing codes can hide international collaboration.
is_multi_authoredLogical; TRUE if more than one author.
Other metrics:
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() collab <- sm_metric_collab_index(corpus) head(collab) # Aggregate collaboration index: mean(collab$n_authors)corpus <- sm_example_corpus() collab <- sm_metric_collab_index(corpus) head(collab) # Aggregate collaboration index: mean(collab$n_authors)
Computes the CD index (Funk & Owen-Smith, 2017) for each work in the corpus. The CD index measures how disruptive a work is by comparing the citation patterns of papers that cite the focal work versus those that cite its references.
sm_metric_disruption(corpus, call = rlang::caller_env())sm_metric_disruption(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object with a populated |
call |
Caller environment for error reporting. |
The CD index is defined as:
Where for a focal work f:
Number of works that cite f but do NOT cite any of f's references (disruptive citations).
Number of works that cite BOTH f and at least one of f's references (consolidating citations).
Number of works that cite at least one of f's references but do NOT cite f (ignored-but-referencing citations).
A CD index near +1 indicates the focal work is highly disruptive: its citers tend to ignore its references. A CD index near -1 indicates the focal work consolidates existing ideas.
Only citations between works inside the corpus count. Repeated links and
self-references are ignored. The focal work itself is excluded from
, as are works published in an earlier year when both years are
known. Same-year and undated works remain eligible: publication year is
the available time resolution. Citers of the focal work always contribute
to or . Works without linked references or without any
internal citers receive NA.
A tibble with columns work_id and cd_index. The CD index
ranges from -1 (consolidating) to +1 (disruptive). Works without
sufficient citation data receive NA.
Funk, R. J., & Owen-Smith, J. (2017). A Dynamic Network Measure of Technological Change. Management Science, 63(3), 791–817. doi:10.1287/mnsc.2015.2366
Other metrics:
sm_metric_collab_index(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() cd <- sm_metric_disruption(corpus) head(cd)corpus <- sm_example_corpus() cd <- sm_metric_disruption(corpus) head(cd)
Computes the Field-Normalized Citation Impact (FNCI) for each work in the corpus. FNCI normalises a work's citation count by the average citation count of all works in the same field and publication year.
sm_metric_fnci( corpus, classification = c("openalex_concepts", "mesh", "manual"), min_cell = NULL, by_type = FALSE, call = rlang::caller_env() )sm_metric_fnci( corpus, classification = c("openalex_concepts", "mesh", "manual"), min_cell = NULL, by_type = FALSE, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
classification |
Character; the classification system used to assign
works to fields. One of |
min_cell |
|
by_type |
Logical. If |
call |
Caller environment for error reporting. |
FNCI is defined as:
Where is the citation count of work i and
is the mean citation count of all works in field f
published in year y.
An FNCI of 1.0 means the work is cited at the corpus average of its baseline cell (by default its field and year). Values > 1.0 indicate above-average impact.
For "openalex_concepts", the highest-scoring level-0 concept from the
concepts table is used. For "mesh", MeSH headings are used: concepts
of the "mesh" vocabulary of sm_concept_vocabularies(), also when
spelled "MEDLINE" (requires MeSH data in concepts; see below). For
"manual", a field column must already exist in corpus$works.
A tibble with one row per work and the columns work_id, field,
year, cited_by_count, field_mean (the baseline: the mean citation
count of the work's cell) and fnci. The column set depends on the
arguments: by_type = TRUE adds doc_type ("article", "review" or
NA), and min_cell adds n_cell (integer; the number of works with a
known citation count in the baseline cell) and baseline (a factor with
the levels field_year, field_year_pm1, year and none; the level
the baseline comes from). An empty corpus gives a 0-row tibble with the
same columns.
A cell with few works gives an unstable baseline, and a work alone in its
cell gets FNCI = 1 by construction. When min_cell is NULL and more
than 25% of the works with a citation count sit in cells with fewer than
10 such works, a message (class scimapR_fnci_small_cells) gives that
share. sm_its(), sm_did() and sm_synth() (outcome = "cnci") and
sm_metric_summary() (metric = "cnci") show it too when they derive
impact from citation counts; their own min_cell argument is passed on
here.
With min_cell set, each work gets the most specific baseline that has at
least min_cell works with a known citation count, the work itself
included:
field_year: its field and publication year;
field_year_pm1: its field in the pooled years y - 1 to y + 1
(truncated at the edges of the corpus);
year: all works of its publication year.
If even the year has fewer than min_cell such works, fnci and
field_mean are NA, baseline is "none" and n_cell gives the size
of the year cell. Works without a field ("unclassified") start at the
year level, and works without a year get NA throughout.
In this mode field may list several categories separated by ;, as
the Web of Science WC field does. A work belongs to the cell of each of its
categories. For each category the most specific level that reaches
min_cell is found, and the work's baseline is the average of the
category baselines at the most specific level any of them reached:
baseline names that level and n_cell is the smallest of the averaged
cells. The baselines are averaged (), not the ratios
(). field keeps the original string. Without
min_cell the string is one field, as before.
With by_type = TRUE, works$type is normalised: lower-cased, its first
;-separated part, trimmed. "article" covers article, journal-article,
jour and research-article; "review" covers review and review article.
Works of any other or a missing type get fnci = NA (and baseline = NA),
with one message (class scimapR_fnci_other_types). The type stays part
of the cell at every fallback level. The PubMed and Semantic Scholar
fetchers record every work as "journal-article", so their reviews count
as articles.
MeSH check tags (Humans, Animals, Male, Female, Pregnancy, the age groups, common laboratory and farm animals, and the historical periods) describe who or what was studied, and common descriptors of study design, statistics and laboratory material (such as Retrospective Studies, Risk Factors, Treatment Outcome or Cells, Cultured) describe how it was studied. Neither is ever used as a field.
sm_fetch_pubmed(), sm_read_pubmed_xml() and sm_enrich_concepts()
(source = "mesh") record each heading's major-topic flag as its score:
1 for a major topic of the article (starred in PubMed) and 0 otherwise.
A work gets its highest-scoring heading; among several, the one that
most works of the corpus carry (ties in alphabetical order). A work whose
headings carry no scores, as with MeSH terms from other sources, gets its
heading that is most frequent in the corpus, with one message (class
scimapR_fnci_mesh_unscored). A work with no other headings is
unclassified.
Waltman, L., van Eck, N. J., van Leeuwen, T. N., Visser, M. S., & van Raan, A. F. J. (2011). Towards a New Crown Indicator: Some Theoretical Considerations. Journal of Informetrics, 5(1), 37–47. doi:10.1016/j.joi.2010.08.001
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() fnci <- sm_metric_fnci(corpus, classification = "openalex_concepts") head(fnci) # Fall back to larger baselines for cells with fewer than 5 works, with # articles and reviews normalised separately fnci <- sm_metric_fnci(corpus, min_cell = 5, by_type = TRUE) table(fnci$baseline, useNA = "ifany")corpus <- sm_example_corpus() fnci <- sm_metric_fnci(corpus, classification = "openalex_concepts") head(fnci) # Fall back to larger baselines for cells with fewer than 5 works, with # articles and reviews normalised separately fnci <- sm_metric_fnci(corpus, min_cell = 5, by_type = TRUE) table(fnci$baseline, useNA = "ifany")
Computes the g-index for entities at the specified level. The g-index is the largest g such that the top g works have at least g^2 citations in total (Egghe, 2006).
sm_metric_g_index( corpus, level = c("author", "institution", "source", "country"), self_corrected = FALSE, call = rlang::caller_env() )sm_metric_g_index( corpus, level = c("author", "institution", "source", "country"), self_corrected = FALSE, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
level |
Character; the entity level. Defaults to |
self_corrected |
Logical (default |
call |
Caller environment for error reporting. |
A tibble with columns for the entity ID/name and g_index.
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() sm_metric_g_index(corpus, level = "author")corpus <- sm_example_corpus() sm_metric_g_index(corpus, level = "author")
Computes the h-index for entities at the specified level. An entity has h-index h if h of its works have at least h citations each.
sm_metric_h_index( corpus, level = c("author", "institution", "source", "country"), self_corrected = FALSE, call = rlang::caller_env() )sm_metric_h_index( corpus, level = c("author", "institution", "source", "country"), self_corrected = FALSE, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
level |
Character; the entity level. One of |
self_corrected |
Logical (default |
call |
Caller environment for error reporting. |
Self-correction uses the corpus's internal reference network (no API calls):
citations counted against a work are reduced by those coming from works that
share the entity. Because the network is internal to the corpus, this is a
lower-bound correction on the global cited_by_count.
A tibble with columns for the entity ID/name and h_index.
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() sm_metric_h_index(corpus, level = "author")corpus <- sm_example_corpus() sm_metric_h_index(corpus, level = "author")
Computes the m-index (m-quotient) for entities at the specified level. The m-index equals the h-index divided by the number of years since the entity's first publication (Hirsch, 2005).
sm_metric_m_index( corpus, level = c("author", "institution", "source", "country"), self_corrected = FALSE, call = rlang::caller_env() )sm_metric_m_index( corpus, level = c("author", "institution", "source", "country"), self_corrected = FALSE, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
level |
Character; the entity level. Defaults to |
self_corrected |
Logical (default |
call |
Caller environment for error reporting. |
A tibble with columns for the entity ID/name, h_index,
first_year, career_years, and m_index.
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() sm_metric_m_index(corpus, level = "author")corpus <- sm_example_corpus() sm_metric_m_index(corpus, level = "author")
Computes the Uzzi et al. (2013) novelty score for each work based on atypical combinations of cited journals. A work is novel if its reference list contains unusual journal pairings – combinations that are rarely seen together in the broader literature.
sm_metric_novelty(corpus, call = rlang::caller_env())sm_metric_novelty(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object with populated |
call |
Caller environment for error reporting. |
The algorithm:
For each work, identify the journals (sources) of its cited references.
Enumerate all pairwise journal combinations in each reference list.
Compute the observed frequency of each journal pair across all works.
Compute the expected frequency under independence (product of marginal frequencies).
The novelty score for a work is the median of
across all its journal pairs.
High values indicate atypical combinations.
Works with fewer than 2 references with known journals receive NA.
A tibble with columns work_id and novelty. Higher values
indicate more novel (atypical) journal combinations. Works with
insufficient data receive NA.
Uzzi, B., Mukherjee, S., Stringer, M., & Jones, B. (2013). Atypical Combinations and Scientific Impact. Science, 342(6157), 468–472. doi:10.1126/science.1240474
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() nov <- sm_metric_novelty(corpus) head(nov)corpus <- sm_example_corpus() nov <- sm_metric_novelty(corpus) head(nov)
Computes the Relative Citation Ratio (RCR) for each work in the corpus. RCR normalises citation counts by the expected citation rate of works in the same field and publication year, providing a field-normalised measure of scientific influence.
sm_metric_rcr(corpus, baseline = "field_year", call = rlang::caller_env())sm_metric_rcr(corpus, baseline = "field_year", call = rlang::caller_env())
corpus |
An sm_corpus object. |
baseline |
Character; the normalisation baseline. Currently only
|
call |
Caller environment for error reporting. |
The RCR is calculated as:
Where:
The citation count of work i.
The expected citation count, computed as the mean citation count of works in the same field-year group.
Field assignment uses the top-level concept (level 0) from the concepts table. Works without concept assignments are grouped into an "unclassified" field.
An RCR of 1.0 means the work is cited at the average rate for its field-year. Values above 1.0 indicate above-average impact.
A tibble with columns work_id, cited_by_count, expected_rate,
and rcr. Works without sufficient data receive NA for rcr.
Hutchins, B. I., Yuan, X., Anderson, J. M., & Santangelo, G. M. (2016). Relative Citation Ratio (RCR): A New Metric That Uses Citation Rates to Measure Influence at the Article Level. PLOS Biology, 14(9), e1002541. doi:10.1371/journal.pbio.1002541
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() rcr <- sm_metric_rcr(corpus) head(rcr)corpus <- sm_example_corpus() rcr <- sm_metric_rcr(corpus) head(rcr)
Summarises a work-level impact metric robustly. Citation-based metrics are
heavy-tailed, so the mean is a poor central estimate; this function reports
the median with a bootstrap confidence interval and the proportion of papers
in the top citation fraction of a chosen reference, alongside the mean.
Without an external reference, pp_top10 equals top_pct by construction
and does not measure impact relative to other publications.
sm_metric_summary( corpus, metric = c("citations", "cnci", "rcr", "fwci"), robust = TRUE, n_boot = 2000L, conf = 0.95, top_pct = 0.1, seed = NULL, min_cell = NULL, top_reference = NULL, call = rlang::caller_env() )sm_metric_summary( corpus, metric = c("citations", "cnci", "rcr", "fwci"), robust = TRUE, n_boot = 2000L, conf = 0.95, top_pct = 0.1, seed = NULL, min_cell = NULL, top_reference = NULL, call = rlang::caller_env() )
corpus |
An |
metric |
Which work-level metric to summarise: |
robust |
Logical (default |
n_boot |
Number of bootstrap resamples for the median CI (default
|
conf |
Confidence level for the bootstrap interval (default |
top_pct |
Single number strictly between 0 and 1: the top fraction
of the reference distribution (default |
seed |
Optional integer seed for reproducible bootstrap resampling. When supplied, the RNG state is saved and restored so the call has no global side effect (mirroring scimapR's reproducibility guarantees). |
min_cell |
For |
top_reference |
Reference for |
call |
Caller environment for error reporting. |
The bootstrap uses base-R resampling by default; if the optional boot
package is installed it is used instead (percentile interval).
pp_top10 remains citation-based for every metric; top_reference
changes neither the metric summary nor the bootstrap. Tied citation counts
share the part of their reference block in the top fraction equally,
following Waltman and Schreiber (2013). Using the corpus itself gives
top_pct exactly before rounding. An external reference from comparable
fields and years, or OpenAlex flags, makes this an informative share.
For "cnci" and "fwci" only works with a finite value count, and for
"cnci" only works of corpus$works (a corpus$metrics table may hold
others). metric = "fwci" aborts (class scimapR_no_fwci) when the
works have no fwci value.
A one-row tibble: metric, n, mean, median, and – when
robust = TRUE – median_ci_low, median_ci_high, pp_top10,
n_boot. Type-stable: an empty corpus, or for "cnci" a corpus
without usable impact data, returns a one-row tibble with n = 0 and
NA statistics. The attribute impact_source (character) names where
the values come from: for "cnci" the same label as the
impact_source of sm_its() (such as "works$fwci (OpenAlex)"), for
"fwci" "works$fwci (OpenAlex)", for "citations"
"works$cited_by_count" and for "rcr" "sm_metric_rcr()"; it is
NA on the n = 0 row. For "cnci" and "fwci" a message (class
scimapR_impact_source) names the source too.
Waltman, L., & Schreiber, M. (2013). On the calculation of percentile-based bibliometric indicators. Journal of the American Society for Information Science and Technology, 64(2), 372–379.
sm_summary_works(), sm_count()
Other counting:
sm_citation_maturity(),
sm_citation_window(),
sm_count()
corpus <- sm_example_corpus(n_works = 100, seed = 1) sm_metric_summary(corpus, metric = "citations", seed = 1) # the real-use fixture carries OpenAlex's FWCI, which "cnci" then uses fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) s <- sm_metric_summary(fix, metric = "cnci", robust = FALSE) attr(s, "impact_source")corpus <- sm_example_corpus(n_works = 100, seed = 1) sm_metric_summary(corpus, metric = "citations", seed = 1) # the real-use fixture carries OpenAlex's FWCI, which "cnci" then uses fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) s <- sm_metric_summary(fix, metric = "cnci", robust = FALSE) attr(s, "impact_source")
Constructs a directed citation network from the references table of an
sm_corpus. Each node represents a work and a directed edge runs from the
citing work to the cited work. Only works present in the corpus (i.e. both
the citing and cited work exist in corpus$works) are included as nodes
unless external references are also present via cited_work_id.
sm_network_citation(corpus, call = rlang::caller_env())sm_network_citation(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object. Must contain a non-empty |
call |
Caller environment for error reporting. |
Empty input (zero works or zero references) returns an empty directed
tbl_graph with a single name column on nodes and no edges.
Each work counts a cited item once: repeated links and self-loops are
removed. References with no link to a corpus work trigger a warning when
none is linked; use sm_link_references() to resolve identifiers first.
A tidygraph::tbl_graph object (directed). Node data contains
name (the work ID) plus any columns from corpus$works. Edge data
contains from, to, and any additional reference metadata.
Other networks:
sm_network_cocitation(),
sm_network_collab(),
sm_network_coupling(),
sm_network_coword(),
sm_network_semantic()
corpus <- sm_example_corpus() g <- sm_network_citation(corpus) gcorpus <- sm_example_corpus() g <- sm_network_citation(corpus) g
Constructs an undirected co-citation network. Two works (A and B) are connected by an edge if they are both cited by a common third work. The edge weight equals the number of works that co-cite A and B.
sm_network_cocitation( corpus, min_weight = 2L, linked_only = FALSE, call = rlang::caller_env() )sm_network_cocitation( corpus, min_weight = 2L, linked_only = FALSE, call = rlang::caller_env() )
corpus |
An sm_corpus object with a populated |
min_weight |
Integer; minimum co-citation count to retain an edge.
Defaults to |
linked_only |
Use only references with a |
call |
Caller environment for error reporting. |
The algorithm:
For every citing work, enumerate all pairs of cited works.
Count how often each pair co-occurs.
Filter by min_weight.
Each work counts a cited item once; self-references are excluded. Items
are keyed by cited_work_id, else normalised DOI, else OpenAlex id, else
upper-case reference text with whitespace squished and final periods
removed. Identifier kinds are kept separate, even when labels coincide.
linked_only = TRUE restores the earlier behaviour and warns when none
points to corpus works.
Empty input (zero works, zero references, or no pairs above the threshold)
returns an empty undirected tbl_graph.
A tidygraph::tbl_graph object (undirected). Nodes carry name
(work ID for corpus works, else DOI, OpenAlex id or original reference
text), in_corpus, and columns from corpus$works (missing for external
items). Edges carry a weight column representing co-citation frequency.
Other networks:
sm_network_citation(),
sm_network_collab(),
sm_network_coupling(),
sm_network_coword(),
sm_network_semantic()
corpus <- sm_example_corpus() g <- sm_network_cocitation(corpus, min_weight = 2L) gcorpus <- sm_example_corpus() g <- sm_network_cocitation(corpus, min_weight = 2L) g
Constructs an undirected collaboration (co-authorship) network. Nodes
represent entities at the chosen level (authors, institutions, or
countries) and an edge connects two entities if they co-authored at least
one work. Edge weight equals the number of co-authored works.
sm_network_collab( corpus, level = c("author", "institution", "country"), call = rlang::caller_env() )sm_network_collab( corpus, level = c("author", "institution", "country"), call = rlang::caller_env() )
corpus |
An sm_corpus object with a populated |
level |
Character; the entity level for network nodes. One of
|
call |
Caller environment for error reporting. |
For level = "author", the author_id column from authorships is used
and author metadata is joined from corpus$authors.
For level = "institution", the institution_id column is used and
institution metadata is joined from corpus$institutions.
For level = "country", the country_code column is used.
Empty input returns an empty undirected tbl_graph.
A tidygraph::tbl_graph object (undirected). Nodes carry name
(entity ID or label) and, for "author" level, columns from
corpus$authors. Edges carry a weight column.
Other networks:
sm_network_citation(),
sm_network_cocitation(),
sm_network_coupling(),
sm_network_coword(),
sm_network_semantic()
corpus <- sm_example_corpus() g <- sm_network_collab(corpus, level = "author") gcorpus <- sm_example_corpus() g <- sm_network_collab(corpus, level = "author") g
Constructs an undirected bibliographic coupling network. Two works are
connected if they share at least min_shared cited references. The edge
weight equals the number of shared references.
sm_network_coupling( corpus, min_shared = 1L, linked_only = FALSE, call = rlang::caller_env() )sm_network_coupling( corpus, min_shared = 1L, linked_only = FALSE, call = rlang::caller_env() )
corpus |
An sm_corpus object with a populated |
min_shared |
Integer; minimum number of shared references to create an
edge. Defaults to |
linked_only |
Use only references with a |
call |
Caller environment for error reporting. |
Bibliographic coupling is the mirror of co-citation: two works are coupled
if they cite the same references, suggesting intellectual similarity.
Each work counts a cited item once; self-references are excluded. Items
are keyed by cited_work_id, else normalised DOI, else OpenAlex id, else
upper-case reference text with whitespace squished and final periods
removed. Identifier kinds are kept separate. linked_only = TRUE
restores the earlier behaviour and warns when none points to corpus works.
Empty input (zero works, zero references, or no pairs above the threshold)
returns an empty undirected tbl_graph.
A tidygraph::tbl_graph object (undirected). Nodes carry name
(work ID) and columns from corpus$works. Edges carry a weight column
representing the number of shared references.
Other networks:
sm_network_citation(),
sm_network_cocitation(),
sm_network_collab(),
sm_network_coword(),
sm_network_semantic()
corpus <- sm_example_corpus() g <- sm_network_coupling(corpus, min_shared = 3L) gcorpus <- sm_example_corpus() g <- sm_network_coupling(corpus, min_shared = 3L) g
Constructs an undirected co-word network. Nodes represent terms and an edge connects two terms if they co-occur within the same work. Edge weight equals the number of works in which both terms appear.
sm_network_coword( corpus, field = c("concepts", "title", "abstract", "keywords"), ngram = 1L, min_freq = 5L, stopwords = NULL, vocabulary = NULL, call = rlang::caller_env() )sm_network_coword( corpus, field = c("concepts", "title", "abstract", "keywords"), ngram = 1L, min_freq = 5L, stopwords = NULL, vocabulary = NULL, call = rlang::caller_env() )
corpus |
An sm_corpus object. |
field |
Character; the text field to extract terms from. One of
|
ngram |
Integer; for free-text fields ( |
min_freq |
Integer; minimum document frequency a term must have to be
included. Defaults to |
stopwords |
Character vector of additional stopwords to remove when
tokenising free text. Combined with tidytext::stop_words. Set to
|
vocabulary |
For |
call |
Caller environment for error reporting. |
When field = "concepts", terms come from the concept_name column in
corpus$concepts (no tokenisation needed), of every vocabulary or of
those named in vocabulary.
When field = "keywords", terms are whole keywords from
corpus$concepts, not tokenised. By default these are the author
keywords (the author_keyword family: author-keywords,
manual-tags); a corpus without any falls back to the indexer keywords
(the indexer_keyword family: Keywords Plus, Scopus index keywords,
Zotero automatic tags and MeSH) with a message, and a corpus with
neither gives an empty network with a message. The fallback applies to
the whole corpus, not per work.
When field is "title" or "abstract", the text is tokenised with
tidytext::unnest_tokens() using the chosen ngram size and stopwords
are removed.
Terms are lower-cased, and terms below min_freq are dropped. Empty
input returns an empty undirected tbl_graph.
A tidygraph::tbl_graph object (undirected). Nodes carry name
(the term) and freq (document frequency). Edges carry a weight column
(co-occurrence count).
Other networks:
sm_network_citation(),
sm_network_cocitation(),
sm_network_collab(),
sm_network_coupling(),
sm_network_semantic()
corpus <- sm_example_corpus() g <- sm_network_coword(corpus, field = "concepts", min_freq = 5L) g # Keyword networks from a Web of Science file path <- tempfile(fileext = ".txt") writeLines(c( "PT J", "AU Doe, J", "TI First work", "DE heat illness; recruits; cooling", "ID HYPERTHERMIA; EXERCISE", "PY 2021", "UT WOS:000000000000001", "ER", "PT J", "AU Roe, R", "TI Second work", "DE heat illness; recruits", "ID HYPERTHERMIA; EXERCISE", "PY 2022", "UT WOS:000000000000002", "ER", "EF" ), path) wos <- sm_read_wos(path, verbose = FALSE) sm_network_coword(wos, field = "keywords", min_freq = 2L) sm_network_coword(wos, field = "keywords", vocabulary = "keywords-plus", min_freq = 2L)corpus <- sm_example_corpus() g <- sm_network_coword(corpus, field = "concepts", min_freq = 5L) g # Keyword networks from a Web of Science file path <- tempfile(fileext = ".txt") writeLines(c( "PT J", "AU Doe, J", "TI First work", "DE heat illness; recruits; cooling", "ID HYPERTHERMIA; EXERCISE", "PY 2021", "UT WOS:000000000000001", "ER", "PT J", "AU Roe, R", "TI Second work", "DE heat illness; recruits", "ID HYPERTHERMIA; EXERCISE", "PY 2022", "UT WOS:000000000000002", "ER", "EF" ), path) wos <- sm_read_wos(path, verbose = FALSE) sm_network_coword(wos, field = "keywords", min_freq = 2L) sm_network_coword(wos, field = "keywords", vocabulary = "keywords-plus", min_freq = 2L)
Constructs an undirected k-nearest-neighbour network based on work
embeddings. Each work is connected to its k nearest neighbours in
embedding space (cosine similarity). The edge weight is the cosine
similarity between the connected works.
sm_network_semantic(corpus, k = 10L, call = rlang::caller_env())sm_network_semantic(corpus, k = 10L, call = rlang::caller_env())
corpus |
An sm_corpus object. Must contain an |
k |
Integer; number of nearest neighbours per work. Defaults to |
call |
Caller environment for error reporting. |
Work IDs and embedding row names must be unique. Repeated IDs stop
with class scimapR_duplicate_ids; use sm_validate() to inspect them.
Embeddings must be present in corpus$embeddings (a numeric matrix with
row names matching work_id). Use sm_embed_works() to compute them.
Works without a row, such as works without text, are not nodes.
The function computes cosine similarity via matrix multiplication on
L2-normalised vectors and selects the top-k neighbours for each work.
Empty input or missing embeddings returns an empty undirected tbl_graph.
A tidygraph::tbl_graph object (undirected). Nodes carry name
(work ID) and columns from corpus$works. Edges carry a weight column
(cosine similarity, between 0 and 1).
Other networks:
sm_network_citation(),
sm_network_cocitation(),
sm_network_collab(),
sm_network_coupling(),
sm_network_coword()
corpus <- sm_example_corpus(with_embeddings = TRUE) g <- sm_network_semantic(corpus, k = 5L) gcorpus <- sm_example_corpus(with_embeddings = TRUE) g <- sm_network_semantic(corpus, k = 5L) g
Canonical values for works$oa_status. All levels except closed are
open; missing and unrecognised values are unknown. Status definitions
follow the OpenAlex taxonomy (see the link below),
with open for access without a known colour. When an export names several
statuses, the package uses the first recognised level in the order below:
diamond, gold, hybrid, bronze, green, open, closed.
sm_corpus() and sm_corpus_from_tables() normalise common spellings
with a message. Consumers also recognise those spellings in saved corpora.
The column remains character, and unknown strings are kept as written.
sm_oa_statuses(describe = FALSE)sm_oa_statuses(describe = FALSE)
describe |
Logical; if |
A character vector or a tibble of status descriptions.
sm_audit_oa(), sm_filter_works(),
https://help.openalex.org/data/works/open-access/
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_save_corpus(),
sm_validate(),
validate_sm_corpus()
sm_oa_statuses() sm_oa_statuses(describe = TRUE)sm_oa_statuses() sm_oa_statuses(describe = TRUE)
Qualitative viridis palette
sm_palette_qualitative(n, option = "D")sm_palette_qualitative(n, option = "D")
n |
Number of colours. |
option |
Viridis palette option. |
A character vector of hex colours.
Other plots:
autoplot.sm_corpus(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
Source productivity distribution following Bradford's law of scattering.
sm_plot_bradford(corpus, dark = FALSE, ...)sm_plot_bradford(corpus, dark = FALSE, ...)
corpus |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
Visualise the citation network of top-cited works.
sm_plot_citation_network( corpus, top_n = 50L, dark = FALSE, precompute = FALSE, max_nodes = NULL, ... )sm_plot_citation_network( corpus, top_n = 50L, dark = FALSE, precompute = FALSE, max_nodes = NULL, ... )
corpus |
An |
top_n |
Number of top works to include. |
dark |
Logical; dark mode? |
precompute |
Logical (default |
max_nodes |
Optional integer node cap. |
... |
Additional arguments. |
A ggplot object (a ggraph plot when precompute = FALSE, a plain
ggplot when precompute = TRUE).
For big networks, prefer precompute = TRUE and save the returned object
(e.g. with saveRDS()); printing it in a document then re-renders cheaply
without recomputing the layout. See the networks section of the getting-
started vignette.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() sm_plot_citation_network(corpus)corpus <- sm_example_corpus() sm_plot_citation_network(corpus)
Visualise international or institutional collaboration patterns.
sm_plot_collab( corpus, level = c("country", "institution", "author"), top_n = 20L, dark = FALSE, precompute = FALSE, max_nodes = NULL, ... )sm_plot_collab( corpus, level = c("country", "institution", "author"), top_n = 20L, dark = FALSE, precompute = FALSE, max_nodes = NULL, ... )
corpus |
An |
level |
Collaboration level. |
top_n |
Number of top entities to include. |
dark |
Logical; dark mode? |
precompute |
Logical (default |
max_nodes |
Optional integer node cap. |
... |
Additional arguments. |
A ggplot object (ggraph when precompute = FALSE, plain ggplot
when precompute = TRUE).
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() sm_plot_collab(corpus, level = "country")corpus <- sm_example_corpus() sm_plot_collab(corpus, level = "country")
Visualise collaborator retention and turnover across career periods.
sm_plot_collab_turnover(traj, dark = FALSE, ...)sm_plot_collab_turnover(traj, dark = FALSE, ...)
traj |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other trajectory:
is_sm_trajectory(),
sm_author_trajectory(),
sm_plot_topic_pivots(),
sm_plot_trajectory(),
sm_researcher_profile(),
sm_trajectory
corpus <- sm_example_corpus() traj <- sm_author_trajectory(corpus, author_id = corpus$authors$author_id[1]) sm_plot_collab_turnover(traj)corpus <- sm_example_corpus() traj <- sm_author_trajectory(corpus, author_id = corpus$authors$author_id[1]) sm_plot_collab_turnover(traj)
Four-panel visualization: geographic distribution, gender mix, funding sources, and OA evolution. Uses viridis throughout.
sm_plot_equity_dashboard(corpus, dark = FALSE, ...)sm_plot_equity_dashboard(corpus, dark = FALSE, ...)
corpus |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object composed via patchwork.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() sm_plot_equity_dashboard(corpus)corpus <- sm_example_corpus() sm_plot_equity_dashboard(corpus)
Visualise how research topics/clusters evolve across time periods.
sm_plot_evolution(corpus, time_var = "year", dark = FALSE, ...)sm_plot_evolution(corpus, time_var = "year", dark = FALSE, ...)
corpus |
An |
time_var |
Column name for time variable. Default |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus(with_embeddings = TRUE, seed = 42) corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10) sm_plot_evolution(corpus)corpus <- sm_example_corpus(with_embeddings = TRUE, seed = 42) corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10) sm_plot_evolution(corpus)
Vocabulary growth curve over accumulated documents.
sm_plot_heaps(corpus, dark = FALSE, ...)sm_plot_heaps(corpus, dark = FALSE, ...)
corpus |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
UMAP/t-SNE/PCA scatter plot of work embeddings, coloured by cluster.
sm_plot_landscape( corpus, color_by = "cluster_id", reducer = c("umap", "tsne", "pca"), n_components = 2L, dark = FALSE, ... )sm_plot_landscape( corpus, color_by = "cluster_id", reducer = c("umap", "tsne", "pca"), n_components = 2L, dark = FALSE, ... )
corpus |
An |
color_by |
Column name in works to colour by. Default |
reducer |
Dimensionality reduction method. |
n_components |
Number of output dimensions (2 for plotting). |
dark |
Logical; dark mode? |
... |
Additional arguments. |
UMAP retains its Euclidean metric and automatic thread settings.
If its PCA dependency raises the known older-R NULL-input error, it
retries with base SVD initialisation and reports
scimapR_umap_pca_fallback. Other errors propagate unchanged.
A ggplot object with one point per row of
corpus$embeddings, so works without an embedding are not shown.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10) sm_plot_landscape(corpus)corpus <- sm_example_corpus() corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10) sm_plot_landscape(corpus)
Frequency distribution of author productivity compared to Lotka's law.
sm_plot_lotka(corpus, dark = FALSE, ...)sm_plot_lotka(corpus, dark = FALSE, ...)
corpus |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
Bar chart of publication counts over time.
sm_plot_production(corpus, by = c("year", "month"), dark = FALSE, ...)sm_plot_production(corpus, by = c("year", "month"), dark = FALSE, ...)
corpus |
An |
by |
Time unit: |
dark |
Logical; dark mode? |
... |
Additional arguments (currently unused). |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() sm_plot_production(corpus)corpus <- sm_example_corpus() sm_plot_production(corpus)
Strategic diagram plotting clusters by centrality (external links) and density (internal links), following Callon et al.
sm_plot_thematic_map(corpus, by = "cluster_id", dark = FALSE, ...)sm_plot_thematic_map(corpus, by = "cluster_id", dark = FALSE, ...)
corpus |
An |
by |
Grouping variable. Default |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_top(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus(with_embeddings = TRUE, seed = 42) corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10) sm_plot_thematic_map(corpus)corpus <- sm_example_corpus(with_embeddings = TRUE, seed = 42) corpus <- sm_cluster_hdbscan(corpus, min_cluster_size = 10) sm_plot_thematic_map(corpus)
Horizontal bar chart of top authors, sources, institutions, or countries.
sm_plot_top( corpus, level = c("authors", "sources", "institutions", "countries"), n = 20L, metric = c("count", "citations"), dark = FALSE, ... )sm_plot_top( corpus, level = c("authors", "sources", "institutions", "countries"), n = 20L, metric = c("count", "citations"), dark = FALSE, ... )
corpus |
An |
level |
Entity level. |
n |
Number of top entities to show. |
metric |
Ranking metric. |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_scale_color(),
sm_theme()
corpus <- sm_example_corpus() sm_plot_top(corpus, level = "authors", n = 10)corpus <- sm_example_corpus() sm_plot_top(corpus, level = "authors", n = 10)
Vertical timeline showing when an author's research focus shifted.
sm_plot_topic_pivots(traj, dark = FALSE, ...)sm_plot_topic_pivots(traj, dark = FALSE, ...)
traj |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object.
Other trajectory:
is_sm_trajectory(),
sm_author_trajectory(),
sm_plot_collab_turnover(),
sm_plot_trajectory(),
sm_researcher_profile(),
sm_trajectory
corpus <- sm_example_corpus() traj <- sm_author_trajectory(corpus, author_id = corpus$authors$author_id[1]) sm_plot_topic_pivots(traj)corpus <- sm_example_corpus() traj <- sm_author_trajectory(corpus, author_id = corpus$authors$author_id[1]) sm_plot_topic_pivots(traj)
Multi-panel visualization of an author's career trajectory.
sm_plot_trajectory(traj, dark = FALSE, ...)sm_plot_trajectory(traj, dark = FALSE, ...)
traj |
An |
dark |
Logical; dark mode? |
... |
Additional arguments. |
A ggplot object composed via patchwork.
Other trajectory:
is_sm_trajectory(),
sm_author_trajectory(),
sm_plot_collab_turnover(),
sm_plot_topic_pivots(),
sm_researcher_profile(),
sm_trajectory
corpus <- sm_example_corpus() traj <- sm_author_trajectory(corpus, author_id = corpus$authors$author_id[1]) sm_plot_trajectory(traj)corpus <- sm_example_corpus() traj <- sm_author_trajectory(corpus, author_id = corpus$authors$author_id[1]) sm_plot_trajectory(traj)
Returns the full provenance table from a corpus, documenting the data lineage of every work: where it was fetched from, when, with which query, and which version of scimapR performed the ingestion.
sm_provenance(corpus)sm_provenance(corpus)
corpus |
An |
A tibble with columns work_id, source, source_id_external,
fetch_date, query, engine, scimapR_version, and prompt_hash,
and the optional columns kind (the kind of step: "api_query",
"file", "enrichment", "refresh" or "manual", see
sm_certificate(); NA for rows that do not record one), filter and
n_max (the filter and the maximum number of works of a search, which
sm_rebuild_from_cert() uses to replay it). File readers record the
file in query relative to the working directory when it lies below
it, else by its name.
Other reproducibility:
sm_certificate(),
sm_cite_corpus(),
sm_diff_corpora(),
sm_hash_corpus(),
sm_register_input(),
sm_snapshot()
corpus <- sm_example_corpus() sm_provenance(corpus)corpus <- sm_example_corpus() sm_provenance(corpus)
Full-text search across titles and abstracts in a corpus.
sm_query(corpus, query, fields = c("title", "abstract"), ignore_case = TRUE)sm_query(corpus, query, fields = c("title", "abstract"), ignore_case = TRUE)
corpus |
An |
query |
A search string (regex supported). |
fields |
Which fields to search. Default |
ignore_case |
Logical; case-insensitive search? |
An sm_corpus with matching works.
Other filters:
sm_filter_works()
corpus <- sm_example_corpus() results <- sm_query(corpus, "transcriptomics")corpus <- sm_example_corpus() results <- sm_query(corpus, "transcriptomics")
Constructs an sm_question S3 object encoding a structured research
question using a recognised framework (PICO, PECO, PICOS, SPIDER, or
free-form). The object carries all structured fields, auto-generated
query strings suitable for bibliographic databases, and a prompt template
for LLM-based screening.
The question receives a content-addressable ID (SHA-256 hash of all input fields), ensuring that identical questions always produce the same identifier regardless of when they are created.
sm_question( text, framework = c("PICO", "PECO", "PICOS", "SPIDER", "free"), population = NULL, intervention = NULL, comparison = NULL, outcome = NULL, exposure = NULL, design = NULL, timeframe = NULL, include_terms = NULL, exclude_terms = NULL, languages = "en", notes = NULL ) ## S3 method for class 'sm_question' print(x, ...) ## S3 method for class 'sm_question' format(x, ...)sm_question( text, framework = c("PICO", "PECO", "PICOS", "SPIDER", "free"), population = NULL, intervention = NULL, comparison = NULL, outcome = NULL, exposure = NULL, design = NULL, timeframe = NULL, include_terms = NULL, exclude_terms = NULL, languages = "en", notes = NULL ) ## S3 method for class 'sm_question' print(x, ...) ## S3 method for class 'sm_question' format(x, ...)
text |
Character. The full research question in natural language. |
framework |
Character. The structuring framework. One of |
population |
Character. The population or sample under study. |
intervention |
Character. The intervention or phenomenon of interest. |
comparison |
Character. The comparator or control condition. |
outcome |
Character. The outcome(s) of interest. |
exposure |
Character. The exposure (for PECO framework). |
design |
Character. Study design (for PICOS/SPIDER frameworks). |
timeframe |
Character. Optional time restriction. |
include_terms |
Character vector. Additional terms that must appear in relevant works. |
exclude_terms |
Character vector. Terms whose presence disqualifies a work. |
languages |
Character vector. ISO 639-1 language codes to restrict
the search. Default |
notes |
Character. Free-text notes about the question. |
x |
An |
... |
Ignored. |
An sm_question S3 object with fields:
Content-hash identifier.
The research question text.
The structuring framework.
POSIXct creation timestamp.
Structured facets (may be NULL).
Term filters.
Language filter.
Free-text notes.
Named list of database-specific query strings.
A prompt template for LLM screening.
print(): x invisibly.
format(): A single character string.
Other question:
is_sm_question(),
sm_corpus_for_question(),
sm_screen_against_question(),
sm_screen_regex(),
sm_screen_summary()
q <- sm_question( text = "Does immunotherapy improve survival in metastatic melanoma?", framework = "PICO", population = "adults with metastatic melanoma", intervention = "immune checkpoint inhibitors", comparison = "chemotherapy", outcome = "overall survival" ) print(q)q <- sm_question( text = "Does immunotherapy improve survival in metastatic melanoma?", framework = "PICO", population = "adults with metastatic melanoma", intervention = "immune checkpoint inhibitors", comparison = "chemotherapy", outcome = "overall survival" ) print(q)
Detect the bibliographic file format from the file extension and content, then dispatch to the appropriate reader function.
sm_read_auto( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_auto( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a bibliographic file. |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
Format detection proceeds in two stages:
Extension-based: .bib (BibTeX), .ris (RIS), .json/.jsonl
(OpenAlex JSON), .xml (PubMed XML or EndNote XML).
Content-based: For .csv, .tsv, and .txt files, the first
few lines are inspected for format-specific signatures:
WoS plaintext: begins with FN or PT tags
WoS tab-delimited: header contains PT, AU, TI and UT; also detected
in .tab files and files without an extension
Scopus CSV: contains EID column header
Lens CSV: contains Lens ID column header
Dimensions CSV: contains Dimensions ID or PubYear header
Cochrane CSV: contains Cochrane in header or record-like structure
Zotero CSV: contains Key and Item Type columns
RIS-format content in non-.ris files
For XML files, the parsed root distinguishes PubMed XML
(PubmedArticleSet, PubmedArticle, PubmedBookArticleSet) from
EndNote XML (xml/records or records). Unknown roots are unsupported;
malformed XML raises a file-reading error.
For .nbib and .enw, the first 20 lines determine the content: RIS
records are accepted, but MEDLINE text (PMID- in .nbib, .txt or
files without extensions) and Refer text (% tags in .enw) are not
supported. Export MEDLINE records as PubMed XML, or use
sm_fetch_pubmed(); export Refer libraries as RIS or EndNote XML.
A leading UTF-8 BOM is ignored for text format detection.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_auto("references.bib") corpus <- sm_read_auto("exported_data.csv") ## End(Not run)## Not run: corpus <- sm_read_auto("references.bib") corpus <- sm_read_auto("exported_data.csv") ## End(Not run)
Parse standard BibTeX (.bib) files into an sm_corpus object.
Extracts entry type, title, author, year, DOI, journal, abstract, volume,
pages, and keywords from each @type{key, ...} entry.
sm_read_bib( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_bib( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser follows the BibTeX format specification as described in
the original BibTeX documentation (Patashnik, 1988). Field values may be
enclosed in braces or double-quotes and may span multiple lines. String
concatenation with # is not supported; @string and @preamble
entries are skipped.
Protective braces are removed from titles, journals, abstracts, keywords
and DOIs; escaped braces remain literal. Common LaTeX accent commands
(\", \', \^, \~, \=, \., \c, \v, \u,
\H, \k, \r, and backslash followed by a grave accent) decode
to precomposed letters where available, else the bare letter.
Letter commands \ss, \o, \O, \aa, \AA,
\ae, \AE, \oe, \OE, \l, \L and \i also decode, as do
\&, \%, \$, \# and \_. DOI cleaning only removes braces
and decodes those five escapes. Ties (~), -- and math expressions
remain as written. Whitespace outside math is collapsed and trimmed.
Only a lowercase and outside braces separates authors; a comma outside
braces flips a name from surname-first to given-name-first. Braced
corporate names stay together, and a trailing and others is retained.
Quoted field values end only at a quote outside braces. The first
isolated four-digit year (1000–2999) in year is used, falling back to
the BibLaTeX date field when no such year is present.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_bib("references.bib") corpus$works ## End(Not run)## Not run: corpus <- sm_read_bib("references.bib") corpus$works ## End(Not run)
Parse Cochrane Library (CDSR) export files into an sm_corpus object.
Supports both CSV/tab-delimited exports and RIS-format exports from the
Cochrane Library search interface.
sm_read_cochrane( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_cochrane( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a Cochrane export file
( |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The Cochrane Library exports records in CSV or RIS format. The CSV export typically contains columns such as: Record Number (or #), Authors, Title, Source, Year, DOI, Abstract. RIS exports follow the standard RIS specification and are delegated to the RIS parser.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_cochrane("cochrane_export.csv") corpus$works ## End(Not run)## Not run: corpus <- sm_read_cochrane("cochrane_export.csv") corpus$works ## End(Not run)
Parse Dimensions CSV export files into an sm_corpus object.
Handles the standard Dimensions analytics platform CSV export format.
sm_read_dimensions( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_dimensions( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a Dimensions CSV file. |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser reads the Dimensions CSV export using readr::read_csv().
Dimensions CSV files may begin with informational rows before the header;
the parser skips lines that do not look like the header. Key columns
matched (case-insensitive): Title, DOI, Authors, Source title
(or Source Title), PubYear (or Publication Year), Times cited,
Document Type (or Publication Type), Abstract, Dimensions ID,
MeSH terms, Author Keywords, ISSN and Corresponding Authors.
The authors that the Corresponding Authors column names (;-separated,
an affiliation in parentheses after a name is ignored) get
authorships$is_corresponding = TRUE and the other authors of the
record FALSE; names are compared by surname and given names or
initials. A name that fits several authors equally, such as "Wang, Y"
for Wang Yan and Wang Yu, leaves them NA. Without that column, or when
it names none of the record's authors for certain, is_corresponding is
NA (unknown).
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_dimensions("dimensions_export.csv") corpus$works ## End(Not run)## Not run: corpus <- sm_read_dimensions("dimensions_export.csv") corpus$works ## End(Not run)
Parse EndNote XML export files into an sm_corpus object.
Uses xml2 to extract record metadata from the EndNote XML format.
sm_read_endnote( path, encoding = "UTF-8", verbose = TRUE, call = rlang::caller_env() )sm_read_endnote( path, encoding = "UTF-8", verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to an EndNote XML file. |
encoding |
Character scalar. File encoding (default |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The parser uses xml2::read_xml() to read the EndNote XML format. Each
xml/records/record element (or records/record) is processed. The
following fields are extracted:
titles/title/style for the title
contributors/authors/author/style for author names
dates/year/style for year
periodical/full-title/style or secondary-title/style for journal
electronic-resource-num/style for DOI
abstract/style for abstract
ref-type attribute for document type
keywords/keyword/style for keywords
accession-num/style for record identifier
No bibliometrix engine is available since EndNote XML is not directly
supported as a format by bibliometrix::convert2df().
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_endnote("library.xml") corpus$works ## End(Not run)## Not run: corpus <- sm_read_endnote("library.xml") corpus$works ## End(Not run)
Parse Lens.org scholarly CSV export files into an sm_corpus object.
Handles the standard Lens scholarly export format with columns for
Lens ID, Title, Authors, DOI, Source Title, Abstract, and more.
sm_read_lens( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_lens( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a Lens CSV file. |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser reads the Lens.org scholarly CSV export using
readr::read_csv(). Key columns matched (case-insensitive):
Lens ID, Title, Date Published, Year Published (or Publication Year),
DOI, Source Title, Authors, Abstract, Citing Works Count
(or Scholarly Citations Count), Document Type (or Publication Type),
Open Access Colour (or Color), Open Access Status, Is Open Access,
or exactly Open Access (in that priority order; never a licence), MeSH Terms,
Keywords, Source ISSN, Language.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_lens("lens_export.csv") corpus$works ## End(Not run)## Not run: corpus <- sm_read_lens("lens_export.csv") corpus$works ## End(Not run)
Parse OpenAlex JSON export files into an sm_corpus object. Handles
JSON arrays of works, a saved API response (an object whose results
hold the works, compact or pretty-printed), a single work object and
newline-delimited JSON (JSONL, one work or one response per line).
Extracts fields from the OpenAlex works schema including id, doi, title,
publication year, type, cited_by_count, authorships, host venue,
concepts, open access status, and referenced works.
The works also keep OpenAlex's field-weighted citation impact (fwci),
its citation percentile (citation_percentile, 0 to 1, with the flags
is_top_10pct and is_top_1pct) and the day of the record's
updated_date (citations_as_of, NA when the file has none), all NA
where a record lacks them, and the corpus gets the yearly citation
counts (counts_by_year) as citations_by_year (see sm_corpus()).
Each authorship's raw_affiliation holds its raw_affiliation_strings
(joined with "; "), else the raw_affiliation_string of its
affiliations, else the names of its institutions. Works are linked to
their source (works$source_id), and sources are told apart by their
OpenAlex id, else by their name.
sm_read_openalex_json( path, encoding = "UTF-8", verbose = TRUE, call = rlang::caller_env() )sm_read_openalex_json( path, encoding = "UTF-8", verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a |
encoding |
Character scalar. File encoding (default |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The parser reads the OpenAlex works JSON schema as documented at https://developers.openalex.org/api-entities/works/work-object. The whole file is parsed first; only when that fails is it read as newline-delimited JSON, one value per line. No bibliometrix engine is available since OpenAlex is not a shared bibliometric export format.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
sm_fetch_openalex(), sm_enrich_openalex_impact()
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
path <- system.file("extdata", "example_openalex_inverted.json", package = "scimapR") corpus <- sm_read_openalex_json(path, verbose = FALSE) corpus$works[c("title", "year", "cited_by_count")]path <- system.file("extdata", "example_openalex_inverted.json", package = "scimapR") corpus <- sm_read_openalex_json(path, verbose = FALSE) corpus$works[c("title", "year", "cited_by_count")]
Parse PubMed XML export files (NCBI DTD format) into an sm_corpus
object. Extracts article metadata from PubmedArticle nodes including
PMID, title, abstract, journal, authors, MeSH terms, DOI, and
publication type.
sm_read_pubmed_xml( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_pubmed_xml( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a PubMed XML file. |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser uses xml2::read_xml() to parse the NCBI PubMed XML
DTD. Each PubmedArticle element is processed to extract:
MedlineCitation/PMID for the PubMed identifier
MedlineCitation/Article/ArticleTitle for the title
MedlineCitation/Article/Abstract/AbstractText for the abstract
(multiple sections are concatenated)
MedlineCitation/Article/Journal/Title and /ISSN for the journal
MedlineCitation/Article/Journal/JournalIssue/PubDate/Year for year
MedlineCitation/Article/AuthorList/Author for authors
MedlineCitation/MeshHeadingList/MeshHeading for MeSH terms; the
concept score is 1 for a major topic of the article (MajorTopicYN
on the descriptor or one of its qualifiers) and 0 otherwise
PubmedData/ArticleIdList/ArticleId[@IdType='doi'] for DOI
MedlineCitation/Article/PublicationTypeList for document type
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_pubmed_xml("pubmed_result.xml") corpus$works ## End(Not run)## Not run: corpus <- sm_read_pubmed_xml("pubmed_result.xml") corpus$works ## End(Not run)
Parse RIS (Research Information Systems) tag-value format files into an
sm_corpus object. Supports the standard RIS specification with two-letter
tags followed by two spaces, a dash, and a space.
sm_read_ris( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_ris( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser follows the RIS format specification (Thomson Reuters).
Each record starts with TY - and ends with ER -. Tags are two
uppercase letters followed by two spaces, a hyphen, and a space. Repeating
tags (AU, KW) generate multiple values per record.
Publication year comes from the first usable year in PY, Y1, then
DA. Bare years and dates such as 2019/03/01/ or 2020/// are accepted:
the first isolated four-digit year from 1000 through 2999 is used.
Y2 is never a fallback because it often holds an access or secondary
date. Unrecognised dates yield NA without a coercion warning.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_scopus(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_ris("references.ris") corpus$works ## End(Not run)## Not run: corpus <- sm_read_ris("references.ris") corpus$works ## End(Not run)
Parse Scopus CSV export files into an sm_corpus object. Handles the
standard Scopus CSV format with columns for Authors, Title, Year,
Source title, DOI, Abstract, and more.
sm_read_scopus( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_scopus( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a Scopus CSV file. |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser reads the Scopus CSV export using readr::read_csv().
Scopus CSV column names may vary between export versions; the parser
matches on known column name patterns. Key columns:
Authors, Title, Year, Source title, DOI, Abstract, Document Type,
Cited by, Author Keywords, Index Keywords, ISSN, Language of Original
Document, EID.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_wos(),
sm_read_zotero()
## Not run: corpus <- sm_read_scopus("scopus.csv") corpus$works ## End(Not run)## Not run: corpus <- sm_read_scopus("scopus.csv") corpus$works ## End(Not run)
Parse Web of Science (WoS) plaintext or tab-delimited export files into
an sm_corpus object, preserving authors, fields and cited references.
sm_read_wos( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )sm_read_wos( path, encoding = "UTF-8", engine = c("native", "bibliometrix", "auto"), verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a plaintext or tab-delimited export. |
encoding |
Character scalar. File encoding (default |
engine |
Character scalar. One of |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The native parser follows the Web of Science Core Collection export format.
Tab-delimited files have a header containing PT, AU, TI and UT and one
record per line. Cells are unquoted, so quotes in titles are preserved.
Both layouts share field, author and reference-linking rules. Tab-delimited
input always uses the native path with engine = "native" or "auto";
engine = "bibliometrix" rejects it. UTF-8 BOMs are accepted; UTF-16
exports can be read with encoding = "UTF-16LE".
A file whose records all lack a title, accession number and author is an
error. Such records among readable ones are dropped with one warning,
including when verbose = FALSE. Files without any PT record still
return an empty corpus.
Each record begins with PT (publication type) and ends with ER.
Field tags are two uppercase letters followed by a single space.
Continuation lines begin with three spaces. Tags parsed:
AU or AF (authors), TI (title), SO (source), SN and EI (ISSN and
electronic ISSN), J9 and JI (source abbreviations), AB (abstract),
DI (DOI), PY (year), DT (document type), LA (language), TC (times
cited), UT (accession number, kept in provenance), PM (PubMed ID),
C1 (addresses), RP (corresponding authors), CR (cited references),
DE (author keywords),
ID (Keywords Plus), WC (Web of Science categories) and SC (research
areas). Other tags are not read.
Each distinct SO becomes a row of sources, with issn_l from SN
(EI when SN is missing), issn holding SN and EI, and the abbreviations
J9 and JI in the columns abbreviation and iso_abbreviation.
DE, ID, WC and SC become concepts, one row per ;-separated term, in
the vocabularies "author-keywords", "keywords-plus", "wos-category"
and "wos-subject-category" of sm_concept_vocabularies(). Terms keep
the case of the file.
Author names are stored in the AU form, such as "J Smith" for
"Smith, J", and authors$display_name_alternatives keeps the name as
the file writes it, which tells a surname of several words such as
"Garcia Lopez" from the initials.
Both parsers write one authorship row per author and address. The names in the brackets of C1 link authors to addresses: each name belongs to the author with the same full name (from AF), else to the one with the same surname and initials, else to the one with the same surname and first initial, so a short surname such as "He" never takes the address of "Chen". An address that C1 lists without names, as older records do, belongs to every author of the record, so each of them gets a row for it. An address whose names designate none of the authors belongs to the author of a single-author record and else to nobody.
The corresponding authors come from RP, which names them before
"(corresponding author)" ("(reprint author)" in older files), in any
case, with each name and its address on a line of its own or on one
line. The authors these names designate, matched like the names of C1,
get is_corresponding = TRUE on all their rows and the other authors
FALSE. When a name fits several authors equally (namesakes with the
same surname and initials), the one whose C1 addresses include the
address RP gives with the name is meant; if that does not tell them
apart, they get NA. A record without RP, or whose RP designates none
of its authors for certain, gets NA (unknown) for all its authors;
the native parser reports the number of such records when
verbose = TRUE.
The "bibliometrix" engine (also used by "auto" when bibliometrix can
read the file) converts the data frame of bibliometrix::convert2df()
as as_sm_corpus() does: it reads the same tags into sources,
references and concepts, tells authors apart by the full names of AF
and links C1 and RP to the authors by the same rule.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_zotero()
## Not run: corpus <- sm_read_wos("savedrecs.txt") corpus$works ## End(Not run)## Not run: corpus <- sm_read_wos("savedrecs.txt") corpus$works ## End(Not run)
Parse Zotero CSV export files into an sm_corpus object.
Handles the standard Zotero CSV export format with columns for
Key, Title, Author, Publication Year, DOI, Publication Title,
Abstract, and Item Type.
sm_read_zotero( path, encoding = "UTF-8", verbose = TRUE, call = rlang::caller_env() )sm_read_zotero( path, encoding = "UTF-8", verbose = TRUE, call = rlang::caller_env() )
path |
Character scalar. Path to a Zotero CSV file. |
encoding |
Character scalar. File encoding (default |
verbose |
Logical. Print progress messages? |
call |
Caller environment for error reporting. |
An sm_corpus object.
The parser reads the Zotero CSV export format using readr::read_csv().
Zotero CSV exports have a standard set of column names. Key columns
matched (case-insensitive): Key, Title, Author, Publication Year,
DOI, Publication Title, Abstract Note, Item Type, ISSN, Language,
Manual Tags, Automatic Tags. No bibliometrix engine is available since
Zotero CSV is not a format supported by bibliometrix::convert2df().
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959–975. doi:10.1016/j.joi.2017.08.007
Other readers:
sm_read_auto(),
sm_read_bib(),
sm_read_cochrane(),
sm_read_dimensions(),
sm_read_endnote(),
sm_read_lens(),
sm_read_openalex_json(),
sm_read_pubmed_xml(),
sm_read_ris(),
sm_read_scopus(),
sm_read_wos()
## Not run: corpus <- sm_read_zotero("zotero_export.csv") corpus$works ## End(Not run)## Not run: corpus <- sm_read_zotero("zotero_export.csv") corpus$works ## End(Not run)
Computes a symmetric set difference between two corpora (or coercible data
frames) using the shared DOI-then-title matching engine. This generalises
sm_diff_corpora(), which compares strictly by internal work_id:
sm_reconcile() instead matches on content (normalised DOI with a fuzzy
title fallback), so it works across corpora from different sources that do
not share identifiers.
sm_reconcile( corpus_a, corpus_b, match = c("doi_then_title", "doi", "title"), threshold = 0.9, call = rlang::caller_env() ) ## S3 method for class 'sm_reconciliation' print(x, ...) ## S3 method for class 'sm_reconciliation' summary(object, ...) ## S3 method for class 'sm_reconciliation' autoplot(object, ...)sm_reconcile( corpus_a, corpus_b, match = c("doi_then_title", "doi", "title"), threshold = 0.9, call = rlang::caller_env() ) ## S3 method for class 'sm_reconciliation' print(x, ...) ## S3 method for class 'sm_reconciliation' summary(object, ...) ## S3 method for class 'sm_reconciliation' autoplot(object, ...)
corpus_a, corpus_b
|
An |
match |
Matching strategy: |
threshold |
Minimum Jaro-Winkler title similarity ( |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
object |
An |
An sm_reconciliation S3 object with components:
Tibble of matched pairs: a_id, b_id, title,
match_type, match_score.
Tibble of corpus_a records absent from corpus_b
(id, doi, title, year).
Tibble of corpus_b records absent from corpus_a.
Match provenance tibble (a_id, b_id, match_type,
match_score), the same shape as sm_coverage_audit()'s.
One-row tibble with counts.
print returns x invisibly.
summary returns the one-row summary tibble.
autoplot returns a ggplot set-size bar chart.
sm_coverage_audit(), sm_diff_corpora()
Other coverage:
sm_coverage_audit(),
sm_coverage_breakdowns(),
sm_journal_in_index(),
sm_match_types()
a <- sm_example_corpus(n_works = 20, seed = 1) b <- sm_example_corpus(n_works = 20, seed = 1)[5:20] rec <- sm_reconcile(a, b) reca <- sm_example_corpus(n_works = 20, seed = 1) b <- sm_example_corpus(n_works = 20, seed = 1)[5:20] rec <- sm_reconcile(a, b) rec
Re-fetches metadata for works whose last_refreshed timestamp is older
than max_age_days. For each stale work, the original provenance source
is consulted: OpenAlex supplies citation counts and open-access status;
Crossref supplies citation counts. Other sources remain stale. Failed
requests, missing identifiers and records not returned are reported.
Only works with returned updates receive a new timestamp and provenance
row. metadata$last_refresh advances only when at least one work was
refreshed and none failed. Enrichment rows never select a refresh source.
The operation is idempotent: running it twice in succession will not
re-fetch works that were just refreshed. The function refuses to operate
on a locked corpus; use sm_unlock() first.
sm_refresh( corpus, max_age_days = 30, what = c("citations", "oa_status"), sources = NULL, verbose = TRUE, api_key = Sys.getenv("OPENALEX_API_KEY"), mailto = Sys.getenv("SCIMAPR_MAILTO"), call = rlang::caller_env() )sm_refresh( corpus, max_age_days = 30, what = c("citations", "oa_status"), sources = NULL, verbose = TRUE, api_key = Sys.getenv("OPENALEX_API_KEY"), mailto = Sys.getenv("SCIMAPR_MAILTO"), call = rlang::caller_env() )
corpus |
An |
max_age_days |
Numeric. Works last refreshed more than this many days
ago will be re-fetched. Default |
what |
Character vector of data aspects to refresh. One or more of
|
sources |
Optional character vector restricting which provenance
sources to re-query (e.g., |
verbose |
Logical. Print progress messages? |
api_key |
OpenAlex API key, sent only in the authorization header. |
mailto |
Contact address for Crossref requests. |
call |
Caller environment for error reporting. |
A refreshed sm_corpus with updated fields and appended
provenance rows.
Other refresh:
sm_lock(),
sm_staleness()
corpus <- sm_example_corpus() # In practice, this would re-fetch from APIs: # refreshed <- sm_refresh(corpus, max_age_days = 7)corpus <- sm_example_corpus() # In practice, this would re-fetch from APIs: # refreshed <- sm_refresh(corpus, max_age_days = 7)
Record a local file that a corpus was built from, such as a database
snapshot, an institutional export, a publication tracker or an
affiliation dictionary, together with its SHA-256 hash. sm_certificate()
lists the registered files in its local_inputs block, and
sm_verify_certificate() checks that each of them still has the hash
recorded here. sm_corpus_from_tables() registers the files given as its
inputs.
sm_register_input( corpus, path, role = NULL, builder = NULL, call = rlang::caller_env() )sm_register_input( corpus, path, role = NULL, builder = NULL, call = rlang::caller_env() )
corpus |
An |
path |
Path of the file. |
role |
|
builder |
|
call |
Caller environment for error reporting. |
The file is hashed as a stream (digest::digest(path, algo = "sha256", file = TRUE)), so large snapshots are not read into memory. The function
adds one row to corpus$metadata$local_inputs, a tibble with these
columns:
| Column | Type | Content |
path |
character | The path, relative or as given (see below) |
path_abs |
character | The absolute path of the file |
root |
character | The working directory at registration |
basename |
character | The file name |
sha256 |
character | SHA-256 hash of the content |
bytes |
double | Size in bytes |
role |
character | What the file is, such as "dictionary" |
builder |
character | The code that read it |
registered_at |
POSIXct | When it was registered (UTC, whole seconds) |
path is relative to the working directory when the file lies below
it, and as given otherwise. role names the file's part in the corpus,
such as "source database" or "publication tracker", and builder
the code that read it, such as "R/02_corpus.R::build_corpus".
Registering the same file again replaces its row; paths are compared
after they are made absolute, so "data/x.csv" and "./data/x.csv" name
the same file. A corpus without registered files has no such element;
sm_certificate() then writes an empty block.
Local inputs are metadata: they do not enter the corpus hash
(sm_hash_corpus()), and sm_rebuild_from_cert() cannot replay them. A
certificate records path_abs and root as well as path, so that
sm_verify_certificate() finds the files on the same computer from any
folder; these absolute paths show the folder names of that computer.
The corpus with the file recorded in
corpus$metadata$local_inputs.
sm_certificate(), sm_verify_certificate()
Other reproducibility:
sm_certificate(),
sm_cite_corpus(),
sm_diff_corpora(),
sm_hash_corpus(),
sm_provenance(),
sm_snapshot()
corpus <- sm_example_corpus(n_works = 10) tracker <- tempfile(fileext = ".csv") writeLines(c("doi,unit", "10.9999/example.1,Unit A"), tracker) corpus <- sm_register_input(corpus, tracker, role = "publication tracker", builder = "R/01_import.R::read_tracker") corpus$metadata$local_inputscorpus <- sm_example_corpus(n_works = 10) tracker <- tempfile(fileext = ".csv") writeLines(c("doi,unit", "10.9999/example.1,Unit A"), tracker) corpus <- sm_register_input(corpus, tracker, role = "publication tracker", builder = "R/01_import.R::read_tracker") corpus$metadata$local_inputs
One-shot workflow: fetch all works for a researcher by ORCID, enrich, embed, and build a trajectory analysis.
sm_researcher_profile( orcid, sources = c("openalex"), enrich = c("oa_status"), embed = FALSE, trajectory = TRUE, mailto = Sys.getenv("SCIMAPR_MAILTO"), n_max = 200L )sm_researcher_profile( orcid, sources = c("openalex"), enrich = c("oa_status"), embed = FALSE, trajectory = TRUE, mailto = Sys.getenv("SCIMAPR_MAILTO"), n_max = 200L )
orcid |
ORCID identifier. |
sources |
API sources to query. |
enrich |
Enrichment steps to run. |
embed |
Logical; compute embeddings? |
trajectory |
Logical; build trajectory analysis? |
mailto |
Contact address sent to OpenAlex (see
|
n_max |
Maximum number of works to fetch (default |
An sm_corpus with trajectory attached as attribute.
Other trajectory:
is_sm_trajectory(),
sm_author_trajectory(),
sm_plot_collab_turnover(),
sm_plot_topic_pivots(),
sm_plot_trajectory(),
sm_trajectory
## Not run: # profile <- sm_researcher_profile("0000-0001-8006-9742") ## End(Not run)## Not run: # profile <- sm_researcher_profile("0000-0001-8006-9742") ## End(Not run)
Launch a comprehensive 13-tab interactive explorer for bibliometric analysis. The app provides auto/light/dark theming, publication-ready export in multiple formats, and viridis colour palettes throughout.
sm_run_app(corpus = NULL, launch.browser = TRUE, ...)sm_run_app(corpus = NULL, launch.browser = TRUE, ...)
corpus |
An |
launch.browser |
Passed to |
... |
Additional arguments passed to |
Invisible NULL. Launches a Shiny application.
if (interactive()) { sm_run_app() # sm_run_app(my_corpus) }if (interactive()) { sm_run_app() # sm_run_app(my_corpus) }
Persist a corpus to disk as RDS and reload it.
sm_save_corpus(corpus, path, compress = "xz") sm_load_corpus(path)sm_save_corpus(corpus, path, compress = "xz") sm_load_corpus(path)
corpus |
An |
path |
File path for saving. |
compress |
Compression type passed to |
For sm_save_corpus, the path invisibly. For sm_load_corpus,
an sm_corpus object.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_validate(),
validate_sm_corpus()
corpus <- sm_example_corpus(n_works = 10) path <- tempfile(fileext = ".rds") sm_save_corpus(corpus, path) loaded <- sm_load_corpus(path) nrow(loaded$works)corpus <- sm_example_corpus(n_works = 10) path <- tempfile(fileext = ".rds") sm_save_corpus(corpus, path) loaded <- sm_load_corpus(path) nrow(loaded$works)
Wraps viridis with sensible defaults for scimapR plots.
sm_scale_color(option = "viridis", discrete = TRUE, ...) sm_scale_fill(option = "viridis", discrete = TRUE, ...)sm_scale_color(option = "viridis", discrete = TRUE, ...) sm_scale_fill(option = "viridis", discrete = TRUE, ...)
option |
Viridis palette option. One of |
discrete |
Logical; discrete or continuous scale? |
... |
Additional arguments passed to the viridis scale function. |
A ggplot2 scale object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_theme()
sm_screen_against_question() uses a chat provider to screen each
work in a corpus against a structured research question. For each work,
the provider is asked to classify the title/abstract as "include",
"exclude", or "uncertain", with a confidence score and brief reason.
Results are written to the corpus's screening table.
sm_screen_regex() provides a deterministic fallback using
regular-expression matching on titles and abstracts.
sm_screen_summary() returns a count summary of screening decisions
by stage.
sm_screen_against_question( corpus, question, stages = c("title-abstract", "full-text"), llm = NULL, batch_size = 10L, include_uncertain = TRUE, verbose = TRUE, call = rlang::caller_env() )sm_screen_against_question( corpus, question, stages = c("title-abstract", "full-text"), llm = NULL, batch_size = 10L, include_uncertain = TRUE, verbose = TRUE, call = rlang::caller_env() )
corpus |
An |
question |
An |
stages |
Character vector of screening stages to run. One or more
of |
llm |
An ellmer chat provider object (e.g., from
|
batch_size |
Integer. Number of works to send per request. |
include_uncertain |
Logical. If |
verbose |
Logical. Print progress? |
call |
Caller environment for error reporting. |
A modified sm_corpus with updated screening table, including
the provider model on each new row. Failed batches warn and are retried
on the next run, replacing their earlier failed rows.
Other question:
is_sm_question(),
sm_corpus_for_question(),
sm_question(),
sm_screen_regex(),
sm_screen_summary()
corpus <- sm_example_corpus() q <- sm_question( text = "spatial transcriptomics in cancer", framework = "free" ) # Deterministic regex screening (no provider needed): screened <- sm_screen_regex( corpus, include_terms = c("spatial", "transcriptom") ) sm_screen_summary(screened)corpus <- sm_example_corpus() q <- sm_question( text = "spatial transcriptomics in cancer", framework = "free" ) # Deterministic regex screening (no provider needed): screened <- sm_screen_regex( corpus, include_terms = c("spatial", "transcriptom") ) sm_screen_summary(screened)
Create a PRISMA 2020-compatible flow diagram from corpus screening data.
sm_screen_prisma( corpus, decisions = NULL, stages = c("identification", "title-abstract", "full-text", "inclusion"), n_identified = NULL )sm_screen_prisma( corpus, decisions = NULL, stages = c("identification", "title-abstract", "full-text", "inclusion"), n_identified = NULL )
corpus |
An |
decisions |
Optional external decisions tibble. |
stages |
Stage names in flow order. Defaults to identification, title-abstract, full-text and inclusion. Only current works still in the flow count; each stage uses the latest decision per work. |
n_identified |
Whole count before deduplication and other removals,
at least the corpus size. Defaults to |
A list with $counts (tibble) and $plot (ggplot).
Other screening:
sm_export_covidence(),
sm_export_rayyan(),
sm_import_rayyan(),
sm_merge_screening_decisions()
corpus <- sm_example_corpus(with_screening = TRUE) prisma <- sm_screen_prisma(corpus) prisma$countscorpus <- sm_example_corpus(with_screening = TRUE) prisma <- sm_screen_prisma(corpus) prisma$counts
A deterministic screening method that uses regular expression
matching on work titles and abstracts. Works matching any include_terms
(case-insensitive) and none of the exclude_terms are classified as
"include"; works matching an exclude term are "exclude"; works
matching no include term are "exclude".
sm_screen_regex( corpus, include_terms, exclude_terms = NULL, unicode = TRUE, call = rlang::caller_env() )sm_screen_regex( corpus, include_terms, exclude_terms = NULL, unicode = TRUE, call = rlang::caller_env() )
corpus |
An |
include_terms |
Character vector of regex patterns. A work must match at least one to be included. |
exclude_terms |
Character vector of regex patterns. A work matching
any is excluded. Default |
unicode |
Logical (default |
call |
Caller environment for error reporting. |
Terms are Perl-compatible regular expressions (PCRE), matched as written
and case-insensitively against the title and abstract of each work. In
Unicode mode (the default) each term is prefixed with the PCRE item
(*UCP) unless its leading items already include it, so \b finds the
edge of a word that starts with an umlaut and \w consumes one. A term
can therefore also stop matching: one that used \W, \S, \D or \B
to stand for a non-ASCII letter no longer matches it, and \b no longer
finds an edge between an ASCII letter and a non-ASCII letter next to it,
as inside a German compound word. Write [A-Za-z0-9_] instead of \w
for an ASCII-only class.
A modified sm_corpus with updated screening table.
Other question:
is_sm_question(),
sm_corpus_for_question(),
sm_question(),
sm_screen_against_question(),
sm_screen_summary()
corpus <- sm_example_corpus(n_works = 20, n_authors = 5) screened <- sm_screen_regex(corpus, include_terms = c("spatial", "transcriptom"), exclude_terms = "\\bmouse\\b") sm_screen_summary(screened)corpus <- sm_example_corpus(n_works = 20, n_authors = 5) screened <- sm_screen_regex(corpus, include_terms = c("spatial", "transcriptom"), exclude_terms = "\\bmouse\\b") sm_screen_summary(screened)
Returns a summary tibble of screening decisions by stage, showing the count of included, excluded, and uncertain works at each stage.
sm_screen_summary(corpus, call = rlang::caller_env())sm_screen_summary(corpus, call = rlang::caller_env())
corpus |
An |
call |
Caller environment for error reporting. |
A tibble with columns stage, decision, n, and pct.
Other question:
is_sm_question(),
sm_corpus_for_question(),
sm_question(),
sm_screen_against_question(),
sm_screen_regex()
Identifies self-citations from the references already in the corpus — no
per-citation API calls. A citation from a citing work to a cited work is a
self-citation at the chosen level when the citing and cited works share an
author (or institution). This is the quota-light "reference overlap" method.
sm_self_citation( corpus, level = c("author", "institution"), method = c("reference_overlap"), call = rlang::caller_env() ) ## S3 method for class 'sm_self_citation' print(x, ...) ## S3 method for class 'sm_self_citation' summary(object, ...)sm_self_citation( corpus, level = c("author", "institution"), method = c("reference_overlap"), call = rlang::caller_env() ) ## S3 method for class 'sm_self_citation' print(x, ...) ## S3 method for class 'sm_self_citation' summary(object, ...)
corpus |
An |
level |
|
method |
Self-citation definition; currently only |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
object |
An |
An sm_self_citation S3 object (a list) with components:
Tibble: entity_id, n_citations_received (internal
citations to the entity's works), n_self_citations,
self_citation_share.
Tibble: cited_work_id, n_citations_received,
n_self_citations, self_citation_share (per cited work).
Tibble: citing_work_id, cited_work_id, and
shared_author_id (author level) or shared_institution_id
(institution level) — the evidence behind each self-citation.
Type-stable: when references is absent/empty the components are 0-row
tibbles with the documented columns, returned after a cli::cli_warn
(the function never spins).
print returns x invisibly.
summary returns the by_entity tibble.
sm_metric_h_index(), sm_link_references()
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus(n_works = 40, seed = 1) sc <- sm_self_citation(corpus, level = "author") head(sc$by_entity)corpus <- sm_example_corpus(n_works = 40, seed = 1) sc <- sm_self_citation(corpus, level = "author") head(sc$by_entity)
sm_snapshot() serializes an sm_corpus to disk as a compressed RDS file,
embedding the corpus hash as part of the filename for traceability.
sm_snapshot_load() reads a previously saved snapshot and validates that
the loaded object is a well-formed sm_corpus.
The snapshot stores the corpus hash (sm_hash_corpus()) in
metadata$corpus_hash and the version of the hash algorithm in
metadata$hash_version. sm_snapshot_load() recomputes the hash with
that version, or with version 1 for snapshots written before scimapR
0.5.0, and warns when it differs.
sm_snapshot( corpus, path = NULL, compress = c("xz", "gzip", "bzip2", "none"), call = rlang::caller_env() ) sm_snapshot_load(path, call = rlang::caller_env())sm_snapshot( corpus, path = NULL, compress = c("xz", "gzip", "bzip2", "none"), call = rlang::caller_env() ) sm_snapshot_load(path, call = rlang::caller_env())
corpus |
An |
path |
Character. File path for the snapshot. If |
compress |
Character. Compression method passed to |
call |
Caller environment for error reporting. |
For sm_snapshot(): the file path (invisibly).
For sm_snapshot_load(): an sm_corpus object.
Other reproducibility:
sm_certificate(),
sm_cite_corpus(),
sm_diff_corpora(),
sm_hash_corpus(),
sm_provenance(),
sm_register_input()
corpus <- sm_example_corpus() path <- tempfile(fileext = ".rds") sm_snapshot(corpus, path = path) loaded <- sm_snapshot_load(path) identical(nrow(corpus$works), nrow(loaded$works))corpus <- sm_example_corpus() path <- tempfile(fileext = ".rds") sm_snapshot(corpus, path = path) loaded <- sm_snapshot_load(path) identical(nrow(corpus$works), nrow(loaded$works))
Inspects each work in a corpus and reports how long ago it was last
refreshed. Works with last_refreshed older than 30 days are flagged
as stale. This is useful before calling sm_refresh() to preview which
works would be updated.
sm_staleness(corpus, threshold_days = 30, call = rlang::caller_env())sm_staleness(corpus, threshold_days = 30, call = rlang::caller_env())
corpus |
An |
threshold_days |
Numeric. Number of days after which a work is considered stale. Defaults to 30. |
call |
Caller environment for error reporting. |
A tibble with columns:
Work identifier.
POSIXct timestamp of last refresh.
Number of days since last refresh.
Logical; TRUE if age_days > threshold_days.
Other refresh:
sm_lock(),
sm_refresh()
corpus <- sm_example_corpus() sm_staleness(corpus)corpus <- sm_example_corpus() sm_staleness(corpus)
Computes aggregate summary statistics for authors in the corpus.
sm_summary_authors(corpus, call = rlang::caller_env())sm_summary_authors(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object. |
call |
Caller environment for error reporting. |
A tibble with one row containing:
n_authors, n_with_orcid, pct_orcid, mean_works_per_author,
median_works_per_author, max_works_per_author,
mean_authors_per_work, single_author_pct. The works of an author
and the authors of a work are counted as distinct pairs of work_id
and author_id, so an author with several address rows in a work
counts once, as in sm_metric_collab_index().
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_period(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() sm_summary_authors(corpus)corpus <- sm_example_corpus() sm_summary_authors(corpus)
Computes per-year summary statistics for works in the corpus.
sm_summary_period(corpus, call = rlang::caller_env())sm_summary_period(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object. |
call |
Caller environment for error reporting. |
A tibble with one row per year and columns:
year, n_works, mean_citations, median_citations,
total_citations, n_oa, pct_oa, n_authors (unique authors
active in that year), mean_authors_per_work (distinct authors per
work, so an author with several address rows counts once).
Open-access percentages use sm_oa_statuses() and all works in each
year as the denominator, agreeing with sm_audit_oa() to one decimal.
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_sources(),
sm_summary_works()
corpus <- sm_example_corpus() sm_summary_period(corpus)corpus <- sm_example_corpus() sm_summary_period(corpus)
Computes aggregate summary statistics for publication sources in the corpus.
sm_summary_sources(corpus, call = rlang::caller_env())sm_summary_sources(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object. |
call |
Caller environment for error reporting. |
A tibble with one row containing:
n_sources, n_oa_sources, pct_oa_sources, n_publishers,
top_source (source with most works), top_source_n.
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_works()
corpus <- sm_example_corpus() sm_summary_sources(corpus)corpus <- sm_example_corpus() sm_summary_sources(corpus)
Computes aggregate summary statistics for all works in the corpus.
sm_summary_works(corpus, call = rlang::caller_env())sm_summary_works(corpus, call = rlang::caller_env())
corpus |
An sm_corpus object. |
call |
Caller environment for error reporting. |
A tibble with one row containing summary statistics:
n_works, year_min, year_max, year_span, mean_citations,
median_citations, total_citations, n_types (unique document types),
n_sources (unique journals/sources), n_oa (open access works),
pct_oa, n_retracted, n_languages.
Open-access percentages use sm_oa_statuses() and all works as the
denominator, agreeing with sm_audit_oa() to one decimal place.
Other metrics:
sm_metric_collab_index(),
sm_metric_disruption(),
sm_metric_fnci(),
sm_metric_g_index(),
sm_metric_h_index(),
sm_metric_m_index(),
sm_metric_novelty(),
sm_metric_rcr(),
sm_self_citation(),
sm_summary_authors(),
sm_summary_period(),
sm_summary_sources()
corpus <- sm_example_corpus() sm_summary_works(corpus)corpus <- sm_example_corpus() sm_summary_works(corpus)
Builds the unit-by-time outcome panel for a treated institution against a
donor pool and estimates a synthetic control using the optional
tidysynth package. The dependency is kept in Suggests; when it is not
installed an informative error explains how to install it.
sm_synth( corpus, treated, donors, intervention_year, outcome = c("count", "share_q1", "cnci", "leadership"), lag = NULL, institution_aware = FALSE, lead_role = c("corresponding", "first", "last", "first_or_last", "any_lead"), min_cell = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_synth' print(x, ...)sm_synth( corpus, treated, donors, intervention_year, outcome = c("count", "share_q1", "cnci", "leadership"), lag = NULL, institution_aware = FALSE, lead_role = c("corresponding", "first", "last", "first_or_last", "any_lead"), min_cell = NULL, call = rlang::caller_env() ) ## S3 method for class 'sm_synth' print(x, ...)
corpus |
An |
treated |
A single institution label (the treated unit). |
donors |
Character vector of donor-pool institution labels. |
intervention_year |
Year the intervention takes effect: one whole
number such as |
outcome |
One of |
lag |
Citation-maturity lag in years (default |
institution_aware |
For |
lead_role |
For |
min_cell |
For |
call |
Caller environment for error reporting. |
x |
An |
... |
Ignored. |
Each unit's yearly series is built as in sm_its(), and every unit must
contain works. For "count" a unit's years without works count 0, over
every year from the first to the last publication year of the corpus, so
no year is dropped. For the other outcomes the panel stays balanced: a
year in which any unit has no works or no usable value is dropped for
every unit, with a warning, and listed in dropped_years. For "cnci"
the citation-immature years are then excluded for every unit, by the
same rule as in sm_its(), and listed in provisional_years; up to
scimapR 0.4.0 sm_synth() kept them. At least 4 panel years must
remain, 2 or more before intervention_year and 1 or more from it on;
otherwise sm_synth() aborts. For "leadership" the shares leave out
the works whose lead is unknown, as in sm_its().
An sm_synth S3 object wrapping the initialised tidysynth
object (synth), the long panel (panel: unit, year, value),
dropped_years (tibble: unit, year; the unit-years without a usable
value, whose years were dropped for every unit), provisional_years
(integer; the citation-immature years excluded for "cnci"),
leadership (the leadership definition, as in sm_its(), with
institutions the unit labels when institution_aware; NULL for
the other outcomes), impact_source (where the impact values of
"cnci" come from, as in sm_its(); NA for the other outcomes),
and metadata.
print returns x invisibly.
Other causal:
sm_did(),
sm_its()
corpus <- sm_example_corpus(n_works = 300, seed = 1) corpus$authorships$institution_name <- sample( c("A", "B", "C", "D"), nrow(corpus$authorships), replace = TRUE) synth <- sm_synth(corpus, treated = "A", donors = c("B", "C", "D"), intervention_year = 2020, outcome = "count")corpus <- sm_example_corpus(n_works = 300, seed = 1) corpus$authorships$institution_name <- sample( c("A", "B", "C", "D"), nrow(corpus$authorships), replace = TRUE) synth <- sm_synth(corpus, treated = "A", donors = c("B", "C", "D"), intervention_year = 2020, outcome = "count")
A restrained, publication-ready ggplot2 theme with viridis defaults.
sm_theme(base_size = 11, base_family = "", dark = FALSE, ...)sm_theme(base_size = 11, base_family = "", dark = FALSE, ...)
base_size |
Base font size in points. |
base_family |
Base font family. |
dark |
Logical; use dark mode? |
... |
Additional arguments passed to |
A ggplot2::theme object.
Other plots:
autoplot.sm_corpus(),
sm_palette_qualitative(),
sm_plot_bradford(),
sm_plot_citation_network(),
sm_plot_collab(),
sm_plot_equity_dashboard(),
sm_plot_evolution(),
sm_plot_heaps(),
sm_plot_landscape(),
sm_plot_lotka(),
sm_plot_production(),
sm_plot_thematic_map(),
sm_plot_top(),
sm_scale_color()
library(ggplot2) ggplot(mtcars, aes(wt, mpg)) + geom_point() + sm_theme()library(ggplot2) ggplot(mtcars, aes(wt, mpg)) + geom_point() + sm_theme()
Converts an sm_corpus to the data frame format used by
bibliometrix::biblioAnalysis() and the other functions of bibliometrix:
one row per work and one column per Web of Science field tag. This
enables using bibliometrix's analysis functions on corpora built with
scimapR.
sm_to_bibliometrix( corpus, keep_extra = TRUE, db = c("auto", "ISI", "scimapR"), ... )sm_to_bibliometrix( corpus, keep_extra = TRUE, db = c("auto", "ISI", "scimapR"), ... )
corpus |
An |
keep_extra |
Logical. If |
db |
Value of the |
... |
Additional arguments (currently unused). |
AU: authors as SURNAME INITIALS, AF: full names as
SURNAME, GIVEN NAMES; one entry per author of the work, in
position order and separated by ;, from authors$display_name
through authorships. An author with several address rows appears
once; authors without a name are left out.
TI, AB: works$title, works$abstract; TI_raw and AB_raw hold
them in their stored case, as bibliometrix::convert2df() does.
SO, J9, JI: sources$display_name, and the optional columns
sources$abbreviation and sources$iso_abbreviation.
SN, EI: sources$issn_l (else the first value of sources$issn)
and the next distinct value of sources$issn.
C1: one segment per distinct address of the work,
[AUTHOR A; AUTHOR B] ADDRESS, with the authors (AF form) in
position order, joined by ; ; from authorships$raw_affiliation.
RP: one segment per corresponding author
(authorships$is_corresponding), SURNAME, INITIALS (CORRESPONDING AUTHOR), ADDRESS with the author's first address, joined by ; .
EM: the e-mail addresses of the optional column
authorships$email.
CR: references$cited_raw in ref_index order, separated by ;;
NR: the number of items in CR.
DE: author keywords, see below; DE_raw holds them in their stored
case. ID: the concepts of the indexer_keyword family of
sm_concept_vocabularies() (such as Keywords Plus or MeSH). WC: the
vocabulary wos-category (Web of Science categories); SC: the
vocabulary wos-subject-category (research areas). Terms are joined
by ; .
DT, LA, PY, TC, DI, PM: works$type, language, year,
cited_by_count, doi and pmid.
UT: the Web of Science accession number,
provenance$source_id_external of the work's first provenance row
with source == "wos", else a work_id that starts with WOS:.
DB: see below. SR: the short reference FIRST AUTHOR, YEAR, J9
(with SO where J9 is missing), made unique by appending -a,
-b, ... to repeats; SR_FULL keeps it without the suffixes.
With keep_extra = TRUE, country_code holds the country codes of
authorships$country_code, one per authorship row (author and
address), and SO_ISSN holds sources$issn_l. The export has no
AU_CO column, as data from bibliometrix::convert2df() has none:
bibliometrix::metaTagExtraction(M, "AU_CO") adds the country names
that bibliometrix reads from C1 and uses for its country analyses.
Like bibliometrix::convert2df() for Web of Science files, the export
upper-cases AU, AF, TI, SO, J9, JI, AB, C1, RP, EM,
CR (including the DOIs in it), DE, ID, WC, SC, DT and LA.
DI, UT, SN, EI and PM are written as stored, so DI keeps the
lower-case DOIs of the scimapR readers. Upper-casing uses toupper(),
whose result for non-ASCII letters depends on the locale.
Names: a display_name with a comma is read as Surname, Given names.
Otherwise an entry of authors$display_name_alternatives in that form
that agrees with the name gives the surname, so a surname of several
words such as "Garcia Lopez" stays whole; sm_read_wos() keeps the
names of the file there. Else initials at the end of the name (single
letters, or capitals with periods, as in "Chen W." from
sm_read_scopus()) follow the surname, and otherwise the last word is
the surname, together with the lower-case particles before it ("Anna
van der Berg" gives VAN DER BERG A). Particles never become initials.
Without such a spelling, a surname of several words without particles
("Maria Garcia Lopez") keeps only its last word, and a name such as
"SMITH JA" is read as given names followed by the surname. The given
names of AF are the longest spelling at hand, the initials of AU
the shortest. Corpora read by sm_read_wos() store the AU form of a
name (such as "J Smith"), so their AF holds initials.
CR items are written in the Web of Science form SURNAME INITIALS, YEAR, SOURCE, ...: an item Surname, IN, YEAR, ... (with a comma after
the surname, as some exports write it) becomes SURNAME IN, YEAR, ....
Other items, such as titles or OpenAlex IDs, are only upper-cased.
Because ; separates the items of AU, AF, C1, RP, CR, DE,
ID, WC and SC, a ; inside an item becomes ,.
DE holds the author keywords of each work: the concepts of the
author_keyword family of sm_concept_vocabularies() (author-keywords,
manual-tags). A work without author keywords gets its OpenAlex
concepts (openalex) instead, so corpora built from OpenAlex keep their
DE; other vocabularies (Keywords Plus, MeSH, categories, arXiv and
bioRxiv subjects) never enter DE. A work with neither has DE = NA.
DB: with db = "auto", a corpus whose works all come from Web of
Science (each has a provenance row with source == "wos", or a
work_id that starts with WOS:) gets "ISI", the value
bibliometrix::convert2df() writes for Web of Science files, so that
bibliometrix::histNetwork() and bibliometrix::rpys() accept it.
Other corpora get "scimapR", because bibliometrix reads "ISI" data
as Web of Science records; db = "ISI" declares a corpus whose CR
items are in Web of Science form anyway.
RP follows authorships$is_corresponding: it names the authors
flagged TRUE and is NA for a work without one. sm_read_wos() takes
the corresponding authors from the RP field of the file,
sm_read_openalex_json() and sm_fetch_openalex() from the OpenAlex
records, sm_read_dimensions() from the Corresponding Authors column,
sm_fetch_biorxiv() from the bioRxiv API and as_sm_corpus() from
RP. The other readers and fetchers record the corresponding author as
unknown (NA), so their corpora export RP = NA. Corpora built from
tables keep the flags of their tables.
as_sm_corpus() reads the result back: the round trip keeps the works,
their DOIs and years, the authors of each work, their addresses and the
corresponding authors.
A data frame with class c("bibliometrixDB", "data.frame"), one
row per work in the order of corpus$works, with SR as row names.
Every tag column is present, NA where the corpus holds no data.
Aria, M. & Cuccurullo, C. (2017). bibliometrix: An R-tool for comprehensive science mapping analysis. Journal of Informetrics, 11(4), 959-975. doi:10.1016/j.joi.2017.08.007
corpus <- sm_example_corpus(n_works = 10, n_authors = 5, with_embeddings = FALSE) M <- sm_to_bibliometrix(corpus, keep_extra = FALSE) class(M) M[1:3, c("AU", "PY", "SO", "DB")] # a corpus from Web of Science records fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) M <- sm_to_bibliometrix(fix) unique(M$DB) M$C1[1] if (requireNamespace("bibliometrix", quietly = TRUE)) { results <- bibliometrix::biblioAnalysis(M, sep = ";") }corpus <- sm_example_corpus(n_works = 10, n_authors = 5, with_embeddings = FALSE) M <- sm_to_bibliometrix(corpus, keep_extra = FALSE) class(M) M[1:3, c("AU", "PY", "SO", "DB")] # a corpus from Web of Science records fix <- readRDS(sm_example_files("example_realuse_corpus.rds")) M <- sm_to_bibliometrix(fix) unique(M$DB) M$C1[1] if (requireNamespace("bibliometrix", quietly = TRUE)) { results <- bibliometrix::biblioAnalysis(M, sep = ";") }
An sm_trajectory object encodes the career trajectory of a single author
across a corpus, including career stages, topic pivots, collaborator
turnover, emerging collaborators, h-index curve, novelty curve, and
citation acceleration.
The object is created by sm_author_trajectory() and should not normally
be constructed directly.
Other trajectory:
is_sm_trajectory(),
sm_author_trajectory(),
sm_plot_collab_turnover(),
sm_plot_topic_pivots(),
sm_plot_trajectory(),
sm_researcher_profile()
Check referential integrity across all corpus tables and report issues. Checks work, author, source and institution links, duplicate lookup IDs, and duplicate or orphan embedding row names. A locked corpus with a stored hash is also checked for changed content.
sm_validate(corpus, call = rlang::caller_env())sm_validate(corpus, call = rlang::caller_env())
corpus |
An |
call |
Caller environment for error reporting. |
A tibble with table, issue and n_affected, or a 0-row
tibble with the same columns if clean. Missing links count distinct
unresolved IDs; duplicates count surplus rows.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
validate_sm_corpus()
corpus <- sm_example_corpus() issues <- sm_validate(corpus) nrow(issues)corpus <- sm_example_corpus() issues <- sm_validate(corpus) nrow(issues)
Check that an sm_corpus has valid structure and consistent IDs.
validate_sm_corpus(x, call = rlang::caller_env())validate_sm_corpus(x, call = rlang::caller_env())
x |
An |
call |
Caller environment for error reporting. |
x invisibly if valid; throws an error otherwise.
Other corpus:
as_sm_corpus(),
is_sm_corpus(),
sm_bind_corpora(),
sm_build_corpus(),
sm_concept_vocabularies(),
sm_corpus(),
sm_corpus_from_tables(),
sm_dedupe(),
sm_link_references(),
sm_materialise(),
sm_oa_statuses(),
sm_save_corpus(),
sm_validate()