STM toolkit¶
The structural / covariate operations live in topica.stm. The general
post-hoc diagnostics (labeling, alignment, pyLDAvis, …) are on the
Diagnostics page.
topica.standard_errors ¶
standard_errors(model, corpus=None, *, of='effect', method='composition', formula=None, data=None, X=None, feature_names=None, nsims=25, n_boot=200, topn=10, ci=0.95, seed=0, min_alignment=0.5, min_margin=0.1, model_factory=None, refit=None, **fit_kwargs)
Standard errors for the quantities people publish, with topic-estimation uncertainty propagated — one entry point across the model families (issue #15).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
a fitted topica model.
|
|
required |
corpus
|
the ``Corpus`` (or token lists) the model was fit on. Required for
|
|
None
|
of
|
``"effect"`` (covariate effects, needs ``formula``/``data`` or ``X``),
|
|
'effect'
|
method
|
``"composition"`` (default) draws theta from the model's posterior and
|
pools by Rubin's rules — cheap, no refit, sound for effects/prevalence on
STM/CTM/LDA/keyATM. |
'composition'
|
nsims
|
composition theta draws. n_boot : bootstrap resamples.
|
|
25
|
min_alignment
|
a bootstrap topic whose mean top-word Jaccard with the
|
reference falls below this is flagged |
0.5
|
min_margin
|
a topic is also flagged unreliable when its match is *ambiguous*
|
— the best-matching refit topic is less than |
0.1
|
Returns:
| Type | Description |
|---|---|
``of="effect"`` -> ``list[TopicEffect]`` (as :func:`estimate_effect`);
|
|
``of="prevalence"`` -> ``list[TopicPrevalence]``;
|
|
``of="top_words"`` -> ``list[TopWordUncertainty]``.
|
|
topica.stm.estimate_effect ¶
estimate_effect(doc_topic, X=None, *, data=None, formula=None, feature_names=None, topics=None, add_intercept=True, ci=0.95, cluster=None, weights=None, random=None, link='identity', corpus=None, nsims=None, seed=0)
Regress each topic's proportion on document covariates.
Pass a point estimate of θ for an ordinary OLS, or a stack of posterior
draws of θ for the method of composition — the uncertainty-propagating
procedure R stm uses (Treier & Jackman 2008). With draws, each one is
regressed and the results are pooled by Rubin's rules, so the reported
standard errors include the topic-estimation uncertainty, not just OLS
sampling error. Get draws with :func:posterior_theta_samples.
For paper-grade inference two extras matter:
cluster— a length-num_docsarray of group labels (e.g. speaker, user, outlet). Text data is almost always nested, and ignoring it understates uncertainty. Supplying it switches the standard errors to the cluster-robust (CR1) sandwich estimator. (With posterior draws, each draw is clustered and the per-draw covariances are then Rubin-pooled.)link—"identity"(default OLS),"logit"(fractional logit, via binomial quasi-likelihood), or"log"(quasi-Poisson). Because topic proportions live in[0, 1], the logit link keeps fitted values in bounds where OLS can wander outside them (Papke & Wooldridge). Non-identity links report heteroskedasticity- or cluster-robust standard errors.weights— a length-num_docsarray of (survey) weights, or a column name indata. Switches to weighted least squares: documents enter the regression in proportion to their weight, so a weighted sample (e.g. a survey-weighted corpus, or documents weighted by length) estimates the population-level effect. Composes withcluster(weighted cluster-robust SEs) and withlink. Matches faSTM's weightedestimateEffect.random— an lme4-style random-intercept term"(1 | group)"(withgroupa column ofdata). Fits a mixed model — the fixed-effect design plus a random intercept per group — by REML for each posterior draw, then Rubin-pools the fixed effects, matching faSTM'sestimateEffect(... ~ x + (1 | group)). Use it when documents are nested in units (state, outlet, author) whose baseline topic level varies: the random intercept soaks up that between-unit variation so the fixed-effect SEs are not understated. The estimated group and residual standard deviations are attached asTopicEffect.varcomp. Only a random intercept is supported (not random slopes), withlink="identity"and nocluster/weights.
Specifying the design. Give the covariates one of two ways: a prebuilt design
matrix as X (with feature_names), or an R-style formula together
with a data frame, which builds X for you via
:func:topica.design_matrix. Use the same design you fit the model with.
The effects regression is on the covariates you pass here, not on whatever
went into STM.fit; if they differ, the coefficients answer a different
question than the model. The reliable pattern is to build the design once and
pass the identical X (or the identical formula + data) to both
fit and estimate_effect.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_topic
|
array or fitted model
|
Either |
required |
X
|
array(num_docs, p)
|
Document covariates (design matrix); build nonlinear/interaction terms
with :func: |
None
|
feature_names
|
list[str]
|
Column names for |
None
|
data
|
DataFrame
|
Used with |
None
|
formula
|
str
|
R-style formula (e.g. |
None
|
topics
|
sequence[int]
|
Restrict to these topics. Defaults to all. |
None
|
ci
|
float
|
Confidence level for the (normal-approximation) intervals. |
0.95
|
Returns:
| Type | Description |
|---|---|
list[TopicEffect]
|
One regression per topic. For a tidy long table with one row per (topic, feature), concatenate the per-topic frames:: |
topica.stm.average_marginal_effects ¶
average_marginal_effects(doc_topic, covariate, *, formula, data, topics=None, h=None, ci=0.95, cluster=None, weights=None, corpus=None, nsims=None, seed=0, add_intercept=True)
Average marginal effects of a covariate on topic prevalence.
The average expected change in a topic's proportion per unit of covariate,
averaged over the observed documents. For a continuous covariate this is
the average numeric derivative (central difference); for a factor it is the
average contrast of each non-reference level against the reference level. This
is cleaner than reading raw regression coefficients, especially when the design
has splines or interactions, where no single coefficient is the effect (cf. the
margins package, and faSTM's ame()).
The marginal effect is computed on the identity (proportion) scale: each
topic's prevalence is regressed on the design via the method of composition
(the same path as :func:estimate_effect), and the averaged design-change
vector is contracted with the per-topic coefficient posterior, propagating
topic-estimation uncertainty into the standard error via the Rubin-pooled
coefficient covariance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_topic
|
array or fitted model
|
As in :func: |
required |
covariate
|
str
|
Column in |
required |
formula
|
str
|
R-style formula for the design (must reference |
required |
data
|
DataFrame
|
One row per document; the design is rebuilt on perturbed copies of it. |
required |
topics
|
sequence[int]
|
Restrict to these topics. Defaults to all. |
None
|
h
|
float
|
Step for the numeric derivative of a continuous covariate. Defaults to
|
None
|
ci
|
float
|
Confidence level for the (normal-approximation) intervals. |
0.95
|
cluster
|
optional
|
Passed through to :func: |
None
|
weights
|
optional
|
Passed through to :func: |
None
|
corpus
|
optional
|
As in :func: |
None
|
nsims
|
optional
|
As in :func: |
None
|
seed
|
optional
|
As in :func: |
None
|
add_intercept
|
optional
|
As in :func: |
None
|
Returns:
| Type | Description |
|---|---|
AverageMarginalEffects
|
Iterable of :class: |
topica.stm.MarginalEffect ¶
One average marginal effect: a (topic, covariate term) pair.
Produced by :func:average_marginal_effects. topic_name is the topic's
label. ame is the average expected change in the topic's proportion for the
covariate term (a unit change for a continuous covariate, a level-vs-reference
contrast for a factor), averaged over the observed documents. se is its
standard error and ci_low/ci_high the bounds of the confidence
interval.
__annotations__
class-attribute
¶
__annotations__ = {'topic': 'int', 'topic_name': 'str', 'term': 'str', 'ame': 'float', 'se': 'float', 'ci_low': 'float', 'ci_high': 'float'}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__dataclass_fields__
class-attribute
¶
__dataclass_fields__ = {'topic': Field(name='topic',type='int',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'topic_name': Field(name='topic_name',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'term': Field(name='term',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'ame': Field(name='ame',type='float',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'se': Field(name='se',type='float',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'ci_low': Field(name='ci_low',type='float',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'ci_high': Field(name='ci_high',type='float',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__doc__
class-attribute
¶
__doc__ = "One average marginal effect: a (topic, covariate term) pair.\n\n Produced by :func:`average_marginal_effects`. ``topic_name`` is the topic's\n label. ``ame`` is the average expected change in the topic's proportion for the\n covariate term (a unit change for a continuous covariate, a level-vs-reference\n contrast for a factor), averaged over the observed documents. ``se`` is its\n standard error and ``ci_low``/``ci_high`` the bounds of the confidence\n interval.\n "
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
__match_args__
class-attribute
¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
__module__
class-attribute
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
topica.stm.AverageMarginalEffects ¶
The full set of average marginal effects for one covariate.
Returned by :func:average_marginal_effects. Iterate .effects for the
per-(topic, term) :class:MarginalEffect rows, or call :meth:to_frame for a
tidy DataFrame.
__annotations__
class-attribute
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__dataclass_fields__
class-attribute
¶
__dataclass_fields__ = {'covariate': Field(name='covariate',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'effects': Field(name='effects',type='list',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__doc__
class-attribute
¶
__doc__ = 'The full set of average marginal effects for one covariate.\n\n Returned by :func:`average_marginal_effects`. Iterate ``.effects`` for the\n per-(topic, term) :class:`MarginalEffect` rows, or call :meth:`to_frame` for a\n tidy DataFrame.\n '
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
__match_args__
class-attribute
¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
__module__
class-attribute
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
topica.effects.dirichlet_theta_samples ¶
Draw nsims samples of the document-topic matrix θ for a Gibbs model.
A collapsed-Gibbs model's doc_topic is the posterior mean of each
document's θ given its token-topic assignments, where
θ_d ~ Dirichlet(α + n_d) and (α + n_d) = doc_topic_d · (N_d + Σα).
With the document length N_d we recover that Dirichlet and sample it, so the
draws carry each document's within-document estimation uncertainty. Feed the
result to :func:estimate_effect for method-of-composition standard errors on
a model that has no logistic-normal posterior of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_topic
|
array(num_docs, num_topics)
|
The fitted θ (rows sum to one), e.g. |
required |
doc_lengths
|
array(num_docs)
|
Tokens per document ( |
required |
nsims
|
int
|
Number of θ draws. |
25
|
seed
|
int
|
RNG seed. |
0
|
prior
|
float
|
Extra concentration added to every document (a flat pseudo-count |
0.0
|
Returns:
| Type | Description |
|---|---|
array(nsims, num_docs, num_topics)
|
Matches :func: |
topica.stm.posterior_theta_samples ¶
Draw nsims samples of the document-topic matrix θ from a fitted
:class:STM/:class:CTM's variational posterior.
Each document's logistic-normal posterior is η_d ~ N(λ_d, ν_d) (from
model.eta_mean / model.eta_cov); a draw of η is mapped through the
softmax (with the reference category fixed at 0) to a θ row. Feed the result
to :func:estimate_effect for method-of-composition uncertainty.
Returns an array of shape (nsims, num_docs, num_topics).
topica.effects.model_family ¶
Which method-of-composition theta sampler suits model.
"logistic_normal" for STM/CTM (a variational eta posterior),
"dirichlet" for the collapsed-Gibbs models (LDA, keyATM, SeededLDA, ...),
or "none" for models with no posterior over theta (the embedding models),
which need method="bootstrap".
topica.stm.spline ¶
Restricted (natural) cubic-spline basis for a covariate — the building
block for nonlinear prevalence terms like R stm's ~ s(day).
Uses Harrell's restricted-cubic-spline parameterization: df+1 knots (at
evenly spaced quantiles of x unless knots is given) yield df basis
columns whose first is the linear term. np.column_stack the result into
your design matrix and extend feature_names with the returned names.
Returns (basis (n, df), names).
topica.stm.interaction ¶
Interaction columns between two covariate blocks (all pairwise products of
their columns) — for terms like R stm's ~ treatment * party.
a, b are 1-D or 2-D arrays with the same number of rows. Returns
(products (n, ncols), names); np.column_stack into your design matrix.
Predicted prevalence¶
Compute predicted topic prevalence at chosen covariate values, with
simulation-based credible intervals — the model-agnostic counterpart of
R stm's plot.estimateEffect.
topica.predicted_prevalence ¶
predicted_prevalence(model, *, X=None, formula=None, data=None, feature_names=None, at=None, contrast=None, continuous=None, npoints=50, topics=None, link='identity', ci=0.95, nsims=25, n_sim=2000, corpus=None, seed=0, add_intercept=True)
Predicted topic prevalence at chosen covariate values, with simulation-based CIs.
This is the model-agnostic counterpart of R stm's plot.estimateEffect.
It works on any model whose document-topic matrix supports
:func:~topica.effects.composition_theta (STM, CTM, LDA, keyATM covariate,
DMR, SeededLDA, ...) because it regresses the composition-theta draws on the
design matrix — exactly as :func:estimate_effect does — and then pushes
coefficient posterior draws through the link at new covariate values rather
than reporting the coefficients themselves.
Three modes mirror stm's method argument:
at=(point grid) — a dict{covariate: value}or a small DataFrame of reference rows; returns predicted theta per topic per row, with CI.contrast=(difference) — two covariate settings, e.g.contrast={"party": ["D", "R"]}; returns the difference in predicted theta between the two settings per topic, with CI.continuous=(smooth curve) — a column name; sweeps the covariate over its observed range on anpoints-point grid, holding all other columns at their means. Spline terms informulaare evaluated with the training knots, not re-fit to the new grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
fitted topica model
|
Any model whose theta supports the composition method (Gibbs or logistic-normal). Pass the model itself; theta draws are generated internally. |
required |
X
|
array(num_docs, p)
|
Raw design matrix. Provide either |
None
|
formula
|
str
|
R-style formula, e.g. |
None
|
data
|
DataFrame
|
One row per document; required with |
None
|
feature_names
|
list[str]
|
Column names for |
None
|
at
|
dict or DataFrame
|
Reference covariate settings for point predictions. |
None
|
contrast
|
dict or 2 - tuple
|
Two covariate settings; the result is their difference. |
None
|
continuous
|
str
|
Column name to sweep over its observed range. |
None
|
npoints
|
int
|
Number of grid points for |
50
|
topics
|
list[int]
|
Restrict to these topics. Defaults to all. |
None
|
link
|
str
|
|
'identity'
|
ci
|
float
|
Confidence level for the simulation-based interval. Default 0.95. |
0.95
|
nsims
|
int
|
Composition theta draws for Rubin's-rules pooling. Default 25. |
25
|
n_sim
|
int
|
Number of coefficient posterior draws for the simulation CI. Default 2000. |
2000
|
corpus
|
Corpus or token lists
|
Required for Gibbs models that did not retain |
None
|
seed
|
int
|
RNG seed. |
0
|
add_intercept
|
bool
|
Prepend an intercept column to the design matrix. Default True. |
True
|
Returns:
| Type | Description |
|---|---|
list[PredictedPrevalence]
|
One object per topic (in |
topica.PredictedPrevalence ¶
Predicted topic prevalence at a covariate grid, with simulation-based CIs.
Produced by :func:predicted_prevalence. Each entry covers one topic across
all grid points (for at/continuous) or the contrast between two
settings (for contrast).
Attributes:
| Name | Type | Description |
|---|---|---|
topic |
int
|
Zero-based topic index. |
topic_name |
str
|
Human-readable label ( |
mode |
str
|
One of |
grid |
list
|
Reference covariate values: a list of dicts for |
estimate |
ndarray
|
Mean predicted prevalence (or contrast), one entry per grid point. |
ci_low |
ndarray
|
Lower bound of the |
ci_high |
ndarray
|
Upper bound. |
covariate |
str or None
|
For |
__annotations__
class-attribute
¶
__annotations__ = {'topic': 'int', 'topic_name': 'str', 'mode': 'str', 'grid': 'list', 'estimate': 'np.ndarray', 'ci_low': 'np.ndarray', 'ci_high': 'np.ndarray', 'covariate': 'str | None'}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__dataclass_fields__
class-attribute
¶
__dataclass_fields__ = {'topic': Field(name='topic',type='int',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'topic_name': Field(name='topic_name',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'mode': Field(name='mode',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'grid': Field(name='grid',type='list',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'estimate': Field(name='estimate',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'ci_low': Field(name='ci_low',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'ci_high': Field(name='ci_high',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'covariate': Field(name='covariate',type='str | None',default=None,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__doc__
class-attribute
¶
__doc__ = 'Predicted topic prevalence at a covariate grid, with simulation-based CIs.\n\n Produced by :func:`predicted_prevalence`. Each entry covers one topic across\n all grid points (for ``at``/``continuous``) or the contrast between two\n settings (for ``contrast``).\n\n Attributes\n ----------\n topic : int\n Zero-based topic index.\n topic_name : str\n Human-readable label (``topic_names`` from the model, or ``"topic_k"``).\n mode : str\n One of ``"at"``, ``"contrast"``, or ``"continuous"``.\n grid : list\n Reference covariate values: a list of dicts for ``at`` / ``continuous``\n (one per grid row), or ``[setting_a, setting_b]`` for ``contrast``.\n estimate : np.ndarray\n Mean predicted prevalence (or contrast), one entry per grid point.\n ci_low : np.ndarray\n Lower bound of the ``ci``-level simulation interval.\n ci_high : np.ndarray\n Upper bound.\n covariate : str or None\n For ``continuous``, the name of the swept covariate (convenient for\n plotting); ``None`` otherwise.\n '
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
__match_args__
class-attribute
¶
__match_args__ = ('topic', 'topic_name', 'mode', 'grid', 'estimate', 'ci_low', 'ci_high', 'covariate')
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
__module__
class-attribute
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
to_frame ¶
Return a tidy pandas DataFrame with one row per grid point.
Columns are topic, topic_name, any covariate column(s),
estimate, ci_low, and ci_high.
Permutation test¶
Distribution-free test of whether a binary prevalence covariate genuinely shifts topic prevalence, or whether the association could arise by chance.
topica.permutation_test ¶
permutation_test(model, corpus, covariate, *, n_perm=100, topics=None, topn=10, seed=0, model_factory=None, iters=None)
Permutation test for a binary prevalence covariate (R stm's permutationTest).
Assesses whether a binary document-level covariate genuinely shifts topic
prevalence, or whether an apparent association could arise by chance. Each
permutation randomly reassigns the covariate at its empirical rate, refits
the model from fresh starting values (passing the permuted covariate to the
model for covariate-aware families), aligns the refit topics to the
reference (using the Hungarian top-word matcher from
:func:~topica.validation._hungarian), and records the covariate's effect
on every topic. The observed effect for each topic is then compared to the
permutation null to compute a two-sided p-value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
a fitted topica model.
|
The reference fit. Its type is used to build each permutation refit
( |
required |
corpus
|
list of token lists or a ``Corpus``.
|
The documents the model was fit on. Each permutation refits on the same documents with a shuffled covariate. |
required |
covariate
|
array-like (num_docs,), binary (0/1 or True/False).
|
The binary prevalence covariate to test. Must have exactly two unique values; they are mapped to 0 and 1 in sorted order. |
required |
n_perm
|
int
|
Number of permutation refits. Higher values give more stable p-values; 100 is enough for a screening test, 500 for publication. |
100
|
topics
|
sequence of int
|
Restrict the output to these topic indices. Defaults to all topics. |
None
|
topn
|
int
|
Top-word count used for topic alignment across refits. |
10
|
seed
|
int
|
Master RNG seed. Permutation seeds are derived as |
0
|
model_factory
|
callable(seed) -> unfitted model
|
Override the default |
None
|
iters
|
int
|
Iterations for each permutation refit. When |
None
|
Returns:
| Type | Description |
|---|---|
list of :class:`PermutationResult`
|
One entry per topic (restricted to |
Notes
For covariate-aware models (STM, DMR, KeyATM) the permuted covariate is
passed directly to each refit so the null model is correctly specified
(matching R stm's permutationTest behaviour). For covariate-free
models (LDA, HDP, etc.) permuting the labels used only in the effect
statistic remains a valid null.
The p-value uses the (1 + count) / (1 + n_perm) convention, so it is
never exactly zero. Permutation statistics that are NaN (from unmatched
topics in variable-K refits such as HDP) are dropped from the null before
computing the p-value; the effective n is reduced accordingly.
topica.PermutationResult ¶
Result of :func:permutation_test for one topic.
Attributes:
| Name | Type | Description |
|---|---|---|
topic |
int
|
Topic index (aligned to the reference model). |
topic_name |
str
|
Topic label (or |
observed |
float
|
Observed difference in mean prevalence between the two covariate groups. |
null |
(ndarray, shape(n_perm))
|
Per-permutation covariate effects (the null distribution). |
pvalue |
float
|
Two-sided p-value: proportion of permutations whose absolute effect equals or exceeds the absolute observed effect. |
__annotations__
class-attribute
¶
__annotations__ = {'topic': 'int', 'topic_name': 'str', 'observed': 'float', 'null': 'np.ndarray', 'pvalue': 'float'}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__dataclass_fields__
class-attribute
¶
__dataclass_fields__ = {'topic': Field(name='topic',type='int',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'topic_name': Field(name='topic_name',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'observed': Field(name='observed',type='float',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'null': Field(name='null',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'pvalue': Field(name='pvalue',type='float',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__doc__
class-attribute
¶
__doc__ = 'Result of :func:`permutation_test` for one topic.\n\n Attributes\n ----------\n topic : int\n Topic index (aligned to the reference model).\n topic_name : str\n Topic label (or ``"topic_t"`` when no labels are set).\n observed : float\n Observed difference in mean prevalence between the two covariate groups.\n null : numpy.ndarray, shape (n_perm,)\n Per-permutation covariate effects (the null distribution).\n pvalue : float\n Two-sided p-value: proportion of permutations whose absolute effect\n equals or exceeds the absolute observed effect.\n '
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
__match_args__
class-attribute
¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
__module__
class-attribute
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
Per-group prevalence with credible bands¶
Model-neutral per-group topic prevalence with posterior credible intervals drawn from the model's retained MCMC theta draws (or the logistic-normal posterior for STM/CTM).
topica.prevalence_ci ¶
prevalence_ci(model, groups, *, ci=0.95, normalize=True, corpus=None, nsims=None, seed=0, labels=None)
Per-group topic prevalence with posterior credible bands.
Splits the documents by groups (one label per document) and, within each
group, reports the mean topic prevalence with an empirical credible interval
drawn from the model's posterior over theta. For each posterior draw and each
group we average theta over the documents in that group, giving a
(S, num_groups, num_topics) stack of per-draw group prevalences; the point
estimate is the posterior mean over draws and the band is the empirical
(1-ci)/2 and (1+ci)/2 quantiles.
This is the draws-based companion to :func:by_strata. by_strata widens a
descriptive interval by Rubin's rules (a normal approximation); prevalence_ci
reads the credible band straight off the posterior draws, which is what
keyATM's plot_timetrend does. It is model-neutral: the draws come from
:func:composition_theta, so it prefers a Gibbs model's retained MCMC
theta_draws (pass keep_theta_draws=True at fit) and otherwise falls
back to the Dirichlet approximation (Gibbs, needs corpus=) or the
logistic-normal posterior (STM/CTM). :func:topica.time_prevalence_ci is the
dynamic-keyATM wrapper, with groups the timestamps and labels fixed to
the model's time_labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
A fitted model with a posterior over theta (any Dirichlet or logistic-normal model). |
required | |
groups
|
One label per document; documents are pooled within each distinct value. |
required | |
ci
|
Credible-interval coverage (default 0.95 gives a 95 percent band). |
0.95
|
|
normalize
|
When |
True
|
|
corpus
|
The |
None
|
|
nsims
|
Number of posterior draws. |
None
|
|
seed
|
Seed for the draw sampler (used only on the resample / fallback paths). |
0
|
|
labels
|
Optional explicit ordering of the group labels (matched to |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Covariate-aware held-out inference¶
Infer topic proportions for new documents using a fitted STM's prevalence
model, setting the per-document prior from mu_d = X_d gamma.
topica.stm.transform ¶
Infer topic proportions for new documents, optionally using prevalence covariates.
When prevalence information is supplied the per-document prior mean is set
to mu_d = X_d @ gamma (where gamma = model.prevalence_effects),
which mirrors R stm's fitNewDocuments behavior. Without covariates
the covariate-free baseline prior learned at fit time is used, giving the
same result as model.transform(docs) directly.
The topic-word matrix used is always the marginal model.topic_word; a
content model's per-group beta is not applied here. Documents should first
be aligned to the fitted vocabulary with :func:align_corpus if the new
corpus may contain out-of-vocabulary tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
fitted STM
|
A fitted |
required |
docs
|
list[list[str]] or Corpus
|
Token lists (or a Corpus) for the new documents. |
required |
prevalence
|
array - like(num_docs, F)
|
Raw covariate matrix for the new documents, without the intercept
column. An intercept is prepended to match how |
None
|
data
|
DataFrame
|
Document-level DataFrame for the new documents. Required when
|
None
|
formula
|
str
|
R-style formula string (e.g. |
None
|
X
|
array - like(num_docs, p)
|
Pre-built design matrix without the intercept column. Alternative to
|
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Topic proportions, shape |
Map new token lists onto the fitted vocabulary before calling transform,
dropping any out-of-vocabulary tokens.
topica.align_corpus ¶
Restrict token lists to the fitted model's vocabulary before transform.
Each document in new_docs is filtered to keep only tokens that appear in
model.vocabulary. Tokens outside that vocabulary are silently dropped.
Documents that become empty after filtering are represented as empty lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
new_docs
|
list[list[str]]
|
Token lists for the new documents (one list per document). |
required |
model
|
fitted STM or CTM
|
A fitted model with a |
required |
Returns:
| Type | Description |
|---|---|
list[list[str]]
|
Aligned token lists ready to pass to |
Model selection at fixed K¶
Run multiple initializations at a fixed K and compare candidates on the
coherence-exclusivity frontier — the analogue of R stm's selectModel.
topica.select_model ¶
select_model(docs, K, *, runs=20, model='lda', prevalence=None, word_embeddings=None, vocabulary=None, doc_embeddings=None, iters=500, num_samples=3, sample_interval=10, seed=42, coherence_n=10, fraction=None, burn_in_iters=None)
Run N initializations at a fixed K and return the fitted candidates (stm's selectModel).
All runs models are fit from different random seeds. With
fraction set, the procedure uses two stages: a short burn-in
(burn_in_iters, defaulting to 20% of iters) followed by
full training of the top ceil(fraction * runs) models by their
objective (ELBO where the model has one, else log-likelihood, else
mean coherence). This mirrors stm's "run briefly, keep the best ~20%"
heuristic.
This is for models whose fit depends on the random seed — the ones that
scatter across local optima. ETM, ProdLDA, FASTopic,
CombinedTM, and ZeroShotTM all benefit. STM/CTM use a
deterministic spectral init, so every run is identical and multi-start buys
nothing — pick one of the stochastic models instead. (DTM is not selected
here: its topics are time-varying, so coherence/exclusivity are not a single
number; use DTM(init="spectral") for a deterministic fit.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
docs
|
training documents (``list[list[str]]`` or a ``Corpus``).
|
|
required |
K
|
number of topics for every run.
|
|
required |
runs
|
number of random initializations.
|
|
20
|
model
|
which model to fit. One of ``"lda"`` (default), ``"stm"``,
|
|
'lda'
|
prevalence
|
covariate design matrix; required when ``model="stm"``.
|
|
None
|
word_embeddings
|
``(vocab, dim)`` word-embedding matrix; required when
|
|
None
|
vocabulary
|
the word list aligning ``word_embeddings`` rows; required when
|
|
None
|
doc_embeddings
|
``(num_docs, dim)`` document-embedding matrix; required when
|
|
None
|
iters
|
full-training iterations per run (or per survivor when
|
|
500
|
num_samples
|
Gibbs samples per run (LDA only).
|
|
3
|
sample_interval
|
iterations between Gibbs samples (LDA only).
|
|
10
|
seed
|
base RNG seed; run ``r`` uses seed ``seed + r``.
|
|
42
|
coherence_n
|
top-word count for coherence and exclusivity.
|
|
10
|
fraction
|
if given (a float in ``(0, 1]``), keep only the top
|
|
None
|
burn_in_iters
|
burn-in length used for early discard; defaults to
|
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
class:`SelectModelResult` with ``models``, ``coherence``,
|
|
``exclusivity``, and ``run_seeds`` arrays of length equal to the
|
|
|
number of survivors (all ``runs`` when ``fraction`` is ``None``).
|
|
topica.SelectModelResult ¶
Result of :func:select_model.
Attributes:
| Name | Type | Description |
|---|---|---|
models |
list of N fitted models, one per run.
|
|
coherence |
array of shape ``(N,)`` — per-run mean UMass coherence.
|
|
exclusivity |
array of shape ``(N,)`` — per-run mean top-word exclusivity.
|
|
run_seeds |
array of shape ``(N,)`` — seed used for each run.
|
|
__annotations__
class-attribute
¶
__annotations__ = {'models': 'list', 'coherence': 'np.ndarray', 'exclusivity': 'np.ndarray', 'run_seeds': 'np.ndarray'}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__dataclass_fields__
class-attribute
¶
__dataclass_fields__ = {'models': Field(name='models',type='list',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'coherence': Field(name='coherence',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'exclusivity': Field(name='exclusivity',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'run_seeds': Field(name='run_seeds',type='np.ndarray',default=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f4a64521850>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
__doc__
class-attribute
¶
__doc__ = 'Result of :func:`select_model`.\n\n Attributes\n ----------\n models : list of N fitted models, one per run.\n coherence : array of shape ``(N,)`` — per-run mean UMass coherence.\n exclusivity : array of shape ``(N,)`` — per-run mean top-word exclusivity.\n run_seeds : array of shape ``(N,)`` — seed used for each run.\n '
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
__match_args__
class-attribute
¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
__module__
class-attribute
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
Visualize the coherence-versus-exclusivity scatter across candidate runs.
topica.plot_models ¶
Coherence-vs-exclusivity scatter for :func:select_model candidates (stm's plotModels).
Each point is one run. The upper-right corner is the best region:
both coherent (interpretable) and exclusive (distinctive). Use
this plot to pick a run from :func:select_model before fitting
your full analysis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
a :class:`SelectModelResult` returned by :func:`select_model`.
|
|
required |
ax
|
matplotlib ``Axes`` to draw on; a new figure is created if
|
|
None
|
label_runs
|
annotate each point with its run index; default
|
|
True
|
Returns:
| Type | Description |
|---|---|
The matplotlib ``Axes``.
|
|