scholid is a
lightweight, dependency-free (base R only) toolkit for working with
scholarly and academic identifiers. It provides small, well-tested
helpers to detect, normalize, compare, format, classify, extract, and
locate common identifier strings.
This vignette introduces the interface and typical workflows for mixed, messy identifier data.
scholid exposes a small set of user-facing functions
that operate consistently across identifier types:
scholid_types() lists supported identifier types.is_scholid(x, type) checks whether values match the
identifier type.normalize_scholid(x, type) returns canonical identifier
strings.scholid_key(x, type) returns keys for comparing and
deduplicating identifiers.format_scholid(x, type, as) writes identifiers as
resolver URLs, CURIEs, or arXiv DOIs.extract_scholid(text, type) extracts identifiers from
free text.locate_scholid(text, types) finds identifiers of
several types in free text, with their positions.classify_scholid(x) guesses the identifier type per
element.detect_scholid_type(x) detects identifier types from
canonical or wrapped input values (e.g., URLs or labels).These generic helpers dispatch internally to type-specific
implementations such as is_doi(),
normalize_orcid(), and extract_isbn().
## [1] "doi" "arxiv" "bibcode" "openalex" "swhid"
## [6] "ark" "isni" "orcid" "ror" "rrid"
## [11] "uniprot" "refseq" "sra" "geo" "bioproject"
## [16] "assembly" "isbn" "issn" "pmcid" "pmid"
For per-type formats, validation rules, and classification order, see
the How Scholarly Identifiers Are Defined vignette
(vignette("scholid_definitions", package = "scholid")),
also linked from the package site as About identifiers.
is_scholid()is_scholid() checks whether each value is a valid
identifier of a specific type. It expects canonical (or near-canonical)
input; wrapped forms such as URLs should be normalized first. For types
that define a checksum, both is_scholid() and
normalize_scholid() verify it; the definitions vignette
says which types do. It is vectorized and preserves missing values.
## [1] TRUE FALSE NA
normalize_scholid()Normalization removes common wrappers and enforces a canonical representation. This is particularly useful when identifiers are stored as URLs or prefixed labels.
x <- c(
"https://doi.org/10.1000/182.",
"doi:10.1000/182",
" 10.1000/182 "
)
scholid::normalize_scholid(
x = x,
type = "doi"
)## [1] "10.1000/182" "10.1000/182" "10.1000/182"
For ORCID iDs, normalization removes URL prefixes and enforces hyphenated grouping.
x <- c(
"https://orcid.org/0000-0002-1825-0097",
"0000000218250097"
)
scholid::normalize_scholid(
x = x,
type = "orcid"
)## [1] "0000-0002-1825-0097" "0000-0002-1825-0097"
Normalization is designed to be predictable: - NA input
stays NA. - Invalid inputs typically become
NA_character_.
scholid_key()Normalization keeps apart values that name the same thing in
different ways: an ISBN-10 and its ISBN-13, versions of one arXiv
preprint, or DOIs that differ only in the case of ASCII letters.
scholid_key() normalizes values and gives them keys that
match, for use with duplicated(), unique(),
match(), or merge().
x <- c(
"0306406152",
"ISBN 978-0-306-40615-7",
"0-8044-2957-X",
"9780804429573"
)
keys <- scholid::scholid_key(
x = x,
type = "isbn"
)
keys## [1] "9780306406157" "9780306406157" "9780804429573" "9780804429573"
## [1] "0306406152" "0-8044-2957-X"
## [1] "2101.00001" "2101.00001"
## [1] "10.1000/ABC" "10.1000/ABC"
Keys are for comparing only. Some don’t pass
is_scholid(), such as the RefSeq key NM_000546
without its version, so keep the normalized values for storing and
showing identifiers. The key rule of each type is in the definitions
vignette.
format_scholid()format_scholid() writes identifiers as resolver URLs
(the default), as CURIEs, or, for arXiv preprints, as the DOIs that
arXiv registers. It normalizes its input first, so wrapped values work,
and values that don’t normalize give NA. For links in a
report:
x <- c(
"doi:10.7717/peerj.4375",
"https://doi.org/10.1000/182",
"not a doi"
)
urls <- scholid::format_scholid(
x = x,
type = "doi"
)
urls## [1] "https://doi.org/10.7717/peerj.4375" "https://doi.org/10.1000/182"
## [3] NA
normalize_scholid() reads the output back:
## [1] "10.7717/peerj.4375" "10.1000/182" NA
CURIEs use the type’s Bioregistry prefix:
scholid::format_scholid(
x = c("PMID: 12345678", "https://pubmed.ncbi.nlm.nih.gov/7654321/"),
type = "pmid",
as = "curie"
)## [1] "pubmed:12345678" "pubmed:7654321"
The arXiv DOI names the preprint, so it leaves out the version:
scholid::format_scholid(
x = c("arXiv:1706.03762v7", "hep-th/9901001"),
type = "arxiv",
as = "doi"
)## [1] "10.48550/arXiv.1706.03762" "10.48550/arXiv.hep-th/9901001"
Not every type has every form. ISBNs, for example, have no resolver URL, and asking for one is an error that names the forms the type has:
## Error:
## ! Type "isbn" has no "url" form. Available forms for "isbn": "curie".
The forms of each type are listed under “Resolver URLs and CURIEs” in the definitions vignette.
extract_scholid()Extraction is for harvesting identifiers from unstructured text. The result is a list with one element per input element. Each element is a character vector of matches (possibly empty).
txt <- c(
"See https://doi.org/10.1000/182 and doi:10.5555/12345678.",
"No identifier here.",
NA
)
scholid::extract_scholid(
text = txt,
type = "doi"
)## [[1]]
## [1] "10.1000/182" "10.5555/12345678"
##
## [[2]]
## character(0)
##
## [[3]]
## character(0)
The list return type is intentional: a single text string can contain multiple identifiers.
locate_scholid()locate_scholid() looks for identifiers of all supported
types at once, or of the types given in types, and reports
where each one is. The result is a data frame with one row per
identifier: the element of text it was found in, its type,
the token extract_scholid() returns for it, the identifier
as written, and its start and end positions.
txt <- c(
"See doi:10.1000/182 and ORCID 0000-0002-1825-0097.",
NA,
"ISBN 978 0 306 40615 7 (PMID 12345678)"
)
hits <- scholid::locate_scholid(text = txt)
hits## element type id match start end
## 1 1 doi 10.1000/182 10.1000/182 9 19
## 2 1 orcid 0000-0002-1825-0097 0000-0002-1825-0097 31 49
## 3 3 isbn 978 0 306 40615 7 978 0 306 40615 7 6 22
## 4 3 pmid 12345678 12345678 30 37
## [1] "10.1000/182" "0000-0002-1825-0097" "978 0 306 40615 7"
## [4] "12345678"
The digits 40615 inside the ISBN also look like a PMID.
Where the spans of two hits overlap, locate_scholid() keeps
the longer one, and for equal lengths the type that comes first in
scholid_types(). Only the types asked for take part:
## element type id match start end
## 1 1 pmid 40615 40615 16 20
## 2 1 pmid 12345678 12345678 30 37
classify_scholid()classify_scholid() returns the best-guess identifier
type per element for mixed identifier columns. Classification is based
on the set of available is_<type>() checks and the
precedence order defined by scholid_types().
x <- c(
"10.1000/182",
"0000-0002-1825-0097",
"PMC12345",
"2101.00001v2",
"not an id",
NA
)
scholid::classify_scholid(x = x)## [1] "doi" "orcid" "pmcid" "arxiv" NA NA
Many identifiers appear wrapped (URLs, prefixes, trailing punctuation). Classification is strict and expects canonical strings. A common pattern is:
txt <- "Read https://doi.org/10.1000/182 (and ORCID 0000-0002-1825-0097)."
dois <- scholid::extract_scholid(txt, "doi")[[1]]
orcids <- scholid::extract_scholid(txt, "orcid")[[1]]
dois_n <- scholid::normalize_scholid(dois, "doi")
orcids_n <- scholid::normalize_scholid(orcids, "orcid")
scholid::classify_scholid(c(dois_n, orcids_n))## [1] "doi" "orcid"
## [1] TRUE
## [1] TRUE
detect_scholid_type()detect_scholid_type() performs best-effort type
detection for mixed, messy identifier input. In contrast to
classify_scholid(), detection also recognizes common
wrapped forms such as URLs and prefixed labels (e.g., doi:,
https://orcid.org/, arXiv:,
PMID:).
Detection is useful when working with raw data where identifiers may not yet be normalized.
For example, wrapped identifiers are not classified strictly:
x <- c(
"https://doi.org/10.1000/182",
"ORCID: 0000-0002-1825-0097",
"arXiv:2101.00001",
"PMID: 12345",
"not an id"
)
scholid::classify_scholid(x)## [1] NA NA NA NA NA
However, they can be detected directly:
## [1] "doi" "orcid" "arxiv" "pmid" NA
Whitespace and minor formatting irregularities are handled conservatively:
## [1] "orcid" "doi" "issn"
detect_scholid_type() does not modify values. Once the
identifier type is known, use normalize_scholid() to
convert wrapped input to canonical form and is_scholid() to
validate already-canonical values. Both apply checksum verification
where applicable.
A typical workflow for messy data is:
This separation keeps detection permissive, normalization focused on canonicalization of wrapped input, and validation available for already-canonical strings.
scholid is intentionally small and conservative:
is_*(),
normalize_*(), key_*(), and
extract_*() helpers.