Content-covariate diagnostics¶
Diagnostics for the content-covariate models (STM, STS, SAGE), which learn a group-specific topic-word tensor \(\beta_{k,g,v}\) — how each topic is worded by each group. The global topic-word average hides that variation; these read it back out.
They answer a question that recurs with content models: does a group's
distinctive language land within a topic (one topic, worded differently by
group) or fragment into parallel, group-skewed topics? topic_polarization
measures the first; split_topics detects the second. The main lever that moves
a fit between the two is the STM content prior content_prior_var (looser prior →
more within-topic variation).
All read the group tensor through one adapter, so they work across model
families — STM via topic_word_by_group, SAGE via its 3-D topic_word. For an
STM fit with an ordered content_time covariate the group axis is the
base-by-period cross (labels "base@period"), which
content_trajectory / content_divergence read over ordered time. STS has a
continuous sentiment axis rather than discrete groups, so the adapter
discretizes it — evaluating
topic_word_at(level) at the sentiment poles -1/0/+1
(negative/neutral/positive) by default; pass levels= to choose your own.
topica.content.topic_polarization ¶
Per-topic Jensen-Shannon divergence across groups, normalized to [0, 1].
For each topic k, JSD(beta_{k,1}, ..., beta_{k,G}) -- the spread of its
group-specific wordings. 0 = every group words the topic identically;
1 = groups use disjoint vocabularies for it (maximal framing divergence).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
array(num_groups)
|
Group weights |
None
|
levels
|
mapping or sequence
|
STS only: sentiment levels to discretize into groups (see
:func: |
None
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(num_topics))
|
|
topica.content.group_exclusivity ¶
Group-adjusted exclusivity per topic (a FREX-tensor summary), in [0, 1].
Extends the usual exclusivity beta_{k,v} / sum_j beta_{j,v} to the group
tensor: excl_{k,g,v} = beta_{k,g,v} / sum_j sum_g' beta_{j,g',v}. For each
(k, g) it averages the exclusivity of that group's top-n words, then
reduces across groups by summary ("min" = worst-case group, the default;
"mean" = average). High = the topic stays distinctive in every group's
sub-vocabulary; low = at least one group's wording overlaps other topics.
levels (STS only) chooses the sentiment discretization (see
:func:group_topic_word).
Returns:
| Type | Description |
|---|---|
(ndarray, shape(num_topics))
|
|
topica.content.split_topics ¶
Near-duplicate topics pulled apart by group prevalence (fragmentation).
When a single discourse fragments into parallel group-topics instead of living within one topic, you get two topics that (a) share vocabulary -- high cosine between their group-averaged topic-word rows -- yet (b) are each dominated by a different group in the document-topic loadings. This flags those pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
sequence
|
The per-document content-group labels passed to |
required |
cos_thresh
|
float
|
Minimum cosine similarity of the two topics' word rows. |
0.6
|
skew_thresh
|
float
|
Minimum single-group share of a topic's mass for it to count as group-skewed. |
0.65
|
Returns:
| Type | Description |
|---|---|
list of dict, each ``{"pair": (k, j), "cosine": float, "skew": (float, float),
|
|
"groups": (label_k, label_j)}``.
|
|
topica.content.stratified_coherence ¶
stratified_coherence(model, texts, content, *, coherence_type='c_v', n=10, weights=None) -> np.ndarray
Group-stratified topic coherence for a content-covariate model.
Global coherence scores a topic's group-averaged words against the whole
corpus, which hides that a topic can be coherent for one group and gibberish
for another. This instead scores each group's own top words
(TopWords(beta_{k,g})) against that group's own subcorpus
(:math:\mathcal{D}_g, the documents with content == g), and averages
the per-group coherences weighted by group prevalence:
.. math:: \text{Coherence}(k) = \sum_g p(g)\, \text{Coherence}(\text{TopWords}(\beta_{k,g}), \mathcal{D}_g)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
Corpus, list of strings, or list of token lists
|
The training corpus, aligned row-for-row with |
required |
content
|
sequence
|
The per-document content-group labels passed to |
required |
coherence_type
|
one of ``"u_mass"``, ``"c_uci"``, ``"c_npmi"``, ``"c_v"``.
|
|
'c_v'
|
weights
|
array(num_groups)
|
Group weights |
None
|
Returns:
| Type | Description |
|---|---|
(ndarray, shape(num_topics))
|
|
topica.content.diagnostics ¶
One per-topic content-diagnostics table for a content-covariate model.
Consolidates the content metrics into a single row-per-topic table:
polarization (JSD across groups), group_exclusivity (worst-case group),
and -- when texts and content are supplied -- stratified_coherence.
Returns a pandas DataFrame if pandas is available, else a list of row
dicts.
topica.content.group_topic_word ¶
Normalize any content-covariate model to (beta_KGV, group_labels).
beta_KGV is (num_topics, num_groups, num_words) with each row a
probability distribution over words. Raises ValueError for a model with no
group-specific topic-word structure (not a content-covariate model, or fit
without a content covariate).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
levels
|
mapping or sequence
|
STS only: the sentiment levels to discretize its continuous content axis
into groups. A |
None
|
Wording over ordered time (STM content_time)¶
Fit STM with an ordered content_time= covariate and these readers trace how
two groups word a topic across time: the per-period, per-word contrast
p(w | g1) - p(w | g2), and the per-period whole-vocabulary distance between the
groups. Both take ci=True for a design-preserving bootstrap that resamples
documents (or whole cluster=s), refits, realigns the topic by anchor_words, and
returns percentile bands — intervals widen where the random walk is least
constrained (the first and last period).
from topica import content
stm = topica.STM(num_topics=20, seed=1)
stm.fit(docs, content=party, content_time=year, content_prior="l1")
tr = content.content_trajectory(stm, ["tax", "climate"],
groups=("Democrat", "Republican"), anchor_words=econ)
tr.to_frame() # word, period, estimate
dv = content.content_divergence(stm, groups=("Democrat", "Republican"),
anchor_words=econ, measure="hellinger",
ci=True, corpus=docs, fit_kwargs=fk) # + bootstrap CI
topica.content.content_trajectory ¶
content_trajectory(model, words, *, groups=None, topic=None, anchor_words=None, ci=False, corpus=None, fit_kwargs=None, cluster=None, B=50, level=0.95, seed=0)
How two content groups word a topic differently, across ordered time.
Reads the STM content_time surface (fit with STM.fit(..., content_time=))
for the per-period, per-word contrast p(w | group1, period) - p(w | group2,
period) on the target topic -- how the between-group wording contrast moves
over ordered time, read off the smoothed STM surface rather than independent
(group, period) cells.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
fitted STM (content_time)
|
Fit with an ordered |
required |
words
|
sequence[str]
|
Words to trace (silently dropped if out of vocabulary). |
required |
groups
|
(str, str)
|
The two base content groups to contrast. Defaults to the first two. |
None
|
topic
|
int
|
Target topic. Give this or |
None
|
anchor_words
|
sequence[str]
|
Identify the target topic by top-word overlap. Required when |
None
|
ci
|
bool
|
Attach a design-preserving bootstrap CI (needs |
False
|
corpus
|
Bootstrap controls. |
None
|
|
fit_kwargs
|
Bootstrap controls. |
None
|
|
cluster
|
Bootstrap controls. |
None
|
|
B
|
Bootstrap controls. |
None
|
|
level
|
Bootstrap controls. |
None
|
|
seed
|
Bootstrap controls. |
None
|
Returns:
| Type | Description |
|---|---|
ContentTrajectory
|
|
topica.content.content_divergence ¶
content_divergence(model, *, groups=None, topic=None, anchor_words=None, measure='hellinger', ci=False, corpus=None, fit_kwargs=None, cluster=None, B=50, level=0.95, seed=0)
Whole-vocabulary distance between two groups' wording of a topic, per period.
Reads the STM content_time surface for the per-period Hellinger (default) or
total-variation (measure="tv") distance between the two groups' topic-word
distributions -- the whole-distribution group distance per period, pooled over
the vocabulary so the aggregate has a usable bootstrap CI where single-word
contrasts do not. Parameters mirror :func:content_trajectory.
Returns:
| Type | Description |
|---|---|
ContentDivergence
|
|
Choosing K with group-stratified coherence¶
topica.search_k accepts a "stratified_<type>" coherence metric for
content models (model="stm" with a content covariate): it scores each group's
own top words against that group's subcorpus, and reports group-adjusted
exclusivity and mean polarization alongside.