Skip to contents

Performs deterministic document-level topic clustering with Sentence-BERT embeddings. Supply either a loaded `model` or a precomputed embedding matrix. Topics are summarized with representative documents and class-based TF-IDF terms. This is embedding-based topic discovery, not a probabilistic LDA model.

Usage

topics(
  text,
  n_topics,
  column = NULL,
  model = NULL,
  embeddings = NULL,
  batch_size = 32L,
  iter_max = 100L,
  n_terms = 10L,
  n_representatives = 5L,
  stop_words = default_stop_words(),
  min_term_frequency = 1L,
  min_token_length = 2L,
  weighting = c("ctfidf", "bm25"),
  reduce_frequent_words = FALSE,
  stem = FALSE,
  keep_embeddings = TRUE,
  seeds = NULL,
  seed_embeddings = NULL,
  fixed_seeds = FALSE,
  cores = 1L,
  numbers = c("keep", "remove"),
  roman_numerals = c("keep", "remove"),
  section_numbers = c("keep", "remove")
)

Arguments

text

Character vector containing one document per element.

n_topics

Number of semantic topics. Must be at least two.

column

When `text` is a data frame, the name of the column holding the documents to model. Every other column is carried into `$documents`, and rows whose text is missing, blank, or a bibliographic placeholder are dropped. Leave `NULL` for a character vector.

model

A loaded [sbert_model][load_model()], a pinned model name from [models()], or `NULL` for the default model. Ignored when `embeddings` are supplied.

embeddings

Optional numeric matrix with one row per document; when supplied, no model is loaded or used.

batch_size

Batch size passed to [encode()] when `model` is used.

iter_max

Maximum deterministic k-means iterations.

n_terms

Maximum class-based TF-IDF terms returned per topic.

n_representatives

Maximum representative documents per topic.

stop_words

Character vector excluded from topic terms. Use `character()` to disable stop-word filtering.

min_term_frequency

Minimum corpus-wide token frequency.

min_token_length

Minimum Unicode character length for a topic token.

weighting

Class-based term-weighting scheme. `"ctfidf"` (default) uses `tf * log(1 + A / f_x)`; `"bm25"` uses the BM25 inverse-frequency variant `tf * log(1 + (A - f_x + 0.5) / (f_x + 0.5))`, which more aggressively down-weights terms shared across topics (Mendonca and Figueira 2025).

reduce_frequent_words

Whether to square-root the within-topic term frequency before weighting, damping very frequent words.

stem

Whether to collapse inflected forms (for example `animals` and `animal`) onto a shared Porter stem before scoring, displaying the most frequent surface form of each stem. Requires the `SnowballC` package.

keep_embeddings

Whether to retain normalized document embeddings in the returned object.

seeds

Optional guided topics: a character vector (or list of character vectors, collapsed with spaces) of seed words or topic descriptions — one element per seeded topic. Seeds are embedded and become the first `length(seeds)` cluster centroids; remaining topics (up to `n_topics`) are initialized away from the seeds. Seeded topics keep their position — topic i is seed i, never reordered by size — and a named `seeds` vector names those topics' labels. With precomputed `embeddings`, supply `seed_embeddings` as well.

seed_embeddings

Optional matrix of seed embeddings (one row per seed, same dimension as the document embeddings); required when `seeds` is used together with precomputed `embeddings`.

fixed_seeds

When `TRUE`, seed centroids are frozen and documents are simply assigned to the nearest seed (zero-shot classification into the seeded topics; `n_topics` must equal the number of seeds, and topics may be empty). When `FALSE` (default), seeds only initialize the clustering and the data can move the centroids.

cores

Number of forked worker processes used to tokenize documents for term scoring. Default `1` (serial). Values above one use `parallel::mclapply` on Unix-alikes and fall back to serial on Windows or for small corpora; the tokenization — and therefore every result — is identical regardless of the count. Ignored when a prepared [topic_corpus()] is supplied (its tokens are already computed).

numbers

How to treat purely numeric tokens (years, counts) in topic terms. `"keep"` (default) keeps them; `"remove"` drops tokens made only of digits, while retaining alphanumerics such as `covid19`.

roman_numerals

How to treat Roman-numeral tokens (chapter, section, and list markers such as `ii`, `iv`, `xii`). `"keep"` (default) keeps them; `"remove"` drops canonical Roman numerals up to 100. The bound is deliberate: larger Roman numerals collide with common abbreviations that are also valid numerals (`ml`, `mm`, `cc`, `ci`, `cv`), which are always kept. A few small numerals that are also words (`iv`, `vi`, `xl`) are removed when this is on.

section_numbers

How to treat section, reference, and list numbering. `"keep"` (default) keeps it; `"remove"` strips, before tokenizing, both multi-level indices (`1.2.3`, `4.5.6.7` — three or more dot-separated groups) and enumeration or list markers (`1.`, `2.`, `figure 12.` — a standalone one- or two-digit number followed by a period). Decimals (`3.14`), four-digit years (`2020.`), hyphenated numbers (`covid-19.`), and larger counts are left untouched, so genuine values survive even when `numbers = "keep"`. These filter only the topic terms; the document text and its embedding are untouched. To clean the source text itself — list markers, reference noise, junk characters — before encoding, see [clean_corpus()].

Value

An object of class `sbert_topic_model` containing document assignments, topic summaries, ranked terms, representatives, centers, and clustering diagnostics.

Details

Topic labels are arbitrary cluster identifiers. Interpret them with the ranked `terms` and `representatives` tables. Term scores use the class-based TF-IDF weighting `tf * log(1 + A / f_x)`, where `tf` is the frequency of a term normalized within its topic, `f_x` is the term's frequency across all topics, and `A` is the mean topic length (a real number, matching Mendonca and Figueira (2025), Eq. 1). Set `weighting = "bm25"` and/or `reduce_frequent_words = TRUE` for the BM25 and square-root variants (their Eq. 2 to 4). The built-in tokenizer preserves Unicode alphanumeric tokens and internal apostrophes, but does not perform language-specific word segmentation for unspaced CJK text.

Examples

text <- c(
  "Cats chase mice", "Dogs chase balls",
  "Stocks and bonds trade", "Markets price shares"
)
embeddings <- rbind(c(1, 0), c(0.9, 0.1), c(0, 1), c(0.1, 0.9))
topics <- topics(text, 2, embeddings = embeddings)
topics$topics
#>   topic                   label n_documents proportion    withinss
#> 1     1    chase / balls / cats           2        0.5 0.006116265
#> 2     2 bonds / markets / price           2        0.5 0.006116265