tmplot 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tmplot-0.1.1/src/tmplot.egg-info → tmplot-0.1.3}/PKG-INFO +9 -3
- {tmplot-0.1.1 → tmplot-0.1.3}/README.md +8 -1
- {tmplot-0.1.1 → tmplot-0.1.3}/pyproject.toml +1 -1
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot/__init__.py +1 -1
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot/_helpers.py +92 -82
- tmplot-0.1.3/src/tmplot/_report.py +334 -0
- {tmplot-0.1.1 → tmplot-0.1.3/src/tmplot.egg-info}/PKG-INFO +9 -3
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot.egg-info/requires.txt +0 -1
- tmplot-0.1.1/src/tmplot/_report.py +0 -309
- {tmplot-0.1.1 → tmplot-0.1.3}/LICENSE +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/MANIFEST.in +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/setup.cfg +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot/_distance.py +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot/_metrics.py +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot/_stability.py +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot/_vis.py +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot.egg-info/SOURCES.txt +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot.egg-info/dependency_links.txt +0 -0
- {tmplot-0.1.1 → tmplot-0.1.3}/src/tmplot.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tmplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Visualization of Topic Modeling Results
|
|
5
5
|
Author-email: Maksim Terpilovskii <maximtrp@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -50,7 +50,6 @@ Requires-Dist: ipywidgets
|
|
|
50
50
|
Requires-Dist: tqdm
|
|
51
51
|
Provides-Extra: test
|
|
52
52
|
Requires-Dist: pytest; extra == "test"
|
|
53
|
-
Requires-Dist: codecov; extra == "test"
|
|
54
53
|
Requires-Dist: coverage; extra == "test"
|
|
55
54
|
Provides-Extra: models
|
|
56
55
|
Requires-Dist: tomotopy>=0.8.0; extra == "models"
|
|
@@ -59,7 +58,8 @@ Requires-Dist: bitermplus; extra == "models"
|
|
|
59
58
|
|
|
60
59
|
# tmplot
|
|
61
60
|
|
|
62
|
-
[](https://app.codacy.com/gh/maximtrp/tmplot/coverage)
|
|
62
|
+
[](https://app.codacy.com/gh/maximtrp/tmplot)
|
|
63
63
|
[](https://github.com/maximtrp/tmplot/actions/workflows/python-package.yml)
|
|
64
64
|
[](https://tmplot.readthedocs.io/en/latest/?badge=latest)
|
|
65
65
|
[](https://pepy.tech/project/tmplot)
|
|
@@ -96,6 +96,12 @@ Requires-Dist: bitermplus; extra == "models"
|
|
|
96
96
|
- LocallyLinearEmbedding
|
|
97
97
|
- Isomap
|
|
98
98
|
|
|
99
|
+
## Donate
|
|
100
|
+
|
|
101
|
+
If you find this package useful, please consider donating any amount of money. This will help me spend more time on supporting open-source software.
|
|
102
|
+
|
|
103
|
+
<a href="https://www.buymeacoffee.com/maximtrp" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
104
|
+
|
|
99
105
|
## Installation
|
|
100
106
|
|
|
101
107
|
The package can be installed from PyPi:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# tmplot
|
|
2
2
|
|
|
3
|
-
[](https://app.codacy.com/gh/maximtrp/tmplot/coverage)
|
|
4
|
+
[](https://app.codacy.com/gh/maximtrp/tmplot)
|
|
4
5
|
[](https://github.com/maximtrp/tmplot/actions/workflows/python-package.yml)
|
|
5
6
|
[](https://tmplot.readthedocs.io/en/latest/?badge=latest)
|
|
6
7
|
[](https://pepy.tech/project/tmplot)
|
|
@@ -37,6 +38,12 @@
|
|
|
37
38
|
- LocallyLinearEmbedding
|
|
38
39
|
- Isomap
|
|
39
40
|
|
|
41
|
+
## Donate
|
|
42
|
+
|
|
43
|
+
If you find this package useful, please consider donating any amount of money. This will help me spend more time on supporting open-source software.
|
|
44
|
+
|
|
45
|
+
<a href="https://www.buymeacoffee.com/maximtrp" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
46
|
+
|
|
40
47
|
## Installation
|
|
41
48
|
|
|
42
49
|
The package can be installed from PyPi:
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
__all__ = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"get_phi",
|
|
3
|
+
"get_theta",
|
|
4
|
+
"get_relevant_terms",
|
|
5
|
+
"get_salient_terms",
|
|
6
|
+
"get_docs",
|
|
7
|
+
"get_top_docs",
|
|
8
|
+
"calc_terms_marg_probs",
|
|
9
|
+
"calc_topics_marg_probs",
|
|
10
|
+
"calc_terms_probs_ratio",
|
|
11
|
+
]
|
|
7
12
|
from warnings import warn
|
|
8
13
|
from importlib.util import find_spec
|
|
9
14
|
from typing import Union, Optional, Sequence, List
|
|
10
15
|
from functools import partial
|
|
11
16
|
from math import log
|
|
12
17
|
from numpy import ndarray, zeros, argsort, array, arange, vstack
|
|
18
|
+
from numpy import log as nplog
|
|
13
19
|
from pandas import concat, Series, DataFrame
|
|
14
20
|
|
|
15
|
-
tomotopy_installed = find_spec(
|
|
21
|
+
tomotopy_installed = find_spec("tomotopy")
|
|
16
22
|
if tomotopy_installed:
|
|
17
23
|
from tomotopy import (
|
|
18
24
|
LDAModel as tomotopyLDA,
|
|
@@ -22,14 +28,15 @@ if tomotopy_installed:
|
|
|
22
28
|
HDPModel as tomotopyHDP,
|
|
23
29
|
PTModel as tomotopyPT,
|
|
24
30
|
SLDAModel as tomotopySLDA,
|
|
25
|
-
GDMRModel as tomotopyGDMR
|
|
31
|
+
GDMRModel as tomotopyGDMR,
|
|
32
|
+
)
|
|
26
33
|
|
|
27
|
-
gensim_installed = find_spec(
|
|
34
|
+
gensim_installed = find_spec("gensim")
|
|
28
35
|
if gensim_installed:
|
|
29
36
|
from gensim.models.ldamodel import LdaModel as gensimLDA
|
|
30
37
|
from gensim.models.ldamulticore import LdaMulticore as gensimLDAMC
|
|
31
38
|
|
|
32
|
-
bitermplus_installed = find_spec(
|
|
39
|
+
bitermplus_installed = find_spec("bitermplus")
|
|
33
40
|
if bitermplus_installed:
|
|
34
41
|
from bitermplus._btm import BTM
|
|
35
42
|
|
|
@@ -37,12 +44,11 @@ if bitermplus_installed:
|
|
|
37
44
|
def __warn_package_installation(package_name: str):
|
|
38
45
|
warn(
|
|
39
46
|
f'Please install "{package_name}" package to analyze its models.\n'
|
|
40
|
-
f
|
|
47
|
+
f"Run `pip install {package_name}` in the console."
|
|
48
|
+
)
|
|
41
49
|
|
|
42
50
|
|
|
43
|
-
def get_phi(
|
|
44
|
-
model: object,
|
|
45
|
-
vocabulary: Optional[Sequence] = None) -> DataFrame:
|
|
51
|
+
def get_phi(model: object, vocabulary: Optional[Sequence] = None) -> DataFrame:
|
|
46
52
|
"""Get words vs topics matrix (phi).
|
|
47
53
|
|
|
48
54
|
Returns ``phi`` matrix of shape W x T, where W is the number of words,
|
|
@@ -64,7 +70,6 @@ def get_phi(
|
|
|
64
70
|
phi = None
|
|
65
71
|
|
|
66
72
|
if _is_tomotopy(model):
|
|
67
|
-
|
|
68
73
|
# Topics vs words distributions
|
|
69
74
|
twd = list(map(model.get_topic_word_dist, range(model.k)))
|
|
70
75
|
|
|
@@ -75,7 +80,6 @@ def get_phi(
|
|
|
75
80
|
phi.index = list(model.used_vocabs)
|
|
76
81
|
|
|
77
82
|
elif _is_gensim(model):
|
|
78
|
-
|
|
79
83
|
phi = DataFrame(model.get_topics().T)
|
|
80
84
|
if vocabulary:
|
|
81
85
|
phi.index = vocabulary
|
|
@@ -84,8 +88,8 @@ def get_phi(
|
|
|
84
88
|
phi = model.df_words_topics_
|
|
85
89
|
|
|
86
90
|
if isinstance(phi, DataFrame):
|
|
87
|
-
phi.index.name =
|
|
88
|
-
phi.columns.name =
|
|
91
|
+
phi.index.name = "words"
|
|
92
|
+
phi.columns.name = "topics"
|
|
89
93
|
|
|
90
94
|
return phi
|
|
91
95
|
|
|
@@ -93,8 +97,15 @@ def get_phi(
|
|
|
93
97
|
def _is_tomotopy(model: object) -> bool:
|
|
94
98
|
if tomotopy_installed:
|
|
95
99
|
tomotopy_models = [
|
|
96
|
-
tomotopyLDA,
|
|
97
|
-
|
|
100
|
+
tomotopyLDA,
|
|
101
|
+
tomotopyLLDA,
|
|
102
|
+
tomotopyCT,
|
|
103
|
+
tomotopyDMR,
|
|
104
|
+
tomotopyHDP,
|
|
105
|
+
tomotopyPT,
|
|
106
|
+
tomotopySLDA,
|
|
107
|
+
tomotopyGDMR,
|
|
108
|
+
]
|
|
98
109
|
return any(map(partial(isinstance, model), tomotopy_models))
|
|
99
110
|
|
|
100
111
|
__warn_package_installation("tomotopy")
|
|
@@ -118,9 +129,7 @@ def _is_btmplus(model: object) -> bool:
|
|
|
118
129
|
return False
|
|
119
130
|
|
|
120
131
|
|
|
121
|
-
def get_theta(
|
|
122
|
-
model: object,
|
|
123
|
-
corpus: Optional[List] = None) -> DataFrame:
|
|
132
|
+
def get_theta(model: object, corpus: Optional[List] = None) -> Optional[DataFrame]:
|
|
124
133
|
"""Get topics vs documents (theta) matrix.
|
|
125
134
|
|
|
126
135
|
Returns theta matrix of shape T x D, where T is the number of topics,
|
|
@@ -131,7 +140,7 @@ def get_theta(
|
|
|
131
140
|
model : object
|
|
132
141
|
Topic model instance.
|
|
133
142
|
corpus : Optional[List], optional
|
|
134
|
-
Corpus.
|
|
143
|
+
Corpus (must be specified for a `gensim` model).
|
|
135
144
|
|
|
136
145
|
Returns
|
|
137
146
|
-------
|
|
@@ -146,8 +155,7 @@ def get_theta(
|
|
|
146
155
|
|
|
147
156
|
elif _is_gensim(model):
|
|
148
157
|
if corpus is None:
|
|
149
|
-
raise ValueError(
|
|
150
|
-
'`corpus` must be supplied for a gensim model')
|
|
158
|
+
raise ValueError("`corpus` must be supplied for a gensim model")
|
|
151
159
|
tdd = list(map(model.get_document_topics, corpus))
|
|
152
160
|
theta = DataFrame(zeros((len(tdd), model.num_topics)))
|
|
153
161
|
for doc_id, doc_topic in enumerate(tdd):
|
|
@@ -159,14 +167,13 @@ def get_theta(
|
|
|
159
167
|
theta = DataFrame(model.matrix_topics_docs_)
|
|
160
168
|
|
|
161
169
|
if isinstance(theta, DataFrame):
|
|
162
|
-
theta.index.name =
|
|
163
|
-
theta.columns.name =
|
|
170
|
+
theta.index.name = "topics"
|
|
171
|
+
theta.columns.name = "docs"
|
|
164
172
|
|
|
165
173
|
return theta
|
|
166
174
|
|
|
167
175
|
|
|
168
|
-
def get_docs(
|
|
169
|
-
model: object) -> List[str]:
|
|
176
|
+
def get_docs(model: object) -> Optional[List[str]]:
|
|
170
177
|
"""Retrieve documents from topic model object.
|
|
171
178
|
|
|
172
179
|
Parameters
|
|
@@ -182,19 +189,19 @@ def get_docs(
|
|
|
182
189
|
if _is_tomotopy(model):
|
|
183
190
|
docs_raw = map(lambda x: x.words, model.docs)
|
|
184
191
|
return list(
|
|
185
|
-
map(
|
|
186
|
-
|
|
187
|
-
docs_raw))
|
|
192
|
+
map(lambda doc: " ".join(map(lambda x: model.vocabs[x], doc)), docs_raw)
|
|
193
|
+
)
|
|
188
194
|
return None
|
|
189
195
|
|
|
190
196
|
|
|
191
197
|
def get_top_docs(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
docs: Sequence[str],
|
|
199
|
+
model: object = None,
|
|
200
|
+
theta: Optional[ndarray] = None,
|
|
201
|
+
corpus: Optional[List] = None,
|
|
202
|
+
docs_num: int = 5,
|
|
203
|
+
topics: Optional[Sequence[int]] = None,
|
|
204
|
+
) -> DataFrame:
|
|
198
205
|
"""Get top documents for all (or a selected) topic.
|
|
199
206
|
|
|
200
207
|
Parameters
|
|
@@ -230,20 +237,19 @@ def get_top_docs(
|
|
|
230
237
|
|
|
231
238
|
def _select_docs(docs, theta, topic_id: int):
|
|
232
239
|
probs = theta[topic_id, :]
|
|
233
|
-
idx = argsort(probs)[
|
|
240
|
+
idx = argsort(probs)[: -docs_num - 1 : -1]
|
|
234
241
|
result = Series(list(map(lambda x: docs[x], idx)))
|
|
235
|
-
result.name = f
|
|
242
|
+
result.name = f"topic{topic_id}"
|
|
236
243
|
return result
|
|
237
244
|
|
|
238
245
|
topics_num = theta.shape[0]
|
|
239
246
|
topics_idx = arange(topics_num) if topics is None else topics
|
|
240
|
-
return concat(
|
|
241
|
-
map(lambda x: _select_docs(docs, theta, x), topics_idx), axis=1)
|
|
247
|
+
return concat(map(lambda x: _select_docs(docs, theta, x), topics_idx), axis=1)
|
|
242
248
|
|
|
243
249
|
|
|
244
250
|
def calc_topics_marg_probs(
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
theta: Union[DataFrame, ndarray], topic_id: Optional[int] = None
|
|
252
|
+
) -> Union[DataFrame, ndarray]:
|
|
247
253
|
"""Calculate marginal topics probabilities.
|
|
248
254
|
|
|
249
255
|
Parameters
|
|
@@ -268,8 +274,8 @@ def calc_topics_marg_probs(
|
|
|
268
274
|
|
|
269
275
|
|
|
270
276
|
def calc_terms_marg_probs(
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
phi: Union[ndarray, DataFrame], word_id: Optional[int] = None
|
|
278
|
+
) -> Union[ndarray, Series]:
|
|
273
279
|
"""Calculate marginal terms probabilities.
|
|
274
280
|
|
|
275
281
|
Parameters
|
|
@@ -293,10 +299,7 @@ def calc_terms_marg_probs(
|
|
|
293
299
|
return phi.sum(axis=1)
|
|
294
300
|
|
|
295
301
|
|
|
296
|
-
def get_salient_terms(
|
|
297
|
-
terms_freqs: ndarray,
|
|
298
|
-
phi: ndarray,
|
|
299
|
-
theta: ndarray) -> ndarray:
|
|
302
|
+
def get_salient_terms(terms_freqs: ndarray, phi: ndarray, theta: ndarray) -> ndarray:
|
|
300
303
|
"""Get salient terms.
|
|
301
304
|
|
|
302
305
|
Calculated as:
|
|
@@ -323,12 +326,18 @@ def get_salient_terms(
|
|
|
323
326
|
def _p_tw(phi, w, t):
|
|
324
327
|
return phi[w, t] * p_t[t] / p_w[w]
|
|
325
328
|
|
|
326
|
-
saliency = array(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
329
|
+
saliency = array(
|
|
330
|
+
(
|
|
331
|
+
terms_freqs[w]
|
|
332
|
+
* sum(
|
|
333
|
+
(
|
|
334
|
+
_p_tw(phi, w, t) * log(_p_tw(phi, w, t) / p_t[t])
|
|
335
|
+
for t in range(phi.shape[1])
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
for w in range(phi.shape[0])
|
|
339
|
+
)
|
|
340
|
+
)
|
|
332
341
|
# saliency(term w) = frequency(w)
|
|
333
342
|
# * [sum_t p(t | w) * log(p(t | w)/p(t))] for topics t
|
|
334
343
|
# p(t | w) = p(w | t) * p(t) / p(w)
|
|
@@ -336,10 +345,8 @@ def get_salient_terms(
|
|
|
336
345
|
|
|
337
346
|
|
|
338
347
|
def calc_terms_probs_ratio(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
terms_num: int = 30,
|
|
342
|
-
lambda_: float = 0.6) -> DataFrame:
|
|
348
|
+
phi: DataFrame, topic: int, terms_num: int = 30, lambda_: float = 0.6
|
|
349
|
+
) -> DataFrame:
|
|
343
350
|
"""Get terms conditional and marginal probabilities.
|
|
344
351
|
|
|
345
352
|
Parameters
|
|
@@ -367,35 +374,38 @@ def calc_terms_probs_ratio(
|
|
|
367
374
|
pandas.DataFrame
|
|
368
375
|
Words conditional and marginal probabilities.
|
|
369
376
|
"""
|
|
370
|
-
p_cond_name =
|
|
371
|
-
p_cond =
|
|
372
|
-
.rename(p_cond_name)
|
|
373
|
-
if isinstance(phi, DataFrame)
|
|
377
|
+
p_cond_name = "Conditional term probability, p(w | t)"
|
|
378
|
+
p_cond = (
|
|
379
|
+
phi.iloc[:, topic].rename(p_cond_name)
|
|
380
|
+
if isinstance(phi, DataFrame)
|
|
374
381
|
else Series(phi[:, topic], name=p_cond_name)
|
|
382
|
+
)
|
|
375
383
|
|
|
376
|
-
p_marg_name =
|
|
377
|
-
p_marg =
|
|
378
|
-
.rename(p_marg_name)
|
|
379
|
-
if isinstance(phi, DataFrame)
|
|
384
|
+
p_marg_name = "Marginal term probability, p(w)"
|
|
385
|
+
p_marg = (
|
|
386
|
+
phi.sum(axis=1).rename(p_marg_name)
|
|
387
|
+
if isinstance(phi, DataFrame)
|
|
380
388
|
else Series(phi[:, topic], name=p_marg_name)
|
|
389
|
+
)
|
|
381
390
|
|
|
382
391
|
terms_probs = concat((p_marg, p_cond), axis=1)
|
|
383
392
|
relevant_idx = get_relevant_terms(phi, topic, lambda_).index
|
|
384
393
|
terms_probs_slice = terms_probs.loc[relevant_idx].head(terms_num)
|
|
385
394
|
|
|
386
|
-
return
|
|
387
|
-
.reset_index(drop=False)
|
|
395
|
+
return (
|
|
396
|
+
terms_probs_slice.reset_index(drop=False)
|
|
388
397
|
.melt(
|
|
389
398
|
id_vars=[terms_probs_slice.index.name],
|
|
390
|
-
var_name=
|
|
391
|
-
value_name=
|
|
392
|
-
|
|
399
|
+
var_name="Type",
|
|
400
|
+
value_name="Probability",
|
|
401
|
+
)
|
|
402
|
+
.rename(columns={terms_probs_slice.index.name: "Terms"})
|
|
403
|
+
)
|
|
393
404
|
|
|
394
405
|
|
|
395
406
|
def get_relevant_terms(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
lambda_: float = 0.6) -> Series:
|
|
407
|
+
phi: Union[ndarray, DataFrame], topic: int, lambda_: float = 0.6
|
|
408
|
+
) -> Series:
|
|
399
409
|
"""Select relevant terms.
|
|
400
410
|
|
|
401
411
|
Parameters
|
|
@@ -421,10 +431,10 @@ def get_relevant_terms(
|
|
|
421
431
|
pandas.Series
|
|
422
432
|
Terms sorted by relevance (descendingly).
|
|
423
433
|
"""
|
|
424
|
-
phi_topic = phi.iloc[:, topic]
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
434
|
+
phi_topic = phi.iloc[:, topic] if isinstance(phi, DataFrame) else phi[:, topic]
|
|
435
|
+
|
|
436
|
+
# relevance = lambda * log(p(w | t)) + (1 - lambda) * log(p(w | t) / p(w))
|
|
437
|
+
relevance = lambda_ * nplog(phi_topic) + (1 - lambda_) * nplog(
|
|
438
|
+
phi_topic / phi.sum(axis=1)
|
|
439
|
+
)
|
|
430
440
|
return relevance.sort_values(ascending=False)
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
__all__ = ["prepare_coords", "report"]
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Dict, Optional, Sequence, List
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from IPython.display import display
|
|
6
|
+
from ipywidgets import widgets as wdg
|
|
7
|
+
from pandas import DataFrame
|
|
8
|
+
from ._distance import get_topics_dist, get_topics_scatter
|
|
9
|
+
from ._vis import plot_scatter_topics, plot_terms, plot_docs
|
|
10
|
+
from ._helpers import calc_terms_probs_ratio, get_phi, get_theta, get_top_docs
|
|
11
|
+
|
|
12
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
13
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def prepare_coords(
|
|
17
|
+
model: object,
|
|
18
|
+
labels: Optional[Sequence] = None,
|
|
19
|
+
corpus: Optional[List] = None,
|
|
20
|
+
dist_kws: Optional[Dict] = None,
|
|
21
|
+
scatter_kws: Optional[Dict] = None,
|
|
22
|
+
) -> DataFrame:
|
|
23
|
+
"""Prepare coordinates for topics scatter plot.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
model : object
|
|
28
|
+
Topic model instance.
|
|
29
|
+
labels : Optional[Sequence]
|
|
30
|
+
Topics labels.
|
|
31
|
+
corpus : Optional[List], optional
|
|
32
|
+
Corpus (must be specified for a `gensim` model).
|
|
33
|
+
dist_kws : dict, optional
|
|
34
|
+
Keyword arguments passed to :py:meth:`tmplot.get_topics_dist()`.
|
|
35
|
+
scatter_kws : dict, optional
|
|
36
|
+
Keyword arguments passed to :py:meth:`tmplot.get_topics_scatter()`.
|
|
37
|
+
"""
|
|
38
|
+
if not dist_kws:
|
|
39
|
+
dist_kws = {}
|
|
40
|
+
if not scatter_kws:
|
|
41
|
+
scatter_kws = {}
|
|
42
|
+
|
|
43
|
+
phi = get_phi(model)
|
|
44
|
+
theta = get_theta(model, corpus=corpus)
|
|
45
|
+
topics_dists = get_topics_dist(phi, **dist_kws)
|
|
46
|
+
topics_coords = get_topics_scatter(topics_dists, theta, **scatter_kws)
|
|
47
|
+
topics_coords["label"] = labels or theta.index
|
|
48
|
+
return topics_coords
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def report(
|
|
52
|
+
model: object,
|
|
53
|
+
docs: Sequence[str],
|
|
54
|
+
*,
|
|
55
|
+
topics_labels: Optional[Sequence[str]] = None,
|
|
56
|
+
corpus: Optional[List] = None,
|
|
57
|
+
layout: Optional[wdg.Layout] = None,
|
|
58
|
+
show_headers: bool = True,
|
|
59
|
+
show_docs: bool = True,
|
|
60
|
+
show_words: bool = True,
|
|
61
|
+
show_topics: bool = True,
|
|
62
|
+
topics_kws: Optional[dict] = None,
|
|
63
|
+
height: int = 500,
|
|
64
|
+
width: int = 300,
|
|
65
|
+
coords_kws: Optional[dict] = None,
|
|
66
|
+
words_kws: Optional[dict] = None,
|
|
67
|
+
docs_kws: Optional[dict] = None,
|
|
68
|
+
top_docs_kws: Optional[dict] = None,
|
|
69
|
+
) -> wdg.VBox:
|
|
70
|
+
"""Interactive report interface.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
model : object
|
|
75
|
+
Topic model instance.
|
|
76
|
+
docs : Sequence[str]
|
|
77
|
+
Documents.
|
|
78
|
+
topics_labels : Optional[Sequence[str]], optional
|
|
79
|
+
Topics labels.
|
|
80
|
+
corpus : Optional[List[str]], optional
|
|
81
|
+
Gensim corpus (must be specified if you are using a `gensim` model).
|
|
82
|
+
layout : wdg.Layout, optional
|
|
83
|
+
Interface layout instance.
|
|
84
|
+
show_headers : bool, optional
|
|
85
|
+
Show headers.
|
|
86
|
+
show_docs : bool, optional
|
|
87
|
+
Show documents widget.
|
|
88
|
+
show_words : bool, optional
|
|
89
|
+
Show words widget.
|
|
90
|
+
show_topics : bool, optional
|
|
91
|
+
Show topics scatter plot widget.
|
|
92
|
+
topics_kws : dict, optional
|
|
93
|
+
Keyword arguments passed to :py:meth:`tmplot.plot_scatter_topics()`.
|
|
94
|
+
coords_kws : dict, optional
|
|
95
|
+
Keyword arguments passed to :py:meth:`tmplot.prepare_coords()`.
|
|
96
|
+
words_kws : dict, optional
|
|
97
|
+
Keyword arguments passed to :py:meth:`tmplot.plot_terms()`.
|
|
98
|
+
docs_kws : dict, optional
|
|
99
|
+
Keyword arguments passed to :py:meth:`tmplot.plot_docs()`.
|
|
100
|
+
top_docs_kws : dict, optional
|
|
101
|
+
Keyword arguments passed to :py:meth:`tmplot.get_top_docs()`.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
ipywidgets.widgets.widget_box.VBox
|
|
106
|
+
Report interface as a VBox instance.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
_topics_kws = (
|
|
110
|
+
{"chart_kws": {"height": height, "width": width}}
|
|
111
|
+
if not topics_kws
|
|
112
|
+
else deepcopy(topics_kws)
|
|
113
|
+
)
|
|
114
|
+
_coords_kws = {"corpus": corpus} if not coords_kws else deepcopy(coords_kws)
|
|
115
|
+
_words_kws = (
|
|
116
|
+
{"chart_kws": {"height": height, "width": width}}
|
|
117
|
+
if not words_kws
|
|
118
|
+
else deepcopy(words_kws)
|
|
119
|
+
)
|
|
120
|
+
_top_docs_kws = {} if not docs_kws else deepcopy(top_docs_kws)
|
|
121
|
+
_docs_kws = {} if not docs_kws else deepcopy(docs_kws)
|
|
122
|
+
|
|
123
|
+
# Headers init
|
|
124
|
+
topics_header = (
|
|
125
|
+
wdg.HTML("<b>Intertopic distance plot</b>")
|
|
126
|
+
if show_headers and show_topics
|
|
127
|
+
else None
|
|
128
|
+
)
|
|
129
|
+
words_header = (
|
|
130
|
+
wdg.HTML("<b>Relevant words (terms)</b>")
|
|
131
|
+
if show_headers and show_words
|
|
132
|
+
else None
|
|
133
|
+
)
|
|
134
|
+
docs_header = (
|
|
135
|
+
wdg.HTML("<b>Top documents in a topic</b>")
|
|
136
|
+
if show_headers and show_docs
|
|
137
|
+
else None
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Layout init
|
|
141
|
+
grid_cols = " ".join(["1fr"] * sum([show_docs, show_words, show_topics]))
|
|
142
|
+
layout = (
|
|
143
|
+
wdg.Layout(
|
|
144
|
+
grid_template_columns=grid_cols,
|
|
145
|
+
# justify_items='center'
|
|
146
|
+
)
|
|
147
|
+
if not layout
|
|
148
|
+
else layout
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Children widgets list init
|
|
152
|
+
children = []
|
|
153
|
+
|
|
154
|
+
if "topics_coords" not in _topics_kws:
|
|
155
|
+
topics_coords = prepare_coords(model, **_coords_kws)
|
|
156
|
+
_topics_kws.update(
|
|
157
|
+
{
|
|
158
|
+
"topics_coords": topics_coords,
|
|
159
|
+
"label_col": "label",
|
|
160
|
+
"size_col": "size",
|
|
161
|
+
"topic": 0,
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if "terms_probs" not in _words_kws:
|
|
166
|
+
phi = get_phi(model)
|
|
167
|
+
terms_probs = calc_terms_probs_ratio(phi, topic=0)
|
|
168
|
+
_words_kws.update({"terms_probs": terms_probs})
|
|
169
|
+
|
|
170
|
+
if "docs" not in _docs_kws:
|
|
171
|
+
theta = get_theta(model, corpus=corpus).values
|
|
172
|
+
_top_docs_kws.update(
|
|
173
|
+
{"docs": docs, "theta": theta, "topics": [0], "docs_num": 2}
|
|
174
|
+
)
|
|
175
|
+
top_docs = get_top_docs(**_top_docs_kws)
|
|
176
|
+
top_docs.columns = [""]
|
|
177
|
+
_docs_kws.update({"docs": top_docs})
|
|
178
|
+
|
|
179
|
+
# Topic selection
|
|
180
|
+
def _on_select_topic(sel):
|
|
181
|
+
topic = sel['new']
|
|
182
|
+
|
|
183
|
+
if show_words:
|
|
184
|
+
words_plot_output.clear_output(wait=False)
|
|
185
|
+
with words_plot_output:
|
|
186
|
+
terms_probs = calc_terms_probs_ratio(
|
|
187
|
+
phi, topic=topic, lambda_=lambda_slider.value)
|
|
188
|
+
_words_kws.update({'terms_probs': terms_probs})
|
|
189
|
+
display(plot_terms(**_words_kws))
|
|
190
|
+
|
|
191
|
+
if show_topics:
|
|
192
|
+
topics_plot_output.clear_output(wait=False)
|
|
193
|
+
with topics_plot_output:
|
|
194
|
+
_topics_kws.update({'topic': topic})
|
|
195
|
+
display(plot_scatter_topics(**_topics_kws))
|
|
196
|
+
|
|
197
|
+
if show_docs:
|
|
198
|
+
docs_plot_output.clear_output(wait=False)
|
|
199
|
+
with docs_plot_output:
|
|
200
|
+
_top_docs_kws.update({'topics': [sel['new']]})
|
|
201
|
+
top_docs = get_top_docs(**_top_docs_kws)
|
|
202
|
+
top_docs.columns = ['']
|
|
203
|
+
_docs_kws.update({'docs': top_docs})
|
|
204
|
+
display(plot_docs(**_docs_kws))
|
|
205
|
+
|
|
206
|
+
topics_ids = list(range(len(_topics_kws["topics_coords"])))
|
|
207
|
+
topics_labels = topics_labels or topics_ids
|
|
208
|
+
select_topic = wdg.Dropdown(options=list(zip(topics_labels, topics_ids)), value=0)
|
|
209
|
+
select_topic.observe(_on_select_topic, names="value")
|
|
210
|
+
select_topic_header = wdg.HTML("<b>Select a topic</b>:")
|
|
211
|
+
select_topic_widget = wdg.HBox([select_topic_header, select_topic])
|
|
212
|
+
select_topic_wrapper = wdg.VBox(
|
|
213
|
+
[select_topic_widget], layout={"align_items": "center"}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Topics scatter
|
|
217
|
+
def _on_select_topics_method(names):
|
|
218
|
+
topics_plot_output.clear_output(wait=False)
|
|
219
|
+
with topics_plot_output:
|
|
220
|
+
_coords_kws.update({"scatter_kws": {"method": names["new"]}})
|
|
221
|
+
topics_coords = prepare_coords(model, **_coords_kws)
|
|
222
|
+
_topics_kws.update(
|
|
223
|
+
{"topics_coords": topics_coords, "topic": select_topic.value}
|
|
224
|
+
)
|
|
225
|
+
display(plot_scatter_topics(**_topics_kws))
|
|
226
|
+
|
|
227
|
+
if show_topics:
|
|
228
|
+
topics_plot_children = [topics_header] if show_headers else []
|
|
229
|
+
options_methods = [
|
|
230
|
+
("TSNE", "tsne"),
|
|
231
|
+
("Spectral Embedding", "sem"),
|
|
232
|
+
("MDS", "mds"),
|
|
233
|
+
("Locally Linear Embedding (Standard)", "lle"),
|
|
234
|
+
("Locally Linear Embedding (LTSA)", "ltsa"),
|
|
235
|
+
("Isomap", "isomap"),
|
|
236
|
+
]
|
|
237
|
+
topics_method_header = wdg.HTML("Select a method:")
|
|
238
|
+
topics_method = wdg.Dropdown(
|
|
239
|
+
options=options_methods,
|
|
240
|
+
value="tsne",
|
|
241
|
+
layout=wdg.Layout(width=f"{width/1.25}px"),
|
|
242
|
+
)
|
|
243
|
+
topics_method_widget = wdg.HBox([topics_method_header, topics_method])
|
|
244
|
+
topics_method.observe(_on_select_topics_method, names="value")
|
|
245
|
+
topics_plot_output = wdg.Output()
|
|
246
|
+
topics_plot = plot_scatter_topics(**_topics_kws)
|
|
247
|
+
topics_plot_output.append_display_data(topics_plot)
|
|
248
|
+
topics_plot_children.extend([topics_method_widget, topics_plot_output])
|
|
249
|
+
topics_widget = wdg.VBox(topics_plot_children, layout={"align_items": "center"})
|
|
250
|
+
children.append(topics_widget)
|
|
251
|
+
|
|
252
|
+
# Words
|
|
253
|
+
if show_words:
|
|
254
|
+
|
|
255
|
+
def _on_select_lambda(sel):
|
|
256
|
+
topic = select_topic.value
|
|
257
|
+
lambda_ = lambda_slider.value
|
|
258
|
+
words_plot_output.clear_output(wait=False)
|
|
259
|
+
with words_plot_output:
|
|
260
|
+
terms_probs = calc_terms_probs_ratio(phi, topic=topic, lambda_=lambda_)
|
|
261
|
+
_words_kws.update({"terms_probs": terms_probs})
|
|
262
|
+
display(plot_terms(**_words_kws))
|
|
263
|
+
|
|
264
|
+
lambda_slider = wdg.FloatSlider(
|
|
265
|
+
value=0.6,
|
|
266
|
+
min=0.0,
|
|
267
|
+
max=1.0,
|
|
268
|
+
step=0.01,
|
|
269
|
+
description="",
|
|
270
|
+
continuous_update=False,
|
|
271
|
+
orientation="horizontal",
|
|
272
|
+
readout=True,
|
|
273
|
+
readout_format=".2f",
|
|
274
|
+
layout=wdg.Layout(width=f"{width/1.25}px"),
|
|
275
|
+
)
|
|
276
|
+
lambda_slider.observe(_on_select_lambda, names="value")
|
|
277
|
+
lambda_slider_header = wdg.HTML("Lambda value:")
|
|
278
|
+
lambda_slider_widget = wdg.HBox([lambda_slider_header, lambda_slider])
|
|
279
|
+
words_plot = plot_terms(**_words_kws)
|
|
280
|
+
words_plot_output = wdg.Output()
|
|
281
|
+
words_plot_output.append_display_data(words_plot)
|
|
282
|
+
words_plot_children = (
|
|
283
|
+
[words_header, lambda_slider_widget]
|
|
284
|
+
if show_headers
|
|
285
|
+
else [lambda_slider_widget]
|
|
286
|
+
)
|
|
287
|
+
words_plot_children.append(words_plot_output)
|
|
288
|
+
words_widget = wdg.VBox(words_plot_children, layout={"align_items": "center"})
|
|
289
|
+
children.append(words_widget)
|
|
290
|
+
|
|
291
|
+
# Docs
|
|
292
|
+
if show_docs:
|
|
293
|
+
|
|
294
|
+
def _on_select_docs_num(_):
|
|
295
|
+
docs_num = docs_num_slider.value
|
|
296
|
+
docs_plot_output.clear_output(wait=False)
|
|
297
|
+
with docs_plot_output:
|
|
298
|
+
_top_docs_kws.update({"docs_num": docs_num})
|
|
299
|
+
top_docs = get_top_docs(**_top_docs_kws)
|
|
300
|
+
top_docs.columns = [""]
|
|
301
|
+
_docs_kws.update({"docs": top_docs})
|
|
302
|
+
display(plot_docs(**_docs_kws))
|
|
303
|
+
|
|
304
|
+
docs_num_slider = wdg.IntSlider(
|
|
305
|
+
value=2,
|
|
306
|
+
min=1,
|
|
307
|
+
max=100,
|
|
308
|
+
continuous_update=False,
|
|
309
|
+
orientation="horizontal",
|
|
310
|
+
readout=True,
|
|
311
|
+
readout_format="d",
|
|
312
|
+
layout=wdg.Layout(width=f"{width/1.25}px"),
|
|
313
|
+
)
|
|
314
|
+
docs_num_slider.observe(_on_select_docs_num, names="value")
|
|
315
|
+
docs_num_slider_header = wdg.HTML("Documents number:")
|
|
316
|
+
docs_num_slider_widget = wdg.HBox([docs_num_slider_header, docs_num_slider])
|
|
317
|
+
|
|
318
|
+
docs_plot = plot_docs(**_docs_kws)
|
|
319
|
+
docs_plot_output = wdg.Output()
|
|
320
|
+
docs_plot_output.append_display_data(docs_plot)
|
|
321
|
+
docs_plot_children = (
|
|
322
|
+
[docs_header, docs_num_slider_widget]
|
|
323
|
+
if show_headers
|
|
324
|
+
else [docs_num_slider_widget]
|
|
325
|
+
)
|
|
326
|
+
docs_plot_children.append(docs_plot_output)
|
|
327
|
+
docs_widget = wdg.VBox(docs_plot_children, layout={"align_items": "center"})
|
|
328
|
+
children.append(docs_widget)
|
|
329
|
+
|
|
330
|
+
grid_box = wdg.GridBox(children, layout=layout)
|
|
331
|
+
hr_line = wdg.HTML('<hr style="border: 0; border-bottom: 1px solid #aaa">')
|
|
332
|
+
app = wdg.VBox([select_topic_wrapper, hr_line, grid_box])
|
|
333
|
+
|
|
334
|
+
return app
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tmplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Visualization of Topic Modeling Results
|
|
5
5
|
Author-email: Maksim Terpilovskii <maximtrp@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -50,7 +50,6 @@ Requires-Dist: ipywidgets
|
|
|
50
50
|
Requires-Dist: tqdm
|
|
51
51
|
Provides-Extra: test
|
|
52
52
|
Requires-Dist: pytest; extra == "test"
|
|
53
|
-
Requires-Dist: codecov; extra == "test"
|
|
54
53
|
Requires-Dist: coverage; extra == "test"
|
|
55
54
|
Provides-Extra: models
|
|
56
55
|
Requires-Dist: tomotopy>=0.8.0; extra == "models"
|
|
@@ -59,7 +58,8 @@ Requires-Dist: bitermplus; extra == "models"
|
|
|
59
58
|
|
|
60
59
|
# tmplot
|
|
61
60
|
|
|
62
|
-
[](https://app.codacy.com/gh/maximtrp/tmplot/coverage)
|
|
62
|
+
[](https://app.codacy.com/gh/maximtrp/tmplot)
|
|
63
63
|
[](https://github.com/maximtrp/tmplot/actions/workflows/python-package.yml)
|
|
64
64
|
[](https://tmplot.readthedocs.io/en/latest/?badge=latest)
|
|
65
65
|
[](https://pepy.tech/project/tmplot)
|
|
@@ -96,6 +96,12 @@ Requires-Dist: bitermplus; extra == "models"
|
|
|
96
96
|
- LocallyLinearEmbedding
|
|
97
97
|
- Isomap
|
|
98
98
|
|
|
99
|
+
## Donate
|
|
100
|
+
|
|
101
|
+
If you find this package useful, please consider donating any amount of money. This will help me spend more time on supporting open-source software.
|
|
102
|
+
|
|
103
|
+
<a href="https://www.buymeacoffee.com/maximtrp" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
104
|
+
|
|
99
105
|
## Installation
|
|
100
106
|
|
|
101
107
|
The package can be installed from PyPi:
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
__all__ = ['prepare_coords', 'report']
|
|
2
|
-
import warnings
|
|
3
|
-
from typing import Optional, Sequence, List
|
|
4
|
-
from copy import deepcopy
|
|
5
|
-
from ipywidgets import widgets as wdg
|
|
6
|
-
from pandas import DataFrame
|
|
7
|
-
from ._distance import get_topics_dist, get_topics_scatter
|
|
8
|
-
from ._vis import plot_scatter_topics, plot_terms, plot_docs
|
|
9
|
-
from ._helpers import (
|
|
10
|
-
calc_terms_probs_ratio,
|
|
11
|
-
get_phi, get_theta,
|
|
12
|
-
get_top_docs)
|
|
13
|
-
|
|
14
|
-
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
15
|
-
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def prepare_coords(
|
|
19
|
-
model: object,
|
|
20
|
-
labels: Optional[Sequence] = None,
|
|
21
|
-
dist_kws: dict = None,
|
|
22
|
-
scatter_kws: dict = None) -> DataFrame:
|
|
23
|
-
"""Prepare coordinates for topics scatter plot.
|
|
24
|
-
|
|
25
|
-
Parameters
|
|
26
|
-
----------
|
|
27
|
-
model : object
|
|
28
|
-
Topic model instance.
|
|
29
|
-
labels : Optional[Sequence]
|
|
30
|
-
Topics labels.
|
|
31
|
-
dist_kws : dict, optional
|
|
32
|
-
Keyword arguments passed to :py:meth:`tmplot.get_topics_dist()`.
|
|
33
|
-
scatter_kws : dict, optional
|
|
34
|
-
Keyword arguments passed to :py:meth:`tmplot.get_topics_scatter()`.
|
|
35
|
-
"""
|
|
36
|
-
if not dist_kws:
|
|
37
|
-
dist_kws = {}
|
|
38
|
-
if not scatter_kws:
|
|
39
|
-
scatter_kws = {}
|
|
40
|
-
|
|
41
|
-
phi = get_phi(model)
|
|
42
|
-
theta = get_theta(model)
|
|
43
|
-
topics_dists = get_topics_dist(phi, **dist_kws)
|
|
44
|
-
topics_coords = get_topics_scatter(topics_dists, theta, **scatter_kws)
|
|
45
|
-
topics_coords['label'] = labels or theta.index
|
|
46
|
-
return topics_coords
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def report(
|
|
50
|
-
model: object,
|
|
51
|
-
docs: Sequence[str],
|
|
52
|
-
*,
|
|
53
|
-
topics_labels: Optional[Sequence[str]] = None,
|
|
54
|
-
corpus: Optional[List] = None,
|
|
55
|
-
layout: wdg.Layout = None,
|
|
56
|
-
show_headers: bool = True,
|
|
57
|
-
show_docs: bool = True,
|
|
58
|
-
show_words: bool = True,
|
|
59
|
-
show_topics: bool = True,
|
|
60
|
-
topics_kws: dict = None,
|
|
61
|
-
height: int = 500,
|
|
62
|
-
width: int = 300,
|
|
63
|
-
coords_kws: dict = None,
|
|
64
|
-
words_kws: dict = None,
|
|
65
|
-
docs_kws: dict = None,
|
|
66
|
-
top_docs_kws: dict = None) -> wdg.VBox:
|
|
67
|
-
"""Interactive report interface.
|
|
68
|
-
|
|
69
|
-
Parameters
|
|
70
|
-
----------
|
|
71
|
-
model : object
|
|
72
|
-
Topic model instance.
|
|
73
|
-
docs : Sequence[str]
|
|
74
|
-
Documents.
|
|
75
|
-
topics_labels : Optional[Sequence[str]], optional
|
|
76
|
-
Topics labels.
|
|
77
|
-
corpus : Optional[List[str]], optional
|
|
78
|
-
Gensim corpus.
|
|
79
|
-
layout : wdg.Layout, optional
|
|
80
|
-
Interface layout instance.
|
|
81
|
-
show_headers : bool, optional
|
|
82
|
-
Show headers.
|
|
83
|
-
show_docs : bool, optional
|
|
84
|
-
Show documents widget.
|
|
85
|
-
show_words : bool, optional
|
|
86
|
-
Show words widget.
|
|
87
|
-
show_topics : bool, optional
|
|
88
|
-
Show topics scatter plot widget.
|
|
89
|
-
topics_kws : dict, optional
|
|
90
|
-
Keyword arguments passed to :py:meth:`tmplot.plot_scatter_topics()`.
|
|
91
|
-
coords_kws : dict, optional
|
|
92
|
-
Keyword arguments passed to :py:meth:`tmplot.prepare_coords()`.
|
|
93
|
-
words_kws : dict, optional
|
|
94
|
-
Keyword arguments passed to :py:meth:`tmplot.plot_terms()`.
|
|
95
|
-
docs_kws : dict, optional
|
|
96
|
-
Keyword arguments passed to :py:meth:`tmplot.plot_docs()`.
|
|
97
|
-
top_docs_kws : dict, optional
|
|
98
|
-
Keyword arguments passed to :py:meth:`tmplot.get_top_docs()`.
|
|
99
|
-
|
|
100
|
-
Returns
|
|
101
|
-
-------
|
|
102
|
-
ipywidgets.widgets.widget_box.VBox
|
|
103
|
-
Report interface as a VBox instance.
|
|
104
|
-
"""
|
|
105
|
-
from IPython.display import display
|
|
106
|
-
|
|
107
|
-
_topics_kws = {
|
|
108
|
-
'chart_kws': {'height': height, 'width': width}}\
|
|
109
|
-
if not topics_kws else deepcopy(topics_kws)
|
|
110
|
-
_coords_kws = {} if not coords_kws else deepcopy(coords_kws)
|
|
111
|
-
_words_kws = {
|
|
112
|
-
'chart_kws': {'height': height, 'width': width}}\
|
|
113
|
-
if not words_kws else deepcopy(words_kws)
|
|
114
|
-
_top_docs_kws = {} if not docs_kws else deepcopy(top_docs_kws)
|
|
115
|
-
_docs_kws = {} if not docs_kws else deepcopy(docs_kws)
|
|
116
|
-
|
|
117
|
-
# Headers init
|
|
118
|
-
topics_header = wdg.HTML('<b>Intertopic distance plot</b>')\
|
|
119
|
-
if show_headers and show_topics else None
|
|
120
|
-
words_header = wdg.HTML('<b>Relevant words (terms)</b>')\
|
|
121
|
-
if show_headers and show_words else None
|
|
122
|
-
docs_header = wdg.HTML('<b>Top documents in a topic</b>')\
|
|
123
|
-
if show_headers and show_docs else None
|
|
124
|
-
|
|
125
|
-
# Layout init
|
|
126
|
-
grid_cols = " ".join(['1fr'] * sum([show_docs, show_words, show_topics]))
|
|
127
|
-
layout = wdg.Layout(
|
|
128
|
-
grid_template_columns=grid_cols,
|
|
129
|
-
# justify_items='center'
|
|
130
|
-
)\
|
|
131
|
-
if not layout else layout
|
|
132
|
-
|
|
133
|
-
# Children widgets list init
|
|
134
|
-
children = []
|
|
135
|
-
|
|
136
|
-
if 'topics_coords' not in _topics_kws:
|
|
137
|
-
topics_coords = prepare_coords(model, **_coords_kws)
|
|
138
|
-
_topics_kws.update({
|
|
139
|
-
'topics_coords': topics_coords,
|
|
140
|
-
'label_col': 'label',
|
|
141
|
-
'size_col': 'size',
|
|
142
|
-
'topic': 0
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
if 'terms_probs' not in _words_kws:
|
|
146
|
-
phi = get_phi(model)
|
|
147
|
-
terms_probs = calc_terms_probs_ratio(phi, topic=0)
|
|
148
|
-
_words_kws.update({'terms_probs': terms_probs})
|
|
149
|
-
|
|
150
|
-
if 'docs' not in _docs_kws:
|
|
151
|
-
theta = get_theta(model, corpus=corpus).values
|
|
152
|
-
_top_docs_kws.update({
|
|
153
|
-
'docs': docs, 'theta': theta,
|
|
154
|
-
'topics': [0], 'docs_num': 2})
|
|
155
|
-
top_docs = get_top_docs(**_top_docs_kws)
|
|
156
|
-
top_docs.columns = ['']
|
|
157
|
-
_docs_kws.update({'docs': top_docs})
|
|
158
|
-
|
|
159
|
-
# Topic selection
|
|
160
|
-
def _on_select_topic(sel):
|
|
161
|
-
topic = sel['new']
|
|
162
|
-
topics_plot_output.clear_output(wait=False)
|
|
163
|
-
words_plot_output.clear_output(wait=False)
|
|
164
|
-
docs_plot_output.clear_output(wait=False)
|
|
165
|
-
with words_plot_output:
|
|
166
|
-
terms_probs = calc_terms_probs_ratio(
|
|
167
|
-
phi, topic=topic, lambda_=lambda_slider.value)
|
|
168
|
-
_words_kws.update({'terms_probs': terms_probs})
|
|
169
|
-
display(plot_terms(**_words_kws))
|
|
170
|
-
with topics_plot_output:
|
|
171
|
-
_topics_kws.update({'topic': topic})
|
|
172
|
-
display(plot_scatter_topics(**_topics_kws))
|
|
173
|
-
with docs_plot_output:
|
|
174
|
-
_top_docs_kws.update({'topics': [sel['new']]})
|
|
175
|
-
top_docs = get_top_docs(**_top_docs_kws)
|
|
176
|
-
top_docs.columns = ['']
|
|
177
|
-
_docs_kws.update({'docs': top_docs})
|
|
178
|
-
display(plot_docs(**_docs_kws))
|
|
179
|
-
|
|
180
|
-
topics_ids = list(range(len(_topics_kws['topics_coords'])))
|
|
181
|
-
topics_labels = topics_labels or topics_ids
|
|
182
|
-
select_topic = wdg.Dropdown(
|
|
183
|
-
options=list(zip(topics_labels, topics_ids)), value=0)
|
|
184
|
-
select_topic.observe(_on_select_topic, names='value')
|
|
185
|
-
select_topic_header = wdg.HTML('<b>Select a topic</b>:')
|
|
186
|
-
select_topic_widget = wdg.HBox([select_topic_header, select_topic])
|
|
187
|
-
select_topic_wrapper = wdg.VBox(
|
|
188
|
-
[select_topic_widget], layout={'align_items': 'center'})
|
|
189
|
-
|
|
190
|
-
# Topics scatter
|
|
191
|
-
def _on_select_topics_method(names):
|
|
192
|
-
topics_plot_output.clear_output(wait=False)
|
|
193
|
-
with topics_plot_output:
|
|
194
|
-
_coords_kws.update({'scatter_kws': {'method': names['new']}})
|
|
195
|
-
topics_coords = prepare_coords(model, **_coords_kws)
|
|
196
|
-
_topics_kws.update({
|
|
197
|
-
'topics_coords': topics_coords,
|
|
198
|
-
'topic': select_topic.value
|
|
199
|
-
})
|
|
200
|
-
display(plot_scatter_topics(**_topics_kws))
|
|
201
|
-
|
|
202
|
-
if show_topics:
|
|
203
|
-
topics_plot_children = [topics_header] if show_headers else []
|
|
204
|
-
options_methods = [
|
|
205
|
-
('TSNE', 'tsne'),
|
|
206
|
-
('Spectral Embedding', 'sem'),
|
|
207
|
-
('MDS', 'mds'),
|
|
208
|
-
('Locally Linear Embedding (Standard)', 'lle'),
|
|
209
|
-
('Locally Linear Embedding (LTSA)', 'ltsa'),
|
|
210
|
-
('Isomap', 'isomap')
|
|
211
|
-
]
|
|
212
|
-
topics_method_header = wdg.HTML('Select a method:')
|
|
213
|
-
topics_method = wdg.Dropdown(
|
|
214
|
-
options=options_methods,
|
|
215
|
-
value='tsne',
|
|
216
|
-
layout=wdg.Layout(width=f'{width/1.25}px')
|
|
217
|
-
)
|
|
218
|
-
topics_method_widget = wdg.HBox([topics_method_header, topics_method])
|
|
219
|
-
topics_method.observe(_on_select_topics_method, names='value')
|
|
220
|
-
topics_plot_output = wdg.Output()
|
|
221
|
-
topics_plot = plot_scatter_topics(**_topics_kws)
|
|
222
|
-
topics_plot_output.append_display_data(topics_plot)
|
|
223
|
-
topics_plot_children.extend([topics_method_widget, topics_plot_output])
|
|
224
|
-
topics_widget = wdg.VBox(
|
|
225
|
-
topics_plot_children,
|
|
226
|
-
layout={'align_items': 'center'})
|
|
227
|
-
children.append(topics_widget)
|
|
228
|
-
|
|
229
|
-
# Words
|
|
230
|
-
if show_words:
|
|
231
|
-
def _on_select_lambda(sel):
|
|
232
|
-
topic = select_topic.value
|
|
233
|
-
lambda_ = lambda_slider.value
|
|
234
|
-
words_plot_output.clear_output(wait=False)
|
|
235
|
-
with words_plot_output:
|
|
236
|
-
terms_probs = calc_terms_probs_ratio(
|
|
237
|
-
phi, topic=topic, lambda_=lambda_)
|
|
238
|
-
_words_kws.update({'terms_probs': terms_probs})
|
|
239
|
-
display(plot_terms(**_words_kws))
|
|
240
|
-
|
|
241
|
-
lambda_slider = wdg.FloatSlider(
|
|
242
|
-
value=0.6,
|
|
243
|
-
min=0.0,
|
|
244
|
-
max=1.0,
|
|
245
|
-
step=0.01,
|
|
246
|
-
description='',
|
|
247
|
-
continuous_update=False,
|
|
248
|
-
orientation='horizontal',
|
|
249
|
-
readout=True,
|
|
250
|
-
readout_format='.2f',
|
|
251
|
-
layout=wdg.Layout(width=f'{width/1.25}px')
|
|
252
|
-
)
|
|
253
|
-
lambda_slider.observe(_on_select_lambda, names='value')
|
|
254
|
-
lambda_slider_header = wdg.HTML('Lambda value:')
|
|
255
|
-
lambda_slider_widget = wdg.HBox([lambda_slider_header, lambda_slider])
|
|
256
|
-
words_plot = plot_terms(**_words_kws)
|
|
257
|
-
words_plot_output = wdg.Output()
|
|
258
|
-
words_plot_output.append_display_data(words_plot)
|
|
259
|
-
words_plot_children = [words_header, lambda_slider_widget]\
|
|
260
|
-
if show_headers else [lambda_slider_widget]
|
|
261
|
-
words_plot_children.append(words_plot_output)
|
|
262
|
-
words_widget = wdg.VBox(
|
|
263
|
-
words_plot_children,
|
|
264
|
-
layout={'align_items': 'center'})
|
|
265
|
-
children.append(words_widget)
|
|
266
|
-
|
|
267
|
-
# Docs
|
|
268
|
-
if show_docs:
|
|
269
|
-
def _on_select_docs_num(_):
|
|
270
|
-
docs_num = docs_num_slider.value
|
|
271
|
-
docs_plot_output.clear_output(wait=False)
|
|
272
|
-
with docs_plot_output:
|
|
273
|
-
_top_docs_kws.update({'docs_num': docs_num})
|
|
274
|
-
top_docs = get_top_docs(**_top_docs_kws)
|
|
275
|
-
top_docs.columns = ['']
|
|
276
|
-
_docs_kws.update({'docs': top_docs})
|
|
277
|
-
display(plot_docs(**_docs_kws))
|
|
278
|
-
|
|
279
|
-
docs_num_slider = wdg.IntSlider(
|
|
280
|
-
value=2,
|
|
281
|
-
min=1,
|
|
282
|
-
max=100,
|
|
283
|
-
continuous_update=False,
|
|
284
|
-
orientation='horizontal',
|
|
285
|
-
readout=True,
|
|
286
|
-
readout_format='d',
|
|
287
|
-
layout=wdg.Layout(width=f'{width/1.25}px')
|
|
288
|
-
)
|
|
289
|
-
docs_num_slider.observe(_on_select_docs_num, names='value')
|
|
290
|
-
docs_num_slider_header = wdg.HTML('Documents number:')
|
|
291
|
-
docs_num_slider_widget = wdg.HBox(
|
|
292
|
-
[docs_num_slider_header, docs_num_slider])
|
|
293
|
-
|
|
294
|
-
docs_plot = plot_docs(**_docs_kws)
|
|
295
|
-
docs_plot_output = wdg.Output()
|
|
296
|
-
docs_plot_output.append_display_data(docs_plot)
|
|
297
|
-
docs_plot_children = [docs_header, docs_num_slider_widget]\
|
|
298
|
-
if show_headers else [docs_num_slider_widget]
|
|
299
|
-
docs_plot_children.append(docs_plot_output)
|
|
300
|
-
docs_widget = wdg.VBox(
|
|
301
|
-
docs_plot_children,
|
|
302
|
-
layout={'align_items': 'center'})
|
|
303
|
-
children.append(docs_widget)
|
|
304
|
-
|
|
305
|
-
grid_box = wdg.GridBox(children, layout=layout)
|
|
306
|
-
hr_line = wdg.HTML('<hr style="border: 0; border-bottom: 1px solid #aaa">')
|
|
307
|
-
app = wdg.VBox([select_topic_wrapper, hr_line, grid_box])
|
|
308
|
-
|
|
309
|
-
return app
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|