tmplot 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tmplot
3
- Version: 0.1.2
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
@@ -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:
@@ -38,6 +38,12 @@
38
38
  - LocallyLinearEmbedding
39
39
  - Isomap
40
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
+
41
47
  ## Installation
42
48
 
43
49
  The package can be installed from PyPi:
@@ -5,4 +5,4 @@ from ._stability import * # noqa: F401, F403
5
5
  from ._vis import * # noqa: F401, F403
6
6
  from ._metrics import * # noqa: F401, F403
7
7
 
8
- __version__ = '0.1.2'
8
+ __version__ = '0.1.3'
@@ -1,9 +1,14 @@
1
1
  __all__ = [
2
- 'get_phi', 'get_theta',
3
- 'get_relevant_terms', 'get_salient_terms',
4
- 'get_docs', 'get_top_docs',
5
- 'calc_terms_marg_probs', 'calc_topics_marg_probs',
6
- 'calc_terms_probs_ratio']
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
@@ -13,7 +18,7 @@ from numpy import ndarray, zeros, argsort, array, arange, vstack
13
18
  from numpy import log as nplog
14
19
  from pandas import concat, Series, DataFrame
15
20
 
16
- tomotopy_installed = find_spec('tomotopy')
21
+ tomotopy_installed = find_spec("tomotopy")
17
22
  if tomotopy_installed:
18
23
  from tomotopy import (
19
24
  LDAModel as tomotopyLDA,
@@ -23,14 +28,15 @@ if tomotopy_installed:
23
28
  HDPModel as tomotopyHDP,
24
29
  PTModel as tomotopyPT,
25
30
  SLDAModel as tomotopySLDA,
26
- GDMRModel as tomotopyGDMR)
31
+ GDMRModel as tomotopyGDMR,
32
+ )
27
33
 
28
- gensim_installed = find_spec('gensim')
34
+ gensim_installed = find_spec("gensim")
29
35
  if gensim_installed:
30
36
  from gensim.models.ldamodel import LdaModel as gensimLDA
31
37
  from gensim.models.ldamulticore import LdaMulticore as gensimLDAMC
32
38
 
33
- bitermplus_installed = find_spec('bitermplus')
39
+ bitermplus_installed = find_spec("bitermplus")
34
40
  if bitermplus_installed:
35
41
  from bitermplus._btm import BTM
36
42
 
@@ -38,12 +44,11 @@ if bitermplus_installed:
38
44
  def __warn_package_installation(package_name: str):
39
45
  warn(
40
46
  f'Please install "{package_name}" package to analyze its models.\n'
41
- f'Run `pip install {package_name}` in the console.')
47
+ f"Run `pip install {package_name}` in the console."
48
+ )
42
49
 
43
50
 
44
- def get_phi(
45
- model: object,
46
- vocabulary: Optional[Sequence] = None) -> DataFrame:
51
+ def get_phi(model: object, vocabulary: Optional[Sequence] = None) -> DataFrame:
47
52
  """Get words vs topics matrix (phi).
48
53
 
49
54
  Returns ``phi`` matrix of shape W x T, where W is the number of words,
@@ -65,7 +70,6 @@ def get_phi(
65
70
  phi = None
66
71
 
67
72
  if _is_tomotopy(model):
68
-
69
73
  # Topics vs words distributions
70
74
  twd = list(map(model.get_topic_word_dist, range(model.k)))
71
75
 
@@ -76,7 +80,6 @@ def get_phi(
76
80
  phi.index = list(model.used_vocabs)
77
81
 
78
82
  elif _is_gensim(model):
79
-
80
83
  phi = DataFrame(model.get_topics().T)
81
84
  if vocabulary:
82
85
  phi.index = vocabulary
@@ -85,8 +88,8 @@ def get_phi(
85
88
  phi = model.df_words_topics_
86
89
 
87
90
  if isinstance(phi, DataFrame):
88
- phi.index.name = 'words'
89
- phi.columns.name = 'topics'
91
+ phi.index.name = "words"
92
+ phi.columns.name = "topics"
90
93
 
91
94
  return phi
92
95
 
@@ -94,8 +97,15 @@ def get_phi(
94
97
  def _is_tomotopy(model: object) -> bool:
95
98
  if tomotopy_installed:
96
99
  tomotopy_models = [
97
- tomotopyLDA, tomotopyLLDA, tomotopyCT, tomotopyDMR, tomotopyHDP,
98
- tomotopyPT, tomotopySLDA, tomotopyGDMR]
100
+ tomotopyLDA,
101
+ tomotopyLLDA,
102
+ tomotopyCT,
103
+ tomotopyDMR,
104
+ tomotopyHDP,
105
+ tomotopyPT,
106
+ tomotopySLDA,
107
+ tomotopyGDMR,
108
+ ]
99
109
  return any(map(partial(isinstance, model), tomotopy_models))
100
110
 
101
111
  __warn_package_installation("tomotopy")
@@ -119,9 +129,7 @@ def _is_btmplus(model: object) -> bool:
119
129
  return False
120
130
 
121
131
 
122
- def get_theta(
123
- model: object,
124
- corpus: Optional[List] = None) -> DataFrame:
132
+ def get_theta(model: object, corpus: Optional[List] = None) -> Optional[DataFrame]:
125
133
  """Get topics vs documents (theta) matrix.
126
134
 
127
135
  Returns theta matrix of shape T x D, where T is the number of topics,
@@ -132,7 +140,7 @@ def get_theta(
132
140
  model : object
133
141
  Topic model instance.
134
142
  corpus : Optional[List], optional
135
- Corpus.
143
+ Corpus (must be specified for a `gensim` model).
136
144
 
137
145
  Returns
138
146
  -------
@@ -147,8 +155,7 @@ def get_theta(
147
155
 
148
156
  elif _is_gensim(model):
149
157
  if corpus is None:
150
- raise ValueError(
151
- '`corpus` must be supplied for a gensim model')
158
+ raise ValueError("`corpus` must be supplied for a gensim model")
152
159
  tdd = list(map(model.get_document_topics, corpus))
153
160
  theta = DataFrame(zeros((len(tdd), model.num_topics)))
154
161
  for doc_id, doc_topic in enumerate(tdd):
@@ -160,14 +167,13 @@ def get_theta(
160
167
  theta = DataFrame(model.matrix_topics_docs_)
161
168
 
162
169
  if isinstance(theta, DataFrame):
163
- theta.index.name = 'topics'
164
- theta.columns.name = 'docs'
170
+ theta.index.name = "topics"
171
+ theta.columns.name = "docs"
165
172
 
166
173
  return theta
167
174
 
168
175
 
169
- def get_docs(
170
- model: object) -> List[str]:
176
+ def get_docs(model: object) -> Optional[List[str]]:
171
177
  """Retrieve documents from topic model object.
172
178
 
173
179
  Parameters
@@ -183,19 +189,19 @@ def get_docs(
183
189
  if _is_tomotopy(model):
184
190
  docs_raw = map(lambda x: x.words, model.docs)
185
191
  return list(
186
- map(
187
- lambda doc: " ".join(map(lambda x: model.vocabs[x], doc)),
188
- docs_raw))
192
+ map(lambda doc: " ".join(map(lambda x: model.vocabs[x], doc)), docs_raw)
193
+ )
189
194
  return None
190
195
 
191
196
 
192
197
  def get_top_docs(
193
- docs: Sequence[str],
194
- model: object = None,
195
- theta: ndarray = None,
196
- corpus: Optional[List] = None,
197
- docs_num: int = 5,
198
- topics: Sequence[int] = None) -> DataFrame:
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:
199
205
  """Get top documents for all (or a selected) topic.
200
206
 
201
207
  Parameters
@@ -231,20 +237,19 @@ def get_top_docs(
231
237
 
232
238
  def _select_docs(docs, theta, topic_id: int):
233
239
  probs = theta[topic_id, :]
234
- idx = argsort(probs)[:-docs_num-1:-1]
240
+ idx = argsort(probs)[: -docs_num - 1 : -1]
235
241
  result = Series(list(map(lambda x: docs[x], idx)))
236
- result.name = f'topic{topic_id}'
242
+ result.name = f"topic{topic_id}"
237
243
  return result
238
244
 
239
245
  topics_num = theta.shape[0]
240
246
  topics_idx = arange(topics_num) if topics is None else topics
241
- return concat(
242
- 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)
243
248
 
244
249
 
245
250
  def calc_topics_marg_probs(
246
- theta: Union[DataFrame, ndarray],
247
- topic_id: int = None) -> Union[DataFrame, ndarray]:
251
+ theta: Union[DataFrame, ndarray], topic_id: Optional[int] = None
252
+ ) -> Union[DataFrame, ndarray]:
248
253
  """Calculate marginal topics probabilities.
249
254
 
250
255
  Parameters
@@ -269,8 +274,8 @@ def calc_topics_marg_probs(
269
274
 
270
275
 
271
276
  def calc_terms_marg_probs(
272
- phi: Union[ndarray, DataFrame],
273
- word_id: Optional[int] = None) -> Union[ndarray, Series]:
277
+ phi: Union[ndarray, DataFrame], word_id: Optional[int] = None
278
+ ) -> Union[ndarray, Series]:
274
279
  """Calculate marginal terms probabilities.
275
280
 
276
281
  Parameters
@@ -294,10 +299,7 @@ def calc_terms_marg_probs(
294
299
  return phi.sum(axis=1)
295
300
 
296
301
 
297
- def get_salient_terms(
298
- terms_freqs: ndarray,
299
- phi: ndarray,
300
- theta: ndarray) -> ndarray:
302
+ def get_salient_terms(terms_freqs: ndarray, phi: ndarray, theta: ndarray) -> ndarray:
301
303
  """Get salient terms.
302
304
 
303
305
  Calculated as:
@@ -324,12 +326,18 @@ def get_salient_terms(
324
326
  def _p_tw(phi, w, t):
325
327
  return phi[w, t] * p_t[t] / p_w[w]
326
328
 
327
- saliency = array((
328
- terms_freqs[w] * sum((
329
- _p_tw(phi, w, t) * log(_p_tw(phi, w, t) / p_t[t])
330
- for t in range(phi.shape[1])))
331
- for w in range(phi.shape[0])
332
- ))
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
+ )
333
341
  # saliency(term w) = frequency(w)
334
342
  # * [sum_t p(t | w) * log(p(t | w)/p(t))] for topics t
335
343
  # p(t | w) = p(w | t) * p(t) / p(w)
@@ -337,10 +345,8 @@ def get_salient_terms(
337
345
 
338
346
 
339
347
  def calc_terms_probs_ratio(
340
- phi: DataFrame,
341
- topic: int,
342
- terms_num: int = 30,
343
- lambda_: float = 0.6) -> DataFrame:
348
+ phi: DataFrame, topic: int, terms_num: int = 30, lambda_: float = 0.6
349
+ ) -> DataFrame:
344
350
  """Get terms conditional and marginal probabilities.
345
351
 
346
352
  Parameters
@@ -368,35 +374,38 @@ def calc_terms_probs_ratio(
368
374
  pandas.DataFrame
369
375
  Words conditional and marginal probabilities.
370
376
  """
371
- p_cond_name = 'Conditional term probability, p(w | t)'
372
- p_cond = phi.iloc[:, topic]\
373
- .rename(p_cond_name)\
374
- 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)
375
381
  else Series(phi[:, topic], name=p_cond_name)
382
+ )
376
383
 
377
- p_marg_name = 'Marginal term probability, p(w)'
378
- p_marg = phi.sum(axis=1)\
379
- .rename(p_marg_name)\
380
- 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)
381
388
  else Series(phi[:, topic], name=p_marg_name)
389
+ )
382
390
 
383
391
  terms_probs = concat((p_marg, p_cond), axis=1)
384
392
  relevant_idx = get_relevant_terms(phi, topic, lambda_).index
385
393
  terms_probs_slice = terms_probs.loc[relevant_idx].head(terms_num)
386
394
 
387
- return terms_probs_slice\
388
- .reset_index(drop=False)\
395
+ return (
396
+ terms_probs_slice.reset_index(drop=False)
389
397
  .melt(
390
398
  id_vars=[terms_probs_slice.index.name],
391
- var_name='Type',
392
- value_name='Probability')\
393
- .rename(columns={terms_probs_slice.index.name: 'Terms'})
399
+ var_name="Type",
400
+ value_name="Probability",
401
+ )
402
+ .rename(columns={terms_probs_slice.index.name: "Terms"})
403
+ )
394
404
 
395
405
 
396
406
  def get_relevant_terms(
397
- phi: Union[ndarray, DataFrame],
398
- topic: int,
399
- lambda_: float = 0.6) -> Series:
407
+ phi: Union[ndarray, DataFrame], topic: int, lambda_: float = 0.6
408
+ ) -> Series:
400
409
  """Select relevant terms.
401
410
 
402
411
  Parameters
@@ -422,11 +431,10 @@ def get_relevant_terms(
422
431
  pandas.Series
423
432
  Terms sorted by relevance (descendingly).
424
433
  """
425
- phi_topic = phi.iloc[:, topic]\
426
- if isinstance(phi, DataFrame)\
427
- else phi[:, topic]
434
+ phi_topic = phi.iloc[:, topic] if isinstance(phi, DataFrame) else phi[:, topic]
428
435
 
429
436
  # relevance = lambda * log(p(w | t)) + (1 - lambda) * log(p(w | t) / p(w))
430
- relevance = lambda_ * nplog(phi_topic)\
431
- + (1 - lambda_) * nplog(phi_topic / phi.sum(axis=1))
437
+ relevance = lambda_ * nplog(phi_topic) + (1 - lambda_) * nplog(
438
+ phi_topic / phi.sum(axis=1)
439
+ )
432
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.2
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
@@ -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