Python implementation of the Structural Topic Model (STM), a port of the R stm package with a scikit-learn style API
Project description
stm — Structural Topic Model in Python
A Python port of the R stm package (Roberts, Stewart & Tingley) with a scikit-learn style API.
What is STM?
The Structural Topic Model (STM) is a logistic-normal topic model where document metadata ("prevalence covariates") shifts the prior mean of each document's topic proportions. Without covariates it reduces to the Correlated Topic Model (CTM). Estimation uses semi-collapsed variational EM, the same algorithm as the R stm() function.
Installation
pip install structural-topic-model
Quick Start
import numpy as np
from stm import StructuralTopicModel
# X: (n_docs, n_vocab) word count matrix (dense or scipy.sparse)
# covar: (n_docs, n_covariates) prevalence covariate matrix (intercept added automatically)
model = StructuralTopicModel(n_components=10, init="spectral")
model.fit(X, prevalence=covar)
model.theta_ # topic proportions of training docs (n_docs, K)
model.components_ # topic-word distributions (K, V), each row sums to 1
model.gamma_ # prevalence regression coefficients (1+P, K-1)
model.sigma_ # topic covariance matrix (K-1, K-1)
# Inference on new documents (cf. fitNewDocuments)
theta_new = model.transform(X_new, prevalence=covar_new)
# Top words per topic (cf. labelTopics)
model.top_words(n_words=10) # by probability
model.top_words(n_words=10, kind="frex") # FREX: frequency-exclusivity balance
Without covariates, the model is estimated as a CTM:
model = StructuralTopicModel(n_components=10).fit(X)
Content Covariates
Content covariates allow the vocabulary used within topics to vary by document category. Pass one categorical label per document:
model = StructuralTopicModel(n_components=10)
model.fit(X, prevalence=covar, content=party_labels)
model.aspect_components_ # per-level topic-word distributions (n_levels, K, V)
model.kappa_["params"] # sparse deviations from baseline (lasso-estimated)
model.content_levels_ # sorted unique levels
model.transform(X_new, prevalence=covar_new, content=labels_new)
Estimated via Distributed Poisson regression (equivalent to the R package's default kappa.prior="L1").
Covariate Effects (estimateEffect)
Regress topic proportions on covariates using method of composition, returning coefficients with measurement uncertainty:
from stm import estimate_effect
eff = estimate_effect(model, covar, uncertainty="Global", nsims=25)
tables = eff.summary() # {topic: structured array with estimate/std_error/t_value/p_value}
tables[0]["estimate"] # coefficients for topic 0 (first entry is intercept)
Automatic Topic Count (K=0 / Lee & Mimno 2014)
Passing n_components=0 lets the spectral initialization choose the number
of topics from the data (R's K=0). The row-normalized gram matrix is
projected to three dimensions with t-SNE and the vertices of its convex
hull become the anchor words, so the number of vertices is the topic count:
model = StructuralTopicModel(n_components=0, init="spectral").fit(X)
model.n_components_ # number of topics chosen automatically
model.components_ # (K, V)
n_components=0 is only available with init="spectral".
Topic Selection (searchK)
from stm import search_k
res = search_k(X, K_values=[5, 10, 15], prevalence=covar,
model_params={"max_iter": 100})
res["heldout"] # heldout log-likelihood (document completion)
res["residual"] # residual dispersion — Taddy (2012), closer to 1 is better
res["semcoh"] # semantic coherence
res["exclus"] # exclusivity
Diagnostics
from stm import topic_corr, semantic_coherence, exclusivity, check_residuals
tc = topic_corr(model, cutoff=0.01) # topic correlation graph (simple method)
tc.posadj # positive correlation adjacency matrix
semantic_coherence(model, X, M=10) # semantic coherence per topic
exclusivity(model, M=10) # exclusivity per topic
check_residuals(model, X) # residual dispersion test {dispersion, pvalue, df}
R–Python API Reference
| R | Python |
|---|---|
stm(docs, vocab, K, prevalence=~x, data=meta) |
StructuralTopicModel(n_components=K).fit(X, prevalence=design) |
init.type="Spectral" (recommended, default) |
init="spectral" (default) |
init.type="Random" |
init="random" |
K=0 (Lee & Mimno 2014, automatic topic count) |
n_components=0 (init="spectral") |
gamma.prior="Pooled" (default) |
implemented (automatic when covariates are present) |
sigma.prior |
sigma_prior |
emtol / max.em.its |
tol / max_iter |
model= (warm restart) |
warm_start=True |
content=~group (kappa.prior="L1", default) |
fit(X, content=labels) |
interactions |
content_interactions |
fitNewDocuments() |
transform() |
labelTopics() |
top_words() |
estimateEffect() / summary() |
estimate_effect() / .summary() |
searchK() |
search_k() |
make.heldout() / eval.heldout() |
make_heldout() / eval_heldout() |
topicCorr(method="simple") |
topic_corr() |
semanticCoherence() / exclusivity() / checkResiduals() |
semantic_coherence() / exclusivity() / check_residuals() |
$theta / $beta / $sigma / $mu$gamma |
theta_ / components_ / sigma_ / gamma_ |
$beta$logbeta (content model) |
aspect_components_ (probability scale) |
$beta$kappa |
kappa_ |
Differences from scikit-learn LDA
components_contains normalized probability distributions (sklearn LDA stores unnormalized pseudo-counts).- Covariates are passed via
fit(X, prevalence=...)/transform(X, prevalence=...). R formulas are not supported — encode categorical variables numerically beforehand (e.g.pandas.get_dummiesorpatsy). perplexity(X)is provided, computed from the variational lower bound (exp(-bound / n_tokens)); lower is better.warm_start=Trueenables continued learning across repeatedfit()calls (cf. R'smodel=argument).
Possible Future Extensions
The core of STM (estimation, prevalence/content covariates, effect estimation, diagnostics, topic-count selection) is implemented and validated for numerical agreement with the R package. The following R features are not yet covered; they are candidates to add on demand rather than a committed roadmap.
gamma.prior="L1"(prevalence-side glmnet mode)kappa.prior="Jeffreys"(legacy content estimation)fixedintercept=FALSE- LDA (collapsed Gibbs) initialization,
ngroupsmemoized inference estimateEffect()withuncertainty="Local", formula interface (pass pre-expanded basis matrices instead)topicCorr(method="huge"),selectModel(),permutationTest(), plot functions
Development
uv sync
uv run pytest tests/
References
- Roberts, M., Stewart, B., & Tingley, D. (2019). stm: An R Package for Structural Topic Models. Journal of Statistical Software, 91(2).
- Arora, S. et al. (2013). A Practical Algorithm for Topic Modeling with Provable Guarantees. ICML.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file structural_topic_model-1.0.0.tar.gz.
File metadata
- Download URL: structural_topic_model-1.0.0.tar.gz
- Upload date:
- Size: 38.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1024251e3e8c5d066513788641fc13753cde7d0f95a1162848dc8118ebc5d4e2
|
|
| MD5 |
fe950e2e76f669752b2f33b4e4a9382a
|
|
| BLAKE2b-256 |
8d87d29055cb6abbb688d842ae9847fbd34e4f3b91734b99a4f911afa6c63c34
|
File details
Details for the file structural_topic_model-1.0.0-py3-none-any.whl.
File metadata
- Download URL: structural_topic_model-1.0.0-py3-none-any.whl
- Upload date:
- Size: 30.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68f0990ab20d055e1e76eb80b95f4f68f9be49d8a02f839a508f12d1ae49962d
|
|
| MD5 |
f453aec4a66e737046d7349929f67ef0
|
|
| BLAKE2b-256 |
ca7a3031f6cdaab2dc4a710c81673708a4cbb60cd77e1e9c46cf12c22ec17b7b
|