| Title: | 'BERTopic'-Style Topic Modeling Without 'Python' |
|---|---|
| Description: | Implements the 'BERTopic' topic modeling pipeline directly in R: transformer-based sentence embedding, Uniform Manifold Approximation and Projection dimensionality reduction, Hierarchical Density-Based Spatial Clustering of Applications with Noise clustering, and class-based term frequency-inverse document frequency topic extraction - all without any dependency on 'Python', 'conda', or 'reticulate'. Every stage runs in R through 'torch', 'safetensors', 'tok', 'uwot', and 'dbscan'. The package mirrors the accessor API of the original 'Python' package, adds integrated quality metrics and hyperparameter search tools, and introduces part-of-speech filtered and C-value-ranked representation models. |
| Authors: | J.P.G. van der Pol [aut, cre] |
| Maintainer: | J.P.G. van der Pol <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.10 |
| Built: | 2026-10-08 10:45:19 UTC |
| Source: | https://github.com/jpvdp/rhobots |
Wraps stats::hclust + stats::cutree. Like k-means, every
document is assigned to a cluster (no noise label).
agglomerative_clustering(k, linkage = "ward.D2")agglomerative_clustering(k, linkage = "ward.D2")
k |
Number of clusters to cut the dendrogram into. |
linkage |
Linkage method passed to |
An agglomerative_clustering model object.
m <- agglomerative_clustering(k = 5L)m <- agglomerative_clustering(k = 5L)
After fitting a topic model, the top terms for each topic are selected purely by c-TF-IDF score, which can produce redundant terms (e.g. model, models, modeling). MMR re-ranks the candidate terms by jointly maximising their relevance to the topic and their diversity from already-selected terms.
apply_mmr( fit, encoder, diversity = 0.1, top_n = NULL, top_n_candidates = NULL, verbose = TRUE )apply_mmr( fit, encoder, diversity = 0.1, top_n = NULL, top_n_candidates = NULL, verbose = TRUE )
fit |
A |
encoder |
An encoder from |
diversity |
Controls the relevance-diversity trade-off. |
top_n |
Number of terms to keep per topic after MMR. Defaults to
|
top_n_candidates |
Size of the candidate pool drawn from c-TF-IDF
before MMR selection. Must be |
verbose |
Print progress messages (default |
The algorithm mirrors Python BERTopic's
MaximalMarginalRelevance representation model:
For each topic, take the top top_n_candidates terms from
c-TF-IDF as the candidate pool.
Embed every unique candidate term and a topic reference
string (the candidates joined into one string) using encoder.
Greedily select top_n terms by
where is the set of already-selected terms and
= diversity.
Original c-TF-IDF scores are preserved for the selected terms; only the selection and ranking change.
Note: MMR operates on the existing topic_terms in the fit
object. If you subsequently call reduce_topics,
merge_topics, or reduce_outliers, topic terms
are recomputed from c-TF-IDF and MMR needs to be re-applied.
An updated bertopic_fit with topic_terms and
topic_labels replaced by the MMR-selected representation.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- apply_mmr(fit, encoder = enc, diversity = 0.3) get_topic(fit, 0L) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- apply_mmr(fit, encoder = enc, diversity = 0.3) get_topic(fit, 0L) ## End(Not run)
Tokenizes each document (lowercase, split on non-alphanumeric), removes stopwords, optionally generates n-grams, then applies document-frequency filtering. Returns a sparse matrix suitable for the c-TF-IDF step.
build_dtm( docs, min_df = 2, max_df_frac = 0.95, stopwords = character(), ngram_range = c(1L, 1L) )build_dtm( docs, min_df = 2, max_df_frac = 0.95, stopwords = character(), ngram_range = c(1L, 1L) )
docs |
Character vector of documents. |
min_df |
Minimum document frequency: terms appearing in fewer than this many documents are dropped. |
max_df_frac |
Maximum document-frequency fraction: terms appearing in more than this fraction of documents are dropped. |
stopwords |
Character vector of tokens to remove before n-gram construction. |
ngram_range |
Integer vector of length 2, e.g. |
A sparse dgCMatrix of dimensions length(docs) x
vocab size, with colnames set to the vocabulary.
docs <- c("the cat sat on the mat", "the dog chased the cat", "a cat and a dog", "the mat is on the floor") build_dtm(docs, min_df = 1L)docs <- c("the cat sat on the mat", "the dog chased the cat", "a cat and a dog", "the mat is on the floor") build_dtm(docs, min_df = 1L)
Treats each cluster as one big document and ranks terms by class TF multiplied by a class-based inverse document frequency. This is the BERTopic-flavoured TF-IDF that produces interpretable topic descriptors.
c_tf_idf(dtm, cluster_ids, top_n = 10, reduce_frequent_words = FALSE)c_tf_idf(dtm, cluster_ids, top_n = 10, reduce_frequent_words = FALSE)
dtm |
A sparse document-term matrix from |
cluster_ids |
Integer vector of cluster assignments, one per row of
|
top_n |
Number of top terms to return per cluster. |
reduce_frequent_words |
If |
A data frame with columns topic, rank, term, score.
docs <- c("the cat sat on mat", "a dog chased cat", "machine learning models", "deep learning neural nets") dtm <- build_dtm(docs, min_df = 1L) c_tf_idf(dtm, cluster_ids = c(0L, 0L, 1L, 1L), top_n = 3L)docs <- c("the cat sat on mat", "a dog chased cat", "machine learning models", "deep learning neural nets") dtm <- build_dtm(docs, min_df = 1L) c_tf_idf(dtm, cluster_ids = c(0L, 0L, 1L, 1L), top_n = 3L)
S3 generic that dispatches on the classifier class returned by
load_hf_classifier().
classify_texts(classifier, texts, ...) ## S3 method for class 'hf_classifier' classify_texts( classifier, texts, batch_size = 32L, max_length = 512L, verbose = FALSE, ... ) ## Default S3 method: classify_texts(classifier, texts, ...)classify_texts(classifier, texts, ...) ## S3 method for class 'hf_classifier' classify_texts( classifier, texts, batch_size = 32L, max_length = 512L, verbose = FALSE, ... ) ## Default S3 method: classify_texts(classifier, texts, ...)
classifier |
An |
texts |
A character vector of strings. |
... |
Unused (for future extension). |
batch_size |
Number of texts per forward pass. Default 32. |
max_length |
Maximum token sequence length (including special tokens). Sequences are truncated to this value. Default 512. |
verbose |
Print batch progress. Default FALSE. |
Sequence classification (sentiment, topic, ...): returns a data.frame
with columns text, label, score, plus one probability column per
label. For problem_type = "regression" the label columns contain raw
numeric scores. For problem_type = "multi_label_classification" each
label column contains a sigmoid probability and there is no single label
or score column.
Token classification / NER: returns a named list of data.frames,
one per input text, each with columns token, label, score. Special
tokens ([CLS], [SEP], padding) are automatically excluded.
For sequence tasks: a data.frame with nrow(texts) rows.
For NER: a list of data.frames, one per input text.
## Not run: clf <- load_hf_classifier("cardiffnlp/twitter-xlm-roberta-base-sentiment") res <- classify_texts(clf, c("I love this!", "Terrible experience.")) res$label # c("positive", "negative") res$score # highest class probability # VAD regression clf <- load_hf_classifier("RobroKools/vad-bert") classify_texts(clf, c("I am ecstatic!")) # returns: text | valence | arousal | dominance # NER clf <- load_hf_classifier("dslim/bert-base-NER") result <- classify_texts(clf, c("Marie Curie was born in Warsaw.")) result[[1]] # token | label | score ## End(Not run)## Not run: clf <- load_hf_classifier("cardiffnlp/twitter-xlm-roberta-base-sentiment") res <- classify_texts(clf, c("I love this!", "Terrible experience.")) res$label # c("positive", "negative") res$score # highest class probability # VAD regression clf <- load_hf_classifier("RobroKools/vad-bert") classify_texts(clf, c("I am ecstatic!")) # returns: text | valence | arousal | dominance # NER clf <- load_hf_classifier("dslim/bert-base-NER") result <- classify_texts(clf, c("Marie Curie was born in Warsaw.")) result[[1]] # token | label | score ## End(Not run)
Returns the first token's hidden state (B, H) from a (B, L, H) tensor.
Used for models whose 1_Pooling/config.json sets
pooling_mode_cls_token = true (e.g. some BGE and GTE variants).
cls_pool(hidden)cls_pool(hidden)
|
A 3-D |
A 2-D torch_tensor of shape (batch, hidden).
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") tokens <- enc$tokenizer$encode_batch(c("hello world")) ids <- torch::torch_tensor(matrix(tokens[[1L]]$ids, nrow = 1L), dtype = torch::torch_long()) mask <- torch::torch_tensor(matrix(tokens[[1L]]$attention_mask, nrow = 1L), dtype = torch::torch_long()) hidden <- enc$model(ids, mask) cls_pool(hidden) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") tokens <- enc$tokenizer$encode_batch(c("hello world")) ids <- torch::torch_tensor(matrix(tokens[[1L]]$ids, nrow = 1L), dtype = torch::torch_long()) mask <- torch::torch_tensor(matrix(tokens[[1L]]$attention_mask, nrow = 1L), dtype = torch::torch_long()) hidden <- enc$model(ids, mask) cls_pool(hidden) ## End(Not run)
Fit a clustering model and return cluster labels
cluster_docs(model, X, seed = 42L)cluster_docs(model, X, seed = 42L)
model |
A clustering model object. |
X |
Numeric matrix to cluster. |
seed |
Random seed (default 42). |
A list with $labels (integer vector; -1 = noise,
0, 1, 2, ... = cluster IDs) and $model (the fitted
model).
## Not run: m <- hdbscan_clustering(min_pts = 3L) enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("cats and dogs", "machine learning", "pets and animals", "neural networks")) res <- cluster_docs(m, emb) res$labels ## End(Not run)## Not run: m <- hdbscan_clustering(min_pts = 3L) enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("cats and dogs", "machine learning", "pets and animals", "neural networks")) res <- cluster_docs(m, emb) res$labels ## End(Not run)
Given a grouping variable (e.g. publication year, country, experimental condition) of the same length as the corpus, computes which topics are over- or under-represented in each group relative to a null model of independence.
compare_topics( fit, groups, method = c("chi2", "log_ratio"), min_count = 5L, verbose = TRUE )compare_topics( fit, groups, method = c("chi2", "log_ratio"), min_count = 5L, verbose = TRUE )
fit |
A |
groups |
Character or factor vector, one entry per document, defining
the group membership. Noise documents ( |
method |
Test statistic: |
min_count |
Minimum expected count for a (topic, group) cell to be included. Cells below this threshold are silently dropped (default 5). |
verbose |
Print the contingency table (default |
Two statistics are available:
"chi2"Signed chi-square contribution:
. Positive = over-represented,
negative = under-represented. A global chi-square test is also reported.
"log_ratio"Laplace-smoothed log ratio of observed
to expected proportion. Values above 1 mean the topic is twice as
prevalent in that group as expected; below -1 means half as prevalent.
A list of class compare_topics_result with elements:
resultTidy data frame with columns Topic,
Name, Group, Observed, Expected,
Stat.
tableRaw contingency table (topics x groups).
global_statistic, global_pvalue
Global chi-square
statistic and p-value (NA when method = "log_ratio").
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) groups <- sample(c("A", "B"), length(abstracts), replace = TRUE) comp <- compare_topics(fit, groups) visualize_comparison(comp) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) groups <- sample(c("A", "B"), length(abstracts), replace = TRUE) comp <- compare_topics(fit, groups) visualize_comparison(comp) ## End(Not run)
Use as the representation_model argument in fit_bertopic
to select the n-gram vocabulary using the C-value algorithm (Frantzi et al.
2000) rather than plain document-frequency filtering. Unigrams are always
kept alongside the C-value-selected multi-word terms.
cvalue_representation(max_n = 4L, threshold = 0, min_freq = 2L)cvalue_representation(max_n = 4L, threshold = 0, min_freq = 2L)
max_n |
Maximum n-gram length to evaluate (default 4). Longer n-grams are more informative but also rarer; values of 3 - 5 cover most terminology. |
threshold |
Minimum C-value score for a multi-word term to be retained (default 0). Increase to surface only well-established compound terms. |
min_freq |
Minimum corpus frequency for a candidate n-gram to be considered (default 2). |
An object of class cvalue_representation.
cvalue_terms, fit_bertopic,
pos_representation
m <- cvalue_representation(max_n = 3L, threshold = 0.5)m <- cvalue_representation(max_n = 3L, threshold = 0.5)
Implements the C-value method of Frantzi, Ananiadou & Mima (2000). For
each candidate n-gram (n >= 2):
cvalue_terms( docs, max_n = 4L, min_freq = 2L, threshold = 0, stopwords = character(0L) )cvalue_terms( docs, max_n = 4L, min_freq = 2L, threshold = 0, stopwords = character(0L) )
docs |
Character vector of documents. |
max_n |
Maximum n-gram length to consider (default 4). |
min_freq |
Minimum corpus frequency for a candidate term (default 2). |
threshold |
Minimum C-value to include in the returned table (default 0). |
stopwords |
Character vector of tokens to remove before n-gram extraction (default none). |
where is word count, is corpus frequency, and
is the set of longer candidate terms that contain as a
contiguous sub-sequence.
A data frame with columns term (underscore-separated tokens),
freq, n_words, and cvalue, sorted by descending
C-value. Terms with cvalue < threshold are excluded.
Frantzi, K., Ananiadou, S., & Mima, H. (2000). Automatic recognition of multi-word terms: the C-value/NC-value method. International Journal on Digital Libraries, 3(2), 115 - 130.
cvalue_representation, fit_bertopic
docs <- c("sea level rise and climate change", "sea level is rising", "climate change impacts sea level", "Arctic ice melt sea level") cvalue_terms(docs, max_n = 3L, min_freq = 2L)docs <- c("sea level rise and climate change", "sea level is rising", "climate change impacts sea level", "Arctic ice melt sea level") cvalue_terms(docs, max_n = 3L, min_freq = 2L)
Project new data using a fitted dimensionality-reduction model
dim_project(model, X)dim_project(model, X)
model |
A fitted dimensionality-reduction model (returned inside the
list from |
X |
New numeric matrix to project. |
A numeric matrix with the same number of columns as the training embedding.
## Not run: m <- umap_reduction(n_neighbors = 5L, n_components = 2L) enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("first doc", "second doc", "third doc")) res <- dim_reduce(m, emb) new_emb <- embed_texts(enc, c("new document")) dim_project(res$model, new_emb) ## End(Not run)## Not run: m <- umap_reduction(n_neighbors = 5L, n_components = 2L) enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("first doc", "second doc", "third doc")) res <- dim_reduce(m, emb) new_emb <- embed_texts(enc, c("new document")) dim_project(res$model, new_emb) ## End(Not run)
Fit a dimensionality-reduction model and return the reduced matrix
dim_reduce(model, X, seed = 42L, verbose = FALSE)dim_reduce(model, X, seed = 42L, verbose = FALSE)
model |
A dimensionality-reduction model object. |
X |
Numeric matrix to reduce. |
seed |
Random seed (default 42). |
verbose |
Print progress (default |
A list with $embedding (reduced matrix) and $model
(the fitted model, for use with dim_project).
## Not run: m <- umap_reduction(n_neighbors = 5L, n_components = 2L) enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("first doc", "second doc", "third doc")) res <- dim_reduce(m, emb) ## End(Not run)## Not run: m <- umap_reduction(n_neighbors = 5L, n_components = 2L) enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("first doc", "second doc", "third doc")) res <- dim_reduce(m, emb) ## End(Not run)
On the first call (or when overwrite = TRUE) the encoder is used to
compute embeddings and the result is written to cache_file. On
subsequent calls the cached matrix is read directly, making it free to
experiment with different dimensionality-reduction or clustering settings
without re-running the expensive forward passes.
embed_texts_cached( encoder = NULL, texts, cache_file = NULL, overwrite = FALSE, batch_size = 32L, max_length = 256L, normalize = TRUE, device = "cpu", prefix = NULL, chunk_strategy = c("truncate", "mean", "first"), chunk_overlap = 0L, verbose = interactive() )embed_texts_cached( encoder = NULL, texts, cache_file = NULL, overwrite = FALSE, batch_size = 32L, max_length = 256L, normalize = TRUE, device = "cpu", prefix = NULL, chunk_strategy = c("truncate", "mean", "first"), chunk_overlap = 0L, verbose = interactive() )
encoder |
An encoder from |
texts |
Character vector of documents to embed. Still required even when loading from cache so the row count can be validated. |
cache_file |
Path to a |
overwrite |
If |
batch_size, max_length, normalize, device, prefix, chunk_strategy, chunk_overlap
|
Forwarded to |
verbose |
Print progress messages. |
A numeric matrix with length(texts) rows.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") docs <- c("First document.", "Second document.", "Third document.") emb <- embed_texts_cached(enc, docs, cache_file = tempfile(fileext = ".rds")) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") docs <- c("First document.", "Second document.", "Third document.") emb <- embed_texts_cached(enc, docs, cache_file = tempfile(fileext = ".rds")) ## End(Not run)
S3 generic dispatching on the encoder class. For local BERT-family models
(class bert_encoder) see the Details section. For API-backed models
(class api_embedder) the function calls the provider's REST endpoint and
parameters max_length, device, chunk_strategy, and chunk_overlap
are ignored.
## S3 method for class 'api_embedder' embed_texts( encoder, texts, batch_size = NULL, normalize = TRUE, prefix = NULL, verbose = interactive(), ... ) embed_texts(encoder, texts, ...) ## S3 method for class 'bert_encoder' embed_texts( encoder, texts, batch_size = 32L, max_length = 256L, normalize = TRUE, device = "cpu", prefix = NULL, chunk_strategy = c("truncate", "mean", "first"), chunk_overlap = 0L, verbose = interactive(), gc_every = 1L, max_tokens = NULL, ... ) ## Default S3 method: embed_texts(encoder, texts, ...)## S3 method for class 'api_embedder' embed_texts( encoder, texts, batch_size = NULL, normalize = TRUE, prefix = NULL, verbose = interactive(), ... ) embed_texts(encoder, texts, ...) ## S3 method for class 'bert_encoder' embed_texts( encoder, texts, batch_size = 32L, max_length = 256L, normalize = TRUE, device = "cpu", prefix = NULL, chunk_strategy = c("truncate", "mean", "first"), chunk_overlap = 0L, verbose = interactive(), gc_every = 1L, max_tokens = NULL, ... ) ## Default S3 method: embed_texts(encoder, texts, ...)
encoder |
A loaded encoder: a |
texts |
A character vector of strings to embed. |
batch_size |
Number of texts (or chunks) per forward pass. |
normalize |
If |
prefix |
String prepended to every text before tokenization.
|
verbose |
If |
... |
Not used; retained for S3 method compatibility. |
max_length |
Truncate/chunk token sequences to this length (including
special tokens). Capped at the model's |
device |
Either |
chunk_strategy |
One of |
chunk_overlap |
Number of token overlap between consecutive windows
when |
gc_every |
Release memory held by finished batches every |
max_tokens |
Upper limit on padded tokens per forward pass (number of
texts in the batch times the longest one). Memory per batch grows with
sequence length, so this caps it: batches of long texts are made smaller,
while short texts still go up to |
Tokenizes, runs the encoder forward pass, pools over tokens (mean or CLS, auto-detected from the encoder's pooling configuration), and optionally L2-normalizes the result. Batches inputs for memory efficiency.
An instruction prefix can be prepended to every text before tokenization. This is required for best performance with BGE and E5 models:
BGE (BAAI/bge-*): prefix = "Represent this sentence: "
E5 (intfloat/e5-*): prefix = "passage: "
If the encoder was loaded with a prefix via load_hf_bert()'s prefix
argument, that value is used automatically and need not be repeated here.
Passing prefix explicitly always takes precedence.
Long-document chunking (chunk_strategy): BERT-family models have a
fixed maximum sequence length (usually 512 tokens) and silently truncate
longer inputs. Setting chunk_strategy = "mean" (or "first") instead
splits each over-length text into overlapping windows of max_length
tokens, embeds each window, and aggregates:
"truncate" (default): truncate at max_length, fast, no overhead.
"mean": embed all windows, average their vectors (then normalize
if normalize = TRUE). Best quality for long documents.
"first": embed only the first window. Good when the lead of a
document (abstract, summary) is the most informative part.
Use chunk_overlap to set the number of overlapping tokens between
consecutive windows (default 0).
A numeric matrix with length(texts) rows and hidden_size cols.
## Not run: # General model -- no prefix needed enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("First sentence.", "Second sentence.")) # BGE model -- set prefix at load time (applied automatically) enc <- load_hf_bert("BAAI/bge-base-en-v1.5", prefix = "Represent this sentence: ") emb <- embed_texts(enc, docs) # E5 model -- passage prefix for documents, query prefix for queries enc <- load_hf_bert("intfloat/e5-base-v2", prefix = "passage: ") doc_emb <- embed_texts(enc, docs) query_emb <- embed_texts(enc, queries, prefix = "query: ") # Long documents: chunk and mean-pool windows enc <- load_hf_bert("sentence-transformers/all-mpnet-base-v2") emb <- embed_texts(enc, long_papers, chunk_strategy = "mean", chunk_overlap = 32L) # API-based (no torch required) enc <- load_openai_embedder() emb <- embed_texts(enc, docs) ## End(Not run)## Not run: # General model -- no prefix needed enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("First sentence.", "Second sentence.")) # BGE model -- set prefix at load time (applied automatically) enc <- load_hf_bert("BAAI/bge-base-en-v1.5", prefix = "Represent this sentence: ") emb <- embed_texts(enc, docs) # E5 model -- passage prefix for documents, query prefix for queries enc <- load_hf_bert("intfloat/e5-base-v2", prefix = "passage: ") doc_emb <- embed_texts(enc, docs) query_emb <- embed_texts(enc, queries, prefix = "query: ") # Long documents: chunk and mean-pool windows enc <- load_hf_bert("sentence-transformers/all-mpnet-base-v2") emb <- embed_texts(enc, long_papers, chunk_strategy = "mean", chunk_overlap = 32L) # API-based (no torch required) enc <- load_openai_embedder() emb <- embed_texts(enc, docs) ## End(Not run)
Ranks non-noise topics by the c-TF-IDF score of search_term. If
the term is absent from the vocabulary entirely, falls back to substring
matching across each topic's top terms.
find_topics(fit, search_term, top_n = 5L)find_topics(fit, search_term, top_n = 5L)
fit |
A |
search_term |
A single character string. |
top_n |
Number of topics to return (default 5). |
A data frame with columns Topic, Name, Score,
sorted by descending score.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) find_topics(fit, "neural network") ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) find_topics(fit, "neural network") ## End(Not run)
Runs the four-stage BERTopic pipeline:
Embed documents with the supplied encoder.
Reduce embedding dimensionality with UMAP.
Cluster the reduced space with HDBSCAN.
Extract per-topic terms with c-TF-IDF.
fit_bertopic( encoder = NULL, docs, embeddings = NULL, dim_reduction_model = NULL, cluster_model = NULL, representation_model = NULL, umap_n_neighbors = 15, umap_n_components = 5, umap_min_dist = 0, umap_metric = "cosine", hdbscan_min_pts = 10, hdbscan_method = c("eom", "leaf"), top_n_terms = 10, language = "english", stopwords = NULL, extra_stopwords = NULL, ngram_range = c(1L, 1L), reduce_frequent_words = FALSE, seed = 42, verbose = TRUE )fit_bertopic( encoder = NULL, docs, embeddings = NULL, dim_reduction_model = NULL, cluster_model = NULL, representation_model = NULL, umap_n_neighbors = 15, umap_n_components = 5, umap_min_dist = 0, umap_metric = "cosine", hdbscan_min_pts = 10, hdbscan_method = c("eom", "leaf"), top_n_terms = 10, language = "english", stopwords = NULL, extra_stopwords = NULL, ngram_range = c(1L, 1L), reduce_frequent_words = FALSE, seed = 42, verbose = TRUE )
encoder |
A loaded encoder, as returned by |
docs |
Character vector of documents. |
embeddings |
Optional pre-computed embedding matrix (rows = documents,
columns = dimensions). When supplied, |
dim_reduction_model |
A dimensionality-reduction model object from
|
cluster_model |
A clustering model from |
representation_model |
Optional representation model from
|
umap_n_neighbors |
UMAP |
umap_n_components |
Reduced dimensionality for clustering (default 5). |
umap_min_dist |
UMAP |
umap_metric |
UMAP distance metric (default |
hdbscan_min_pts |
HDBSCAN |
hdbscan_method |
HDBSCAN cluster-extraction method: |
top_n_terms |
Number of terms per topic in the output (default 10). |
language |
Language for built-in stopword list (default
|
stopwords |
Character vector of words to drop before c-TF-IDF. Replaces the built-in list when supplied. |
extra_stopwords |
Additional stopwords appended to the built-in (or user-supplied) list. |
ngram_range |
Integer vector of length 2 specifying the minimum and
maximum n-gram sizes for c-TF-IDF (default |
reduce_frequent_words |
If |
seed |
Random seed for reproducibility (default 42). |
verbose |
Whether to print progress messages (default TRUE). |
Also computes a separate 2-D UMAP projection of the same embeddings, for visualization.
This implementation will be extended over time to mirror more of the Python BERTopic package's capabilities (custom vectorizers, topic reduction, representation models, dynamic topic modeling).
A list (class bertopic_fit) with elements:
embeddings, reduced, layout2d, clusters, topic_terms,
hdbscan, docs.
## Not run: enc <- load_hf_bert("pritamdeka/S-Scibert-snli-multinli-stsb") fit <- fit_bertopic(enc, docs = my_abstracts) print_topics(fit) ## End(Not run)## Not run: enc <- load_hf_bert("pritamdeka/S-Scibert-snli-multinli-stsb") fit <- fit_bertopic(enc, docs = my_abstracts) print_topics(fit) ## End(Not run)
All documents are embedded once using the shared encoder, then one BERTopic model is fitted per time period using the appropriate slice of embeddings. Because every model lives in the same embedding space, cosine similarity between topic centroids across periods is a meaningful measure of topic continuity.
fit_topics_over_time( encoder, docs, timestamps, min_similarity = 0.7, bertopic_params = list(), verbose = TRUE )fit_topics_over_time( encoder, docs, timestamps, min_similarity = 0.7, bertopic_params = list(), verbose = TRUE )
encoder |
An encoder from |
docs |
Character vector of all documents. |
timestamps |
A vector of the same length as |
min_similarity |
Minimum cosine similarity between topic centroids in
adjacent periods to count as a link (default |
bertopic_params |
A named list of additional arguments passed to
|
verbose |
Print progress messages (default |
Adjacent periods are compared by computing the full cosine-similarity
matrix between their topic centroids. Any pair whose similarity exceeds
min_similarity becomes a directed link in the transition graph.
From that graph each topic is labelled:
emergesNo incoming link from the previous period.
disappearsNo outgoing link to the next period.
continuesExactly one incoming and one outgoing link.
splitsOne incoming link, multiple outgoing links.
mergesMultiple incoming links, one outgoing link.
isolatedNo links in either direction.
An object of class bertopic_flow containing:
periodsCharacter vector of period labels in order.
fitsNamed list of bertopic_fit objects.
transitionsData frame of inter-period topic links:
period_from, topic_from, period_to,
topic_to, similarity.
topic_infoData frame with one row per (period, topic):
period, topic, label, count,
status.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") period <- rep(c("2010s", "2020s"), each = 50L) flow <- fit_topics_over_time(enc, docs = abstracts, period_var = period) visualize_topic_flow(flow) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") period <- rep(c("2010s", "2020s"), each = 50L) flow <- fit_topics_over_time(enc, docs = abstracts, period_var = period) visualize_topic_flow(flow) ## End(Not run)
Get document-level topic assignments as a data frame
get_document_info(fit, metadata = NULL)get_document_info(fit, metadata = NULL)
fit |
A |
metadata |
An optional named list of additional columns to append,
each with |
A data frame with one row per document and columns
Document, Topic, Name, Top_words.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_document_info(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_document_info(fit) ## End(Not run)
Representative documents are the three training documents whose embeddings lie closest (cosine similarity) to the topic centroid.
get_representative_docs(fit, topic = NULL)get_representative_docs(fit, topic = NULL)
fit |
A |
topic |
Optional integer topic ID. When |
A character vector (single topic) or a named list of character vectors (all topics).
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_representative_docs(fit, topic = 0L) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_representative_docs(fit, topic = 0L) ## End(Not run)
Currently only "english" is supported; returns an empty character
vector for any other value.
get_stopwords(language = "english")get_stopwords(language = "english")
language |
Language name (default |
A character vector of stopwords.
get_stopwords("english")get_stopwords("english")
Get term-score representation for a single topic
get_topic(fit, topic)get_topic(fit, topic)
fit |
A |
topic |
Integer topic ID. |
A data frame with columns term and score, or
NULL if the topic ID is not found.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_topic(fit, topic = 0L) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_topic(fit, topic = 0L) ## End(Not run)
Get topic-level metadata as a data frame
get_topic_info(fit, topic = NULL)get_topic_info(fit, topic = NULL)
fit |
A |
topic |
Optional integer. If provided, only the row for that topic is returned. |
A data frame with columns:
TopicInteger topic ID (-1 = noise/unassigned).
CountNumber of documents assigned to this topic.
NameAuto-generated label (e.g. "0_model_data_...").
RepresentationTop-5 terms, comma-separated.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_topic_info(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_topic_info(fit) ## End(Not run)
Get all topic-term representations
get_topics(fit, top_n = NULL)get_topics(fit, top_n = NULL)
fit |
A |
top_n |
Maximum terms per topic. |
A named list; each element is a data frame with columns
term and score sorted by descending score. Names are
topic IDs (character strings, including "-1" for noise).
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_topics(fit, top_n = 5L) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) get_topics(fit, top_n = 5L) ## End(Not run)
Extends fit_bertopic with a seed_topic_list argument.
For each seed topic the user supplies a character vector of representative
words; these are concatenated into virtual "anchor" documents, embedded, and
added to the corpus before dimensionality reduction and clustering. The
anchors bias the latent space so that documents semantically close to the
seed words cluster together. Seed documents are removed from the fit
before the object is returned.
guided_fit_bertopic( docs, seed_topic_list, embeddings = NULL, encoder = NULL, n_anchor_weight = 3L, ..., verbose = TRUE )guided_fit_bertopic( docs, seed_topic_list, embeddings = NULL, encoder = NULL, n_anchor_weight = 3L, ..., verbose = TRUE )
docs |
Character vector of documents. |
seed_topic_list |
Named list of character vectors – one entry per
expected topic. Example:
|
embeddings |
Optional pre-computed embedding matrix for |
encoder |
An encoder from |
n_anchor_weight |
Number of times each anchor document is replicated before adding to the corpus. Higher values give seeds more influence on the UMAP layout and HDBSCAN clustering (default 3). |
... |
Additional arguments forwarded to |
verbose |
Print progress messages (default |
A bertopic_fit object. The attribute seed_map
records which cluster each seed topic was assigned to.
fit_bertopic, zero_shot_topics
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") seeds <- list( "Climate" = c("carbon", "emissions", "greenhouse", "warming"), "Economy" = c("trade", "wages", "labour", "inflation") ) fit <- guided_fit_bertopic(abstracts, seed_topic_list = seeds, encoder = enc) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") seeds <- list( "Climate" = c("carbon", "emissions", "greenhouse", "warming"), "Economy" = c("trade", "wages", "labour", "inflation") ) fit <- guided_fit_bertopic(abstracts, seed_topic_list = seeds, encoder = enc) ## End(Not run)
HDBSCAN density clustering
hdbscan_clustering( min_pts = 10L, method = c("eom", "leaf"), knn = c("balltree", "kdtree", "adaptive", "fixed"), allow_single_cluster = FALSE )hdbscan_clustering( min_pts = 10L, method = c("eom", "leaf"), knn = c("balltree", "kdtree", "adaptive", "fixed"), allow_single_cluster = FALSE )
min_pts |
Minimum cluster size and core-distance order (default 10). |
method |
|
knn |
kNN graph construction strategy for the Boruvka MST step.
|
allow_single_cluster |
Logical (default |
Default clustering model for fit_bertopic. Uses a Boruvka
minimum spanning tree on the mutual-reachability kNN graph (Rcpp) instead
of the naive Prim's algorithm in dbscan::hdbscan(), which avoids the
O(n^2) memory cost that causes OOM at ~30K+ documents. Memory is O(n x k)
throughout, making it practical at 100K+ documents.
An hdbscan_clustering model object.
m <- hdbscan_clustering(min_pts = 5L) m_kd <- hdbscan_clustering(min_pts = 5L, knn = "kdtree")m <- hdbscan_clustering(min_pts = 5L) m_kd <- hdbscan_clustering(min_pts = 5L, knn = "kdtree")
Performs agglomerative clustering on the L2-normalised topic centroids
stored in fit$topic_centroids using cosine distance. At each merge
node the function pools the c-TF-IDF scores of all constituent topics and
records the top terms, so every level of the hierarchy is labelled.
hierarchical_topics(fit, method = "ward.D2", top_n_terms = 10L)hierarchical_topics(fit, method = "ward.D2", top_n_terms = 10L)
fit |
A |
method |
Linkage method passed to |
top_n_terms |
Number of top terms to record at each internal node. |
The result can be visualised with visualize_hierarchy().
A list of class hierarchical_topics with elements:
hclustthe hclust object (for use with base R dendrogram
functions if desired)
merge_dfa data frame with one row per merge, recording
parent_id, child_left, child_right, distance (cosine),
topics (comma-separated topic IDs), and terms
topic_idsinteger vector of leaf topic IDs in original order
methodthe linkage method used
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(enc, docs = abstracts) h <- hierarchical_topics(fit) print(h) visualize_hierarchy(h, fit = fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(enc, docs = abstracts) h <- hierarchical_topics(fit) print(h) visualize_hierarchy(h, fit = fit) ## End(Not run)
Wraps stats::kmeans. Unlike HDBSCAN, k-means assigns every
document to a cluster (no noise label -1) and requires specifying
the number of clusters k in advance.
kmeans_clustering(k, nstart = 10L, iter.max = 300L)kmeans_clustering(k, nstart = 10L, iter.max = 300L)
k |
Number of clusters. |
nstart |
Number of random restarts (default 10). |
iter.max |
Maximum iterations (default 300). |
A kmeans_clustering model object.
m <- kmeans_clustering(k = 5L)m <- kmeans_clustering(k = 5L)
For each non-noise topic, builds a prompt containing the top c-TF-IDF terms
and up to n_representative_docs representative documents, sends it to
an LLM API, and stores the returned label in fit$topic_labels.
label_topics_llm( fit, provider = c("anthropic", "openai", "ollama"), api_key = NULL, model = NULL, top_n_terms = 10L, n_representative_docs = 3L, custom_prompt = NULL, verbose = TRUE )label_topics_llm( fit, provider = c("anthropic", "openai", "ollama"), api_key = NULL, model = NULL, top_n_terms = 10L, n_representative_docs = 3L, custom_prompt = NULL, verbose = TRUE )
fit |
A |
provider |
LLM provider: |
api_key |
API key string. Ignored for |
model |
Model identifier. Defaults to |
top_n_terms |
Number of top terms included in the prompt (default 10). |
n_representative_docs |
Number of representative documents included in the prompt (default 3). |
custom_prompt |
Optional character string to override the default
prompt template. Use |
verbose |
Print each returned label as it arrives (default |
Three providers are supported:
"anthropic"Anthropic Claude via the Messages API. Requires
api_key or the ANTHROPIC_API_KEY environment variable.
"openai"OpenAI GPT via the Chat Completions API. Requires
api_key or OPENAI_API_KEY.
"ollama"Local Ollama server (OpenAI-compatible endpoint at
http://localhost:11434/v1). No API key required. Start the server
with ollama serve and pull a model with
ollama pull llama3.2 before use.
Requires the httr2 package.
The input fit with updated $topic_labels.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- label_topics_llm(fit, provider = "anthropic", api_key = Sys.getenv("ANTHROPIC_API_KEY"), model = "claude-haiku-4-5-20251001") get_topic_info(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- label_topics_llm(fit, provider = "anthropic", api_key = Sys.getenv("ANTHROPIC_API_KEY"), model = "claude-haiku-4-5-20251001") get_topic_info(fit) ## End(Not run)
Reads weights from a safetensors or PyTorch pickle file, normalizes the
parameter names to match the R module structure, filters out task-head
keys we don't need (pooler.*, cls.*, lm_head.*, ...), and applies
the result via model$load_state_dict().
load_bert_weights(model, weights_path, strict = FALSE)load_bert_weights(model, weights_path, strict = FALSE)
model |
A |
weights_path |
Path to a |
strict |
If TRUE, errors when expected parameters are missing. If FALSE, just warns. Default FALSE – most checkpoints have a handful of extra task-head keys that are correctly ignored. |
Most users don't call this directly – it's invoked by load_hf_bert().
Exposed for users who construct a model manually or want to load
alternative weight files.
Invisibly, the named list of loaded weights.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") # load_bert_weights() is called internally by load_hf_bert() and # load_specter2(); advanced users can call it directly when injecting # custom weights into an existing model object. ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") # load_bert_weights() is called internally by load_hf_bert() and # load_specter2(); advanced users can call it directly when injecting # custom weights into an existing model object. ## End(Not run)
Reads a model directory written by save_bertopic and
reconstructs the bertopic_fit object.
load_bertopic(path)load_bertopic(path)
path |
Path to the directory created by |
A bertopic_fit object.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) path <- tempdir() save_bertopic(fit, path) fit2 <- load_bertopic(path) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) path <- tempdir() save_bertopic(fit, path) fit2 <- load_bertopic(path) ## End(Not run)
Returns an api_embedder that calls the Cohere Embed API.
Requires the httr2 package and a valid Cohere API key.
load_cohere_embedder( model = "embed-english-v3.0", api_key = Sys.getenv("COHERE_API_KEY"), input_type = "search_document" )load_cohere_embedder( model = "embed-english-v3.0", api_key = Sys.getenv("COHERE_API_KEY"), input_type = "search_document" )
model |
Cohere embedding model name.
|
api_key |
API key. Defaults to |
input_type |
Cohere input type controlling the embedding space used.
|
The returned object is compatible with embed_texts(),
embed_texts_cached(), and fit_bertopic().
An api_embedder object.
## Not run: enc <- load_cohere_embedder() # uses COHERE_API_KEY env var emb <- embed_texts(enc, docs) ## End(Not run)## Not run: enc <- load_cohere_embedder() # uses COHERE_API_KEY env var emb <- embed_texts(enc, docs) ## End(Not run)
Load an embedding matrix from disk
load_embeddings(path)load_embeddings(path)
path |
Path to a |
A numeric matrix.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("doc one", "doc two")) path <- tempfile(fileext = ".rds") save_embeddings(emb, path) load_embeddings(path) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("doc one", "doc two")) path <- tempfile(fileext = ".rds") save_embeddings(emb, path) load_embeddings(path) ## End(Not run)
Downloads a model's config, weights, and tokenizer from the HuggingFace
Hub and returns an "encoder" object that can be passed to embed_texts()
or fit_bertopic(). Works for any model with a standard BERT
architecture: vanilla BERT, MiniLM, MPNet, SciBERT, BioBERT, ClinicalBERT,
BERT-for-Patents, FinBERT, LegalBERT, the sentence-transformers built on
top of these, and so on.
load_hf_bert(repo_id, weights_path = NULL, prefix = "")load_hf_bert(repo_id, weights_path = NULL, prefix = "")
repo_id |
A HuggingFace repo ID, e.g.
|
weights_path |
Optional path to a local weights file ( |
prefix |
Optional string prepended to every text before tokenization.
Required for best performance with BGE ( |
The function tries to use modern formats when available and falls back transparently when they aren't:
Weights: model.safetensors is preferred (device-agnostic, safe,
fast). If absent, pytorch_model.bin is read via R-torch's pickle
loader. If the model has only pytorch_model.bin and that file was
saved on a CUDA device, the load will fail – convert the upstream
model to safetensors first (see HuggingFace's safetensors/convert
Space) and pass the result via weights_path.
Tokenizer: tokenizer.json (HuggingFace fast tokenizer via the
tok package) is preferred. If absent, falls back to
make_wordpiece_tokenizer() reading vocab.txt (requires the
wordpiece package).
A list with three elements:
modelthe loaded bert_model nn_module
tokenizera tokenizer object exposing encode_batch()
configthe architecture config as a list
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("Hello world.", "Another sentence.")) # BGE model -- prefix at load time, applied automatically on every embed call enc <- load_hf_bert("BAAI/bge-base-en-v1.5", prefix = "Represent this sentence: ") # E5 model -- passage prefix for documents enc <- load_hf_bert("intfloat/e5-base-v2", prefix = "passage: ") # Override per-call for query-side embedding: query_emb <- embed_texts(enc, queries, prefix = "query: ") # With a custom weights file (e.g. safetensors from an unmerged PR) enc <- load_hf_bert("pritamdeka/S-Scibert-snli-multinli-stsb", weights_path = "/path/to/local/model.safetensors") ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("Hello world.", "Another sentence.")) # BGE model -- prefix at load time, applied automatically on every embed call enc <- load_hf_bert("BAAI/bge-base-en-v1.5", prefix = "Represent this sentence: ") # E5 model -- passage prefix for documents enc <- load_hf_bert("intfloat/e5-base-v2", prefix = "passage: ") # Override per-call for query-side embedding: query_emb <- embed_texts(enc, queries, prefix = "query: ") # With a custom weights file (e.g. safetensors from an unmerged PR) enc <- load_hf_bert("pritamdeka/S-Scibert-snli-multinli-stsb", weights_path = "/path/to/local/model.safetensors") ## End(Not run)
Downloads config.json, model weights, and a tokenizer from the HuggingFace
Hub and returns an hf_classifier object. Supports sequence classification
(sentiment, topic, intent), multi-label classification, regression (e.g. VAD
emotion scores), and token classification (NER, POS tagging).
load_hf_classifier(repo_id, weights_path = NULL, prefix = "")load_hf_classifier(repo_id, weights_path = NULL, prefix = "")
repo_id |
HuggingFace repo ID, e.g. |
weights_path |
Optional path to a local weights file ( |
prefix |
Optional string prepended to every text before tokenization
(useful for instruction-tuned classifiers). Default |
The task type and output format are auto-detected from config.json:
architectures containing "ForTokenClassification" -> NER mode
problem_type = "regression" -> return raw numeric scores
problem_type = "multi_label_classification" -> sigmoid per label
otherwise -> softmax single-label classification
Supported backbone architectures: the same as load_hf_bert() – BERT,
RoBERTa, XLM-RoBERTa, CamemBERT, DistilBERT (backbone only), MPNet.
An hf_classifier list with elements model, tokenizer, config,
id2label, problem_type, task, num_labels, repo_id, prefix.
## Not run: # Sentiment analysis (single-label, 3 classes) clf <- load_hf_classifier("cardiffnlp/twitter-xlm-roberta-base-sentiment") classify_texts(clf, c("I love this!", "Terrible experience.")) # VAD regression (valence, arousal, dominance) clf <- load_hf_classifier("RobroKools/vad-bert") classify_texts(clf, c("I am ecstatic!", "Feeling nervous about the exam.")) # Named entity recognition clf <- load_hf_classifier("dslim/bert-base-NER") classify_texts(clf, c("Albert Einstein was born in Ulm, Germany.")) ## End(Not run)## Not run: # Sentiment analysis (single-label, 3 classes) clf <- load_hf_classifier("cardiffnlp/twitter-xlm-roberta-base-sentiment") classify_texts(clf, c("I love this!", "Terrible experience.")) # VAD regression (valence, arousal, dominance) clf <- load_hf_classifier("RobroKools/vad-bert") classify_texts(clf, c("I am ecstatic!", "Feeling nervous about the exam.")) # Named entity recognition clf <- load_hf_classifier("dslim/bert-base-NER") classify_texts(clf, c("Albert Einstein was born in Ulm, Germany.")) ## End(Not run)
Returns an api_embedder that calls the OpenAI Embeddings API.
Requires the httr2 package and a valid OpenAI API key.
load_openai_embedder( model = "text-embedding-3-small", api_key = Sys.getenv("OPENAI_API_KEY"), dimensions = NULL, base_url = "https://api.openai.com/v1" )load_openai_embedder( model = "text-embedding-3-small", api_key = Sys.getenv("OPENAI_API_KEY"), dimensions = NULL, base_url = "https://api.openai.com/v1" )
model |
OpenAI embedding model name.
|
api_key |
API key. Defaults to |
dimensions |
Optional integer to request a reduced output dimension
(only supported by |
base_url |
API base URL. Override for Azure OpenAI or API proxies. |
The returned object is compatible with embed_texts(),
embed_texts_cached(), and fit_bertopic().
An api_embedder object.
## Not run: enc <- load_openai_embedder() # uses OPENAI_API_KEY env var emb <- embed_texts(enc, c("Transformers in R.", "Topic modelling.")) fit <- fit_bertopic(enc, docs = abstracts) ## End(Not run)## Not run: enc <- load_openai_embedder() # uses OPENAI_API_KEY env var emb <- embed_texts(enc, c("Transformers in R.", "Topic modelling.")) fit <- fit_bertopic(enc, docs = abstracts) ## End(Not run)
SPECTER2 doi:10.48550/arXiv.2211.13308 extends the original SPECTER model
with task-specific Pfeiffer adapters trained on millions of citation pairs.
This function loads the base encoder from allenai/specter2_base and
injects the chosen adapter into each transformer layer, matching the
behaviour of the Python adapters library.
load_specter2( adapter = "NetworkIsLife/specter2", base = "NetworkIsLife/specter2_base", adapter_name = "[PRX]" )load_specter2( adapter = "NetworkIsLife/specter2", base = "NetworkIsLife/specter2_base", adapter_name = "[PRX]" )
adapter |
HuggingFace repo ID of the adapter checkpoint. Must contain
a |
base |
HuggingFace repo ID of the base model.
Default: |
adapter_name |
Name of the adapter as stored in the checkpoint's weight
keys. For all official SPECTER2 adapters this is |
Available adapters on the HuggingFace Hub:
"allenai/specter2"Proximity / similarity – recommended for document retrieval and topic modeling (default).
"allenai/specter2_adhoc_query"Query-side adapter for asymmetric retrieval (query vs. document).
"allenai/specter2_classification"Trained for paper classification tasks.
The returned object is a standard bert_encoder compatible with
embed_texts(), embed_texts_cached(), and fit_bertopic().
A bert_encoder object (same class as load_hf_bert()) with
Pfeiffer adapter modules injected into every transformer layer. The list
also carries $adapter_repo recording which adapter was loaded.
## Not run: enc <- load_specter2() # proximity adapter -- best for topic modeling emb <- embed_texts(enc, c("Graph neural networks for drug discovery.", "Climate tipping points and carbon budgets.")) ## End(Not run)## Not run: enc <- load_specter2() # proximity adapter -- best for topic modeling emb <- embed_texts(enc, c("Graph neural networks for drug discovery.", "Climate tipping points and carbon budgets.")) ## End(Not run)
Accepts three input types, making it easy to manage domain-specific stopwords from any source:
Returned as-is (after deduplication and trimming).
Words are taken from column (or the first column
when column is NULL).
Supports two file types:
Plain text (.txt or no extension): one word per
line, blank lines are ignored.
Tabular (.csv or .tsv): read as a
data frame and words extracted from column (or the first
column).
load_stopwords(source, column = NULL)load_stopwords(source, column = NULL)
source |
A character vector of words, a file path, or a data frame. |
column |
Name of the column to use when |
The result is typically combined with the built-in list before passing to
fit_bertopic:
domain_words <- load_stopwords("domain_stop.txt")
fit <- fit_bertopic(enc, docs,
extra_stopwords = domain_words)
A deduplicated character vector of stopwords (lowercased and whitespace-trimmed).
load_stopwords(c("foo", "bar", "baz")) load_stopwords(data.frame(word = c("foo", "bar")), column = "word")load_stopwords(c("foo", "bar", "baz")) load_stopwords(data.frame(word = c("foo", "bar")), column = "word")
tokenizer.json
Older BERT-family models (SciBERT, BioBERT, the original BERT-base, etc.)
ship a vocab.txt file but not the HuggingFace fast-tokenizer format.
This function builds a tokenizer object exposing the same methods as
tok::tokenizer$from_pretrained() (encode_batch, enable_padding,
enable_truncation) so it can be used interchangeably with
embed_texts().
make_wordpiece_tokenizer(vocab_path, do_lower_case = NULL, max_length = 512L)make_wordpiece_tokenizer(vocab_path, do_lower_case = NULL, max_length = 512L)
vocab_path |
Path to the model's |
do_lower_case |
Logical, whether to lowercase input before
tokenization. If |
max_length |
Default maximum sequence length, including the two
special tokens |
Uses the CRAN package wordpiece for the WordPiece algorithm itself.
A list with methods encode_batch(texts), enable_padding(),
and enable_truncation(max_length).
## Not run: vocab <- hfhub::hub_download("allenai/scibert_scivocab_cased", "vocab.txt") tk <- make_wordpiece_tokenizer(vocab) tk$encode_batch(c("This is a sentence.", "Another one.")) ## End(Not run)## Not run: vocab <- hfhub::hub_download("allenai/scibert_scivocab_cased", "vocab.txt") tk <- make_wordpiece_tokenizer(vocab) tk$encode_batch(c("This is a sentence.", "Another one.")) ## End(Not run)
Takes the encoder's final hidden states (B, L, H) and the attention mask
(B, L), masks out padding positions, and averages along the sequence
dimension to produce (B, H) sentence vectors.
mean_pool(hidden, attention_mask)mean_pool(hidden, attention_mask)
|
A 3-D |
|
attention_mask |
An integer |
A 2-D torch_tensor of shape (batch, hidden).
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") tokens <- enc$tokenizer$encode_batch(c("hello world")) ids <- torch::torch_tensor(matrix(tokens[[1L]]$ids, nrow = 1L), dtype = torch::torch_long()) mask <- torch::torch_tensor(matrix(tokens[[1L]]$attention_mask, nrow = 1L), dtype = torch::torch_long()) hidden <- enc$model(ids, mask) mean_pool(hidden, mask) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") tokens <- enc$tokenizer$encode_batch(c("hello world")) ids <- torch::torch_tensor(matrix(tokens[[1L]]$ids, nrow = 1L), dtype = torch::torch_long()) mask <- torch::torch_tensor(matrix(tokens[[1L]]$attention_mask, nrow = 1L), dtype = torch::torch_long()) hidden <- enc$model(ids, mask) mean_pool(hidden, mask) ## End(Not run)
All topics in topics_to_merge are combined into the one with the
smallest ID. All derived state (c-TF-IDF terms, labels, centroids,
representative docs) is recomputed from the merged assignments.
merge_topics(fit, topics_to_merge)merge_topics(fit, topics_to_merge)
fit |
A |
topics_to_merge |
Integer vector of at least two topic IDs to merge. |
An updated bertopic_fit.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- merge_topics(fit, topics_to_merge = c(0L, 1L)) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- merge_topics(fit, topics_to_merge = c(0L, 1L)) ## End(Not run)
Passes the raw embeddings directly to the clustering step. Useful when the embeddings are already low-dimensional or when you want to cluster in the original space.
no_reduction()no_reduction()
A no_reduction model object.
m <- no_reduction()m <- no_reduction()
Wraps stats::prcomp. Unlike UMAP, PCA is deterministic and fast,
but often produces lower-quality cluster separation for text embeddings.
pca_reduction(n_components = 5L, scale. = FALSE)pca_reduction(n_components = 5L, scale. = FALSE)
n_components |
Number of principal components to retain. |
scale. |
Whether to scale variables before computing PCA
(default |
A pca_reduction model object.
m <- pca_reduction(n_components = 3L)m <- pca_reduction(n_components = 3L)
Annotates each document in docs with a udpipe language model, keeps
only tokens whose UPOS tag is in pos (and/or phrases matching
patterns), then builds a sparse document-term matrix from the
surviving tokens. The result is compatible with c_tf_idf.
pos_dtm( docs, pos = c("NOUN", "PROPN"), patterns = NULL, lemmatize = TRUE, language = "english", model_dir = NULL, min_df = 2L, max_df_frac = 0.95, extra_stopwords = character(0L), verbose = TRUE )pos_dtm( docs, pos = c("NOUN", "PROPN"), patterns = NULL, lemmatize = TRUE, language = "english", model_dir = NULL, min_df = 2L, max_df_frac = 0.95, extra_stopwords = character(0L), verbose = TRUE )
docs |
Character vector of documents. |
pos |
UPOS tags to retain. Default |
patterns |
Optional list of UPOS sequences for multi-word phrases. |
lemmatize |
Use lemmatised forms (default |
language |
udpipe language name (default |
model_dir |
Directory for the cached language model. |
min_df |
Minimum document frequency (default 2). |
max_df_frac |
Maximum document-frequency fraction (default 0.95). |
extra_stopwords |
Character vector of additional words to exclude. |
verbose |
Print progress messages (default |
A sparse dgCMatrix: documents x POS-filtered vocabulary.
## Not run: docs <- c("The neural network learns features.", "Gradient descent optimizes weights.", "Transformers use attention mechanisms.") pos_dtm(docs, pos = c("NOUN", "VERB")) ## End(Not run)## Not run: docs <- c("The neural network learns features.", "Gradient descent optimizes weights.", "Transformers use attention mechanisms.") pos_dtm(docs, pos = c("NOUN", "VERB")) ## End(Not run)
Use as the representation_model argument in fit_bertopic
to restrict the c-TF-IDF vocabulary to tokens whose Universal POS (UPOS) tag
is in pos, or to phrases that match specific POS-tag sequences.
pos_representation( pos = c("NOUN", "PROPN"), patterns = NULL, lemmatize = TRUE, language = "english", model_dir = NULL, min_df = 2L, max_df_frac = 0.95 )pos_representation( pos = c("NOUN", "PROPN"), patterns = NULL, lemmatize = TRUE, language = "english", model_dir = NULL, min_df = 2L, max_df_frac = 0.95 )
pos |
Character vector of UPOS tags to retain as single tokens.
Default |
patterns |
Optional list of UPOS sequences to extract as multi-word
phrases, e.g. |
lemmatize |
Use lemmatised forms rather than surface tokens (default
|
language |
Language name understood by udpipe, e.g. |
model_dir |
Directory to cache the udpipe language model. Defaults to a session-scoped temporary directory. |
min_df |
Minimum document frequency for a term to survive (default 2). |
max_df_frac |
Maximum document-frequency fraction (default 0.95). |
Requires the udpipe package
(install.packages("udpipe")). A language model (~15 MB) is
downloaded from the Universal Dependencies collection on first use and
cached in model_dir.
Common UPOS tags:
NOUNCommon nouns (default, with PROPN)
PROPNProper nouns
VERBMain verbs – use for action-focused topics
ADJAdjectives
ADVAdverbs
An object of class pos_representation.
pos_dtm, fit_bertopic,
cvalue_representation
m <- pos_representation(pos = c("NOUN", "PROPN", "ADJ"))m <- pos_representation(pos = c("NOUN", "PROPN", "ADJ"))
Embeds new documents (or accepts pre-computed embeddings) and assigns each
to the nearest topic centroid by cosine similarity. Noise (-1) is
never assigned as a target – all new documents receive a real topic.
## S3 method for class 'bertopic_fit' predict(object, new_docs, encoder = NULL, embeddings = NULL, ...)## S3 method for class 'bertopic_fit' predict(object, new_docs, encoder = NULL, embeddings = NULL, ...)
object |
A |
new_docs |
Character vector of documents to predict. |
encoder |
Optional encoder from |
embeddings |
Optional numeric matrix of pre-computed embeddings
( |
... |
Unused (for S3 generic compatibility). |
A list with:
topicsInteger vector of assigned topic IDs.
probabilitiesCosine similarity to the assigned centroid
(proxy for confidence, in [0, 1] for normalised embeddings).
all_similaritiesNumeric matrix
(nrow = length(new_docs), ncol = n_topics) of cosine
similarities to every topic centroid.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) pred <- predict(fit, new_docs = c("new paper about deep learning"), encoder = enc) pred$topics ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) pred <- predict(fit, new_docs = c("new paper about deep learning"), encoder = enc) pred$topics ## End(Not run)
Pretty-print discovered topics
print_topics(fit, max_topics = 20)print_topics(fit, max_topics = 20)
fit |
A fit object from |
max_topics |
Maximum number of topics to display. |
Called for its side effect (printing); returns NULL invisibly.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) print_topics(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) print_topics(fit) ## End(Not run)
Print method for bert_encoder objects
## S3 method for class 'bert_encoder' print(x, ...)## S3 method for class 'bert_encoder' print(x, ...)
x |
A bert_encoder object. |
... |
Unused. |
Invisibly returns x.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") print(enc) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") print(enc) ## End(Not run)
Print method for bertopic_fit objects
## S3 method for class 'bertopic_fit' print(x, ...)## S3 method for class 'bertopic_fit' print(x, ...)
x |
A bertopic_fit object. |
... |
Unused. |
Invisibly returns x.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) print(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) print(fit) ## End(Not run)
Print method for bertopic_flow objects
## S3 method for class 'bertopic_flow' print(x, ...)## S3 method for class 'bertopic_flow' print(x, ...)
x |
A |
... |
Unused. |
Invisibly returns x.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") flow <- fit_topics_over_time(enc, docs = abstracts, period_var = rep(c("A", "B"), each = 50L)) print(flow) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") flow <- fit_topics_over_time(enc, docs = abstracts, period_var = rep(c("A", "B"), each = 50L)) print(flow) ## End(Not run)
Print method for hf_classifier objects
## S3 method for class 'hf_classifier' print(x, ...)## S3 method for class 'hf_classifier' print(x, ...)
x |
An hf_classifier object. |
... |
Unused. |
Invisibly returns x.
## Not run: cls <- load_hf_classifier("cardiffnlp/twitter-roberta-base-sentiment-latest") print(cls) ## End(Not run)## Not run: cls <- load_hf_classifier("cardiffnlp/twitter-roberta-base-sentiment-latest") print(cls) ## End(Not run)
Documents assigned to the noise cluster (-1) by HDBSCAN are
reassigned to the most similar non-noise topic. Only documents whose
best similarity exceeds threshold are reassigned; the rest remain
as noise.
reduce_outliers(fit, strategy = "embeddings", threshold = 0, verbose = TRUE)reduce_outliers(fit, strategy = "embeddings", threshold = 0, verbose = TRUE)
fit |
A |
strategy |
How to measure similarity:
|
threshold |
Minimum similarity required to reassign a document.
Documents below the threshold remain as noise ( |
verbose |
Print a reassignment summary (default |
An updated bertopic_fit.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- reduce_outliers(fit, threshold = 0.1) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- reduce_outliers(fit, threshold = 0.1) ## End(Not run)
At each step the two non-noise topics whose centroids have the highest cosine similarity are merged. The loop continues until the desired number of topics remains. After the loop, topics are renumbered 0, 1, 2, ... in order of their (post-merge) IDs and all derived state is recomputed.
reduce_topics(fit, nr_topics, verbose = TRUE)reduce_topics(fit, nr_topics, verbose = TRUE)
fit |
A |
nr_topics |
Target number of non-noise topics. |
verbose |
Print progress messages (default |
An updated bertopic_fit with renumbered topics.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- reduce_topics(fit, nr_topics = 5L) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) fit <- reduce_topics(fit, nr_topics = 5L) ## End(Not run)
Downloads a selection of classic novels, splits them into paragraphs, and runs the full BERTopic pipeline: embed -> UMAP -> HDBSCAN -> c-TF-IDF. Prints a topic summary and shows two interactive plots: a 2-D topic map and a bar chart of top terms per topic.
rhobots_demo(n_per_book = 150L, device = "cpu", seed = 42L, verbose = TRUE)rhobots_demo(n_per_book = 150L, device = "cpu", seed = 42L, verbose = TRUE)
n_per_book |
Maximum paragraphs to sample per book (default 150). Lower values are faster; higher values give richer, more stable topics. |
device |
Device for the encoder: |
seed |
Integer random seed for UMAP and sampling (default 42). |
verbose |
Print progress messages (default |
The encoder used is sentence-transformers/all-MiniLM-L6-v2 (~22 MB),
which is downloaded once and cached by hfhub. Subsequent runs skip
the download.
Invisibly, a list with elements fit, embeddings, encoder,
and texts (a data frame with columns text and title), so you can
continue exploring after the demo.
## Not run: result <- rhobots_demo(n_per_book = 50L) print_topics(result$fit) ## End(Not run)## Not run: result <- rhobots_demo(n_per_book = 50L) print_topics(result$fit) ## End(Not run)
Checks whether the 'torch' C++ backend is installed and prints the appropriate setup instructions. Rhobots requires the 'torch' backend (libtorch + lantern, ~560 MB) to run transformer models. Call this function after installing the package to find out what still needs to be done.
rhobots_install()rhobots_install()
Invisible NULL, called for its side-effect of printing instructions.
rhobots_install()rhobots_install()
Writes a bertopic_fit object to a directory. The fit is split into:
metadata.json – human-readable summary (topic labels, counts,
parameters).
fit.rds – the full fit object minus the large matrices.
embeddings.rds – the document embedding matrix (optional).
dtm.rds – the sparse document-term matrix (optional).
Splitting the heavy matrices means the directory can be browsed and the metadata inspected without loading gigabytes into R.
save_bertopic( fit, path, include_embeddings = TRUE, include_dtm = TRUE, compress = TRUE )save_bertopic( fit, path, include_embeddings = TRUE, include_dtm = TRUE, compress = TRUE )
fit |
A |
path |
Directory path. Created if it does not exist. |
include_embeddings |
Save the embedding matrix (default |
include_dtm |
Save the document-term matrix (default |
compress |
Compress |
Invisibly, the normalised path.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) save_bertopic(fit, tempdir()) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) save_bertopic(fit, tempdir()) ## End(Not run)
Save an embedding matrix to disk
save_embeddings(embeddings, path)save_embeddings(embeddings, path)
embeddings |
A numeric matrix (rows = documents, columns = dimensions). |
path |
File path. Use a |
The resolved file path, invisibly.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("doc one", "doc two")) save_embeddings(emb, tempfile(fileext = ".rds")) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, c("doc one", "doc two")) save_embeddings(emb, tempfile(fileext = ".rds")) ## End(Not run)
Runs fit_bertopic n_runs times with different random
seeds on the same corpus, then measures how consistent the document
assignments are across runs using the Adjusted Rand Index (ARI).
stability_analysis( docs, embeddings, n_runs = 5L, seeds = NULL, ..., verbose = TRUE )stability_analysis( docs, embeddings, n_runs = 5L, seeds = NULL, ..., verbose = TRUE )
docs |
Character vector of documents. |
embeddings |
Pre-computed embedding matrix (strongly recommended to avoid re-embedding for every run). |
n_runs |
Number of independent fits (default 5). |
seeds |
Integer vector of random seeds, length |
... |
Additional arguments forwarded to |
verbose |
Print per-run progress (default |
ARI = 1 means two clusterings are identical; ARI 0 means
agreement no better than chance; negative values indicate systematic
disagreement. A mean ARI above 0.8 across all run pairs indicates a
stable, reproducible topic structure.
A list of class stability_result with elements:
ari_matrixSymmetric ARI matrix (n_runs x n_runs).
mean_ariMean ARI across all off-diagonal pairs.
per_doc_stabilityFor each document, the fraction of runs that agreed on the modal topic assignment (1.0 = always the same).
n_topics_per_runInteger vector of non-noise topic counts.
fitsList of bertopic_fit objects (one per run).
visualize_stability, sweep_topics
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts) stab <- stability_analysis(abstracts, emb, n_runs = 3L) stab$mean_ari ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts) stab <- stability_analysis(abstracts, emb, n_runs = 3L) stab$mean_ari ## End(Not run)
Runs fit_bertopic and topic_quality over a full
factorial grid of UMAP and HDBSCAN parameters (and optionally multiple
embedding models), then returns a tidy data frame of quality metrics for
every combination. Use visualize_sweep to compare runs
visually.
sweep_topics( docs, encoders = NULL, embeddings = NULL, n_neighbors = c(5L, 15L, 30L), n_components = c(5L, 10L), min_pts = c(5L, 10L, 20L), min_topics = NULL, ngram_range = c(1L, 1L), top_n_terms = 10L, extra_stopwords = NULL, sample_size = NULL, quality_top_n = 10L, quality_sample = 500L, quality_space = "reduced", seed = 42L, verbose = TRUE )sweep_topics( docs, encoders = NULL, embeddings = NULL, n_neighbors = c(5L, 15L, 30L), n_components = c(5L, 10L), min_pts = c(5L, 10L, 20L), min_topics = NULL, ngram_range = c(1L, 1L), top_n_terms = 10L, extra_stopwords = NULL, sample_size = NULL, quality_top_n = 10L, quality_sample = 500L, quality_space = "reduced", seed = 42L, verbose = TRUE )
docs |
Character vector of documents. |
encoders |
A single encoder (from |
embeddings |
A pre-computed numeric matrix (rows = documents, columns =
embedding dimensions), or a named list of such matrices when comparing
multiple embedding models. Names must match those in |
n_neighbors |
Integer vector of UMAP |
n_components |
Integer vector of UMAP |
min_pts |
Integer vector of HDBSCAN |
min_topics |
Minimum number of topics required in the |
ngram_range, top_n_terms, extra_stopwords
|
Fixed model parameters passed
to |
sample_size |
If not |
quality_top_n |
Passed to |
quality_sample |
Passed to |
quality_space |
Embedding space for quality metrics; passed to
|
seed |
Random seed for reproducibility. |
verbose |
Print one-line progress per combination. |
Embeddings are the expensive step. When sweeping only UMAP/HDBSCAN
parameters with a single encoder, pass pre-computed embeddings so
the encoder runs only once:
emb <- embed_texts(enc, docs, normalize = TRUE) sw <- sweep_topics(docs, embeddings = emb, min_pts = c(5, 10, 20))
To compare multiple encoders, pass a named list of either encoders or pre-computed matrices:
sw <- sweep_topics(docs,
embeddings = list(minilm = emb1, scibert = emb2),
n_neighbors = c(5, 15), min_pts = c(5, 10))
A list of class topic_sweep with elements:
resultsData frame with one row per parameter combination
and columns for all swept parameters plus quality metrics (including
n_topics).
bestThe selected row of results: highest silhouette
among runs satisfying min_topics, or the run with the most topics
if no run satisfies the constraint.
min_topicsThe min_topics argument (or NULL).
best_met_constraintLogical: TRUE when best
satisfies the min_topics constraint (always TRUE when
min_topics = NULL).
n_docsNumber of documents used (after optional sampling).
sampledLogical: whether a random sample was drawn.
param_namesCharacter vector of swept parameter names.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts, normalize = TRUE) sw <- sweep_topics(abstracts, embeddings = emb, n_neighbors = c(5L, 15L), min_pts = c(5L, 10L)) visualize_sweep(sw) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts, normalize = TRUE) sw <- sweep_topics(abstracts, embeddings = emb, n_neighbors = c(5L, 15L), min_pts = c(5L, 10L)) visualize_sweep(sw) ## End(Not run)
Measures how often the top terms of each topic co-occur in the same documents. High coherence means the words form a semantically tight cluster – they appear together rather than in isolation.
topic_coherence(fit, top_n = 10L, measure = c("npmi", "cv"))topic_coherence(fit, top_n = 10L, measure = c("npmi", "cv"))
fit |
A |
top_n |
Number of top c-TF-IDF terms per topic to include in pairwise co-occurrence calculations (default 10). |
measure |
Coherence measure: |
Two measures are supported:
"npmi"Normalised Pointwise Mutual Information, the standard
measure in recent topic-model benchmarks. Ranges from -1
(terms never co-occur) to 1 (terms always appear together).
Values above 0.1 are generally considered good; above 0.3
is excellent.
"cv"The C coherence measure, a log-conditional
variant that is slightly more discriminative on small corpora. Higher
is better; typical range -4 to 0.
A list of class topic_coherence with elements:
per_topicNamed numeric vector of per-topic scores.
meanMean coherence across all non-noise topics.
measureWhich measure was used.
top_nNumber of terms used.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) topic_coherence(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) topic_coherence(fit) ## End(Not run)
Computes four families of metrics that characterise different aspects of topic quality:
How tightly documents cluster around their topic centroid (mean cosine similarity of each document to its centroid; higher is better).
How distinct topics are from each other (pairwise cosine similarity between L2-normalised topic centroids; lower mean is better, i.e. topics point in different directions in embedding space).
How much vocabulary topics share (pairwise Jaccard
similarity of the top-top_n c-TF-IDF terms; lower mean is
better).
How balanced and noise-free the topic assignments are (normalised size entropy, coefficient of variation, noise ratio).
A silhouette score in the full embedding space is also computed. It is the standard cluster-quality measure: values near 1 mean documents sit close to their own centroid and far from the nearest other cluster; values near 0 or below indicate overlapping or mis-assigned topics.
topic_quality( fit, top_n = 10L, sample_size = 2000L, space = c("original", "reduced") )topic_quality( fit, top_n = 10L, sample_size = 2000L, space = c("original", "reduced") )
fit |
A |
top_n |
Number of top c-TF-IDF terms per topic used for the Jaccard overlap computation. Default 10. |
sample_size |
Maximum number of non-noise documents used when computing
the silhouette score (which requires an |
space |
Embedding space used for cohesion, separation, and silhouette.
|
A list of class topic_quality with elements:
cohesionList with global (scalar) and
per_topic (named vector) mean doc-to-centroid cosine similarity.
separationList with mean_inter_topic_similarity
(scalar) and centroid_similarity (symmetric matrix).
overlapList with mean_jaccard (scalar) and
jaccard_matrix (symmetric matrix of pairwise Jaccard scores).
distributionList with counts (named integer vector),
noise_ratio, entropy (normalised, in ), and
cv (coefficient of variation of topic sizes).
silhouetteList with global, per_topic,
sampled (logical), and sample_n.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) q <- topic_quality(fit) print(q) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) q <- topic_quality(fit) print(q) ## End(Not run)
For each unique timestamp (or time bin), the documents assigned to each topic are collected and a local c-TF-IDF representation is computed. Two optional smoothing passes mirror Python BERTopic's behaviour:
topics_over_time( fit, timestamps, nr_bins = NULL, evolution_tuning = TRUE, global_tuning = TRUE, top_n = NULL )topics_over_time( fit, timestamps, nr_bins = NULL, evolution_tuning = TRUE, global_tuning = TRUE, top_n = NULL )
fit |
A |
timestamps |
A vector of the same length as |
nr_bins |
Optional integer. Bin the timestamps into this many equally spaced intervals (using the per-bin median as the representative label). |
evolution_tuning |
Smooth representations across adjacent timestamps
(default |
global_tuning |
Blend each local representation with the global
c-TF-IDF (default |
top_n |
Number of top terms to include per (topic, timestamp) row.
Defaults to |
evolution_tuningAverages each timestamp's representation with the previous timestamp (L1-normalised) to smooth abrupt changes.
global_tuningAverages each local representation with the global c-TF-IDF (computed over all documents) so words that are absent from a narrow time window are not completely lost.
A data frame (class topics_over_time) with columns
Topic, Words, Frequency, Timestamp,
sorted by Timestamp then Topic.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) years <- as.Date(paste0(sample(2015:2023, length(abstracts), replace = TRUE), "-01-01")) tot <- topics_over_time(fit, timestamps = years) visualize_topics_over_time(tot, fit = fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) years <- as.Date(paste0(sample(2015:2023, length(abstracts), replace = TRUE), "-01-01")) tot <- topics_over_time(fit, timestamps = years) visualize_topics_over_time(tot, fit = fit) ## End(Not run)
Wraps predict.bertopic_fit; see that function for full
parameter documentation.
transform_bertopic(fit, new_docs, encoder = NULL, embeddings = NULL)transform_bertopic(fit, new_docs, encoder = NULL, embeddings = NULL)
fit |
A |
new_docs |
Character vector of new documents. |
encoder |
Optional encoder from |
embeddings |
Optional pre-computed embedding matrix. |
Same as predict.bertopic_fit.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) pred <- transform_bertopic(fit, new_docs = c("new document"), encoder = enc) pred$topics ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) pred <- transform_bertopic(fit, new_docs = c("new document"), encoder = enc) pred$topics ## End(Not run)
Wraps uwot::umap. This is the default dimensionality-reduction
model used by fit_bertopic.
umap_reduction( n_neighbors = 15L, n_components = 5L, min_dist = 0, metric = "cosine" )umap_reduction( n_neighbors = 15L, n_components = 5L, min_dist = 0, metric = "cosine" )
n_neighbors, n_components, min_dist, metric
|
Passed directly to
|
A umap_reduction model object.
m <- umap_reduction(n_neighbors = 10L, n_components = 3L)m <- umap_reduction(n_neighbors = 10L, n_components = 3L)
Produces a grid of horizontal bar charts (via plotly), one panel per
topic, showing c-TF-IDF scores for the top top_n terms. The best
term is always at the top of each panel.
visualize_barchart( fit, topics = NULL, top_n = 8L, n_cols = 4L, width = NULL, height = NULL )visualize_barchart( fit, topics = NULL, top_n = 8L, n_cols = 4L, width = NULL, height = NULL )
fit |
A |
topics |
Integer vector of topic IDs to include. |
top_n |
Number of terms to show per topic (default 8). |
n_cols |
Number of panel columns in the grid (default 4). |
width, height
|
Plot dimensions in pixels. Auto-scaled to the grid
size when |
A plotly figure object.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) visualize_barchart(fit, top_n = 5L) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) visualize_barchart(fit, top_n = 5L) ## End(Not run)
Displays the signed chi-square contributions or log ratios from
compare_topics as a diverging colour heatmap: blue = over-
represented in that group, red = under-represented.
visualize_comparison( comp, top_n_topics = NULL, max_label_chars = 25L, width = 1000L, height = 420L )visualize_comparison( comp, top_n_topics = NULL, max_label_chars = 25L, width = 1000L, height = 420L )
comp |
A |
top_n_topics |
Restrict to the |
max_label_chars |
Maximum characters for topic labels before truncation with an ellipsis (default 25). |
width, height
|
Plot dimensions in pixels (default 1000 x 420). |
Topics are placed on the x-axis (with angled labels) and groups on the y-axis, which keeps the chart readable even when topic labels are long.
A plotly figure.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) groups <- sample(c("A", "B"), length(abstracts), replace = TRUE) comp <- compare_topics(fit, groups) visualize_comparison(comp) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) groups <- sample(c("A", "B"), length(abstracts), replace = TRUE) comp <- compare_topics(fit, groups) visualize_comparison(comp) ## End(Not run)
Produces an interactive plotly dendrogram with:
Leaf labels: topic ID + top c-TF-IDF words (from fit)
Internal node hover: merged topics and their pooled terms
Height axis: cosine distance at which topics were merged
visualize_hierarchy( h, fit = NULL, n_label_words = 3L, width = 900L, height = 600L )visualize_hierarchy( h, fit = NULL, n_label_words = 3L, width = 900L, height = 600L )
h |
A |
fit |
Optional |
n_label_words |
Number of topic words to show per leaf label. |
width, height
|
Plot dimensions in pixels. |
A plotly figure object.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) h <- hierarchical_topics(fit) visualize_hierarchy(h, fit = fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) h <- hierarchical_topics(fit) visualize_hierarchy(h, fit = fit) ## End(Not run)
Produces a four-panel interactive dashboard (via plotly) from a
topic_quality object returned by topic_quality.
The panels are:
Silhouette per topic – horizontal bars coloured from red (negative) through yellow (zero) to green (positive).
Topic size distribution – document counts per topic, with the noise class shown separately.
Centroid similarity – pairwise cosine similarity matrix between topic centroids (lower = more distinct).
Vocabulary overlap – pairwise Jaccard similarity of the
top- c-TF-IDF term sets (lower = less overlap).
visualize_quality(q, fit = NULL, width = 900L, height = 750L)visualize_quality(q, fit = NULL, width = 900L, height = 750L)
q |
A |
fit |
Optional |
width, height
|
Plot dimensions in pixels (defaults: 900 x 750). |
A plotly figure object.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) q <- topic_quality(fit) visualize_quality(q, fit = fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) q <- topic_quality(fit) visualize_quality(q, fit = fit) ## End(Not run)
Interactive heatmap of pairwise ARI scores
visualize_stability(stab, width = 550L, height = 500L)visualize_stability(stab, width = 550L, height = 500L)
stab |
A |
width, height
|
Plot dimensions in pixels (default 550 x 500). |
A plotly figure.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts) stab <- stability_analysis(abstracts, emb, n_runs = 3L) visualize_stability(stab) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts) stab <- stability_analysis(abstracts, emb, n_runs = 3L) visualize_stability(stab) ## End(Not run)
Produces an interactive heatmap (via plotly) where each row is one
parameter combination and each column is a quality metric. Within every
column the values are min-max normalised to so that colours
are comparable across metrics (green = best in column, white = worst).
Metrics where a lower raw value is better (separation, Jaccard overlap,
noise percentage) are inverted before normalisation. Hover text shows the
actual raw values.
visualize_sweep( sweep, metrics = c("silhouette", "cohesion", "separation", "jaccard", "entropy", "noise_pct"), width = 900L, height = NULL )visualize_sweep( sweep, metrics = c("silhouette", "cohesion", "separation", "jaccard", "entropy", "noise_pct"), width = 900L, height = NULL )
sweep |
A |
metrics |
Character vector selecting which columns of
|
width, height
|
Plot dimensions in pixels. Height auto-scales to the
number of runs when |
n_topics is always shown as the first column. When a
min_topics constraint was passed to sweep_topics, rows
that did not meet it are prefixed with "[x] " in the row labels.
A plotly figure object.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts, normalize = TRUE) sw <- sweep_topics(abstracts, embeddings = emb, n_neighbors = c(5L, 15L), min_pts = c(5L, 10L)) visualize_sweep(sw) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") emb <- embed_texts(enc, abstracts, normalize = TRUE) sw <- sweep_topics(abstracts, embeddings = emb, n_neighbors = c(5L, 15L), min_pts = c(5L, 10L)) visualize_sweep(sw) ## End(Not run)
Produces an interactive Plotly Sankey diagram where each column represents
one time period, each node is a topic (sized by document count), and each
link is a cross-period topic similarity above the fitted threshold.
Link thickness scales with similarity x min(count_from, count_to).
visualize_topic_flow( flow, periods = NULL, color_by = c("period", "status"), width = 1100L, height = 650L )visualize_topic_flow( flow, periods = NULL, color_by = c("period", "status"), width = 1100L, height = 650L )
flow |
A |
periods |
Character vector of period labels to include. |
color_by |
One of |
width, height
|
Plot dimensions in pixels. |
A plotly figure.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") flow <- fit_topics_over_time(enc, docs = abstracts, period_var = rep(c("A", "B"), each = 50L)) visualize_topic_flow(flow) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") flow <- fit_topics_over_time(enc, docs = abstracts, period_var = rep(c("A", "B"), each = 50L)) visualize_topic_flow(flow) ## End(Not run)
Produces an interactive 2-D scatter plot (via plotly) with one point per document, coloured by topic.
visualize_topics( fit, dims = NULL, label_topics = TRUE, max_label_chars = 30L, point_size = 5L, noise_color = "#cccccc", width = 900L, height = 700L )visualize_topics( fit, dims = NULL, label_topics = TRUE, max_label_chars = 30L, point_size = 5L, noise_color = "#cccccc", width = 900L, height = 700L )
fit |
A |
dims |
Either |
label_topics |
Annotate topic centroids with short labels (default
|
max_label_chars |
Truncate centroid labels to this many characters. |
point_size |
Marker size (default 5). |
noise_color |
Hex colour for noise documents (default |
width, height
|
Plot dimensions in pixels (defaults: 900 x 700). |
A plotly figure object, or NULL if no topics were found.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) visualize_topics(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) visualize_topics(fit) ## End(Not run)
Produces an interactive Plotly line chart showing how the relative frequency of each topic changes across timestamps. Hovering over a point shows the topic's top words at that time.
visualize_topics_over_time( tot, topics = NULL, normalize = TRUE, width = 900L, height = 550L )visualize_topics_over_time( tot, topics = NULL, normalize = TRUE, width = 900L, height = 550L )
tot |
A |
topics |
Integer vector of topic IDs to include. |
normalize |
If |
width, height
|
Plot dimensions in pixels. |
A plotly figure.
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) years <- as.Date(paste0(sample(2015:2023, length(abstracts), replace = TRUE), "-01-01")) tot <- topics_over_time(fit, timestamps = years) visualize_topics_over_time(tot, fit = fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") fit <- fit_bertopic(docs = abstracts, encoder = enc) years <- as.Date(paste0(sample(2015:2023, length(abstracts), replace = TRUE), "-01-01")) tot <- topics_over_time(fit, timestamps = years) visualize_topics_over_time(tot, fit = fit) ## End(Not run)
Instead of discovering topics through unsupervised clustering, the user
supplies a named character vector of topic descriptions. Each description
is embedded and every document is assigned to the nearest topic by cosine
similarity. Documents whose best similarity falls below threshold
are labelled as noise (-1).
zero_shot_topics( docs, labels, embeddings = NULL, encoder = NULL, label_embeddings = NULL, threshold = 0, ngram_range = c(1L, 2L), top_n_terms = 10L, extra_stopwords = character(0L), verbose = TRUE )zero_shot_topics( docs, labels, embeddings = NULL, encoder = NULL, label_embeddings = NULL, threshold = 0, ngram_range = c(1L, 2L), top_n_terms = 10L, extra_stopwords = character(0L), verbose = TRUE )
docs |
Character vector of documents. |
labels |
Named character vector of topic descriptions. Names become
topic labels (e.g. |
embeddings |
Optional pre-computed numeric matrix for |
encoder |
Optional encoder from |
label_embeddings |
Optional pre-computed numeric matrix for the label
descriptions ( |
threshold |
Minimum cosine similarity for assignment. Documents below
this threshold are assigned to noise ( |
ngram_range |
Integer vector |
top_n_terms |
Number of c-TF-IDF terms stored per topic (default 10). |
extra_stopwords |
Additional stopwords – character vector, file path,
or data frame (same formats accepted by |
verbose |
Print progress messages (default |
The returned object is a full bertopic_fit compatible with all
Rhobots accessor and visualisation functions (get_topic_info(),
visualize_barchart(), topic_quality(), etc.).
A bertopic_fit object. The extra field
$label_names records the original user-supplied label names.
fit_bertopic, guided_fit_bertopic
## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") labels <- c( "Climate change" = "carbon emissions global warming climate", "Machine learning" = "neural network deep learning model training" ) fit <- zero_shot_topics(abstracts, labels = labels, encoder = enc) get_topic_info(fit) ## End(Not run)## Not run: enc <- load_hf_bert("sentence-transformers/all-MiniLM-L6-v2") labels <- c( "Climate change" = "carbon emissions global warming climate", "Machine learning" = "neural network deep learning model training" ) fit <- zero_shot_topics(abstracts, labels = labels, encoder = enc) get_topic_info(fit) ## End(Not run)