tmplot 0.2.0__tar.gz → 0.3.0__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.2.0/src/tmplot.egg-info → tmplot-0.3.0}/PKG-INFO +88 -54
- tmplot-0.3.0/README.md +131 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/__init__.py +1 -1
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/_distance.py +22 -6
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/_helpers.py +40 -3
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/_report.py +7 -2
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/_stability.py +4 -1
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/_vis.py +16 -5
- {tmplot-0.2.0 → tmplot-0.3.0/src/tmplot.egg-info}/PKG-INFO +88 -54
- tmplot-0.2.0/README.md +0 -98
- {tmplot-0.2.0 → tmplot-0.3.0}/LICENSE +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/MANIFEST.in +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/pyproject.toml +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/setup.cfg +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot/_metrics.py +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot.egg-info/SOURCES.txt +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot.egg-info/dependency_links.txt +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot.egg-info/requires.txt +0 -0
- {tmplot-0.2.0 → tmplot-0.3.0}/src/tmplot.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tmplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Visualization of Topic Modeling Results
|
|
5
5
|
Author-email: Maksim Terpilovskii <maximtrp@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -55,6 +55,7 @@ Provides-Extra: models
|
|
|
55
55
|
Requires-Dist: tomotopy>=0.8.0; extra == "models"
|
|
56
56
|
Requires-Dist: gensim; extra == "models"
|
|
57
57
|
Requires-Dist: bitermplus; extra == "models"
|
|
58
|
+
Dynamic: license-file
|
|
58
59
|
|
|
59
60
|
# tmplot
|
|
60
61
|
|
|
@@ -66,35 +67,46 @@ Requires-Dist: bitermplus; extra == "models"
|
|
|
66
67
|
[](https://pypi.org/project/tmplot)
|
|
67
68
|
[](https://github.com/maximtrp/tmplot/issues)
|
|
68
69
|
|
|
69
|
-
**tmplot** is a Python package for
|
|
70
|
+
**tmplot** is a comprehensive Python package for **topic modeling analysis and visualization**. Built for data scientists and researchers, it provides powerful interactive reports and advanced analytics that extend beyond traditional LDAvis/pyLDAvis capabilities.
|
|
71
|
+
|
|
72
|
+
**Analyze** • **Visualize** • **Compare** multiple topic models with ease
|
|
70
73
|
|
|
71
74
|

|
|
72
75
|
|
|
73
|
-
## Features
|
|
76
|
+
## Key Features
|
|
77
|
+
|
|
78
|
+
### Interactive Visualization
|
|
79
|
+
|
|
80
|
+
- **Topic scatter plots** with customizable coordinates and sizing
|
|
81
|
+
- **Term probability charts** with relevance weighting
|
|
82
|
+
- **Document analysis** showing top documents per topic
|
|
83
|
+
- **Interactive reports** with real-time parameter adjustment
|
|
84
|
+
|
|
85
|
+
### Advanced Analytics
|
|
74
86
|
|
|
75
|
-
-
|
|
87
|
+
- **Topic stability analysis** across multiple model runs
|
|
88
|
+
- **Model comparison** with sophisticated distance metrics
|
|
89
|
+
- **Saliency calculations** for term importance
|
|
90
|
+
- **Entropy metrics** for model optimization
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
- [gensim](https://radimrehurek.com/gensim/): `LdaModel`, `LdaMulticore`
|
|
79
|
-
- [bitermplus](https://github.com/maximtrp/bitermplus): `BTM`
|
|
92
|
+
### Model Support
|
|
80
93
|
|
|
81
|
-
-
|
|
94
|
+
- **[tomotopy](https://bab2min.github.io/tomotopy/)**: `LDAModel`, `LLDAModel`, `CTModel`, `DMRModel`, `HDPModel`, `PTModel`, `SLDAModel`, `GDMRModel`
|
|
95
|
+
- **[gensim](https://radimrehurek.com/gensim/)**: `LdaModel`, `LdaMulticore`
|
|
96
|
+
- **[bitermplus](https://github.com/maximtrp/bitermplus)**: `BTM`
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
- Jenson-Shannon divergence
|
|
85
|
-
- Jeffrey's divergence
|
|
86
|
-
- Hellinger distance
|
|
87
|
-
- Bhattacharyya distance
|
|
88
|
-
- Total variation distance
|
|
89
|
-
- Jaccard inversed index
|
|
98
|
+
### Distance Metrics
|
|
90
99
|
|
|
91
|
-
-
|
|
100
|
+
- Kullback-Leibler (symmetric & non-symmetric)
|
|
101
|
+
- Jensen-Shannon divergence
|
|
102
|
+
- Jeffrey's divergence
|
|
103
|
+
- Hellinger & Bhattacharyya distances
|
|
104
|
+
- Total variation distance
|
|
105
|
+
- Jaccard index
|
|
92
106
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- LocallyLinearEmbedding
|
|
97
|
-
- Isomap
|
|
107
|
+
### Dimensionality Reduction
|
|
108
|
+
|
|
109
|
+
t-SNE, SpectralEmbedding, MDS, LocallyLinearEmbedding, Isomap
|
|
98
110
|
|
|
99
111
|
## Donate
|
|
100
112
|
|
|
@@ -102,55 +114,77 @@ If you find this package useful, please consider donating any amount of money. T
|
|
|
102
114
|
|
|
103
115
|
<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
116
|
|
|
105
|
-
##
|
|
117
|
+
## Quick Start
|
|
106
118
|
|
|
107
|
-
|
|
119
|
+
### Installation
|
|
108
120
|
|
|
109
121
|
```bash
|
|
122
|
+
# From PyPI (recommended)
|
|
110
123
|
pip install tmplot
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Or directly from this repository:
|
|
114
124
|
|
|
115
|
-
|
|
125
|
+
# Development version
|
|
116
126
|
pip install git+https://github.com/maximtrp/tmplot.git
|
|
117
127
|
```
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
### Basic Usage
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import tmplot as tmp
|
|
133
|
+
|
|
134
|
+
# Load your topic model and documents
|
|
135
|
+
model = your_fitted_model # tomotopy, gensim, or bitermplus
|
|
136
|
+
docs = your_documents
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- `scikit-learn`
|
|
124
|
-
- `pandas`
|
|
125
|
-
- `altair`
|
|
126
|
-
- `ipywidgets`
|
|
127
|
-
- `tomotopy`, `gensim`, and `bitermplus` (optional)
|
|
138
|
+
# Create interactive report
|
|
139
|
+
tmp.report(model, docs=docs)
|
|
128
140
|
|
|
129
|
-
|
|
141
|
+
# Or create individual visualizations
|
|
142
|
+
coords = tmp.prepare_coords(model)
|
|
143
|
+
tmp.plot_scatter_topics(coords, size_col='size')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Advanced Examples
|
|
147
|
+
|
|
148
|
+
### Get Stable Topics
|
|
130
149
|
|
|
131
150
|
```python
|
|
132
|
-
# Importing packages
|
|
133
151
|
import tmplot as tmp
|
|
134
|
-
import pickle as pkl
|
|
135
|
-
import pandas as pd
|
|
136
152
|
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
# Find stable topics across multiple models
|
|
154
|
+
models = [model1, model2, model3, model4]
|
|
155
|
+
closest_topics, distances = tmp.get_closest_topics(models)
|
|
156
|
+
stable_topics, stable_distances = tmp.get_stable_topics(closest_topics, distances)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Analyze Model
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Calculate entropy for model selection
|
|
163
|
+
entropy_score = tmp.entropy(phi_matrix)
|
|
140
164
|
|
|
141
|
-
#
|
|
142
|
-
|
|
165
|
+
# Analyze topic stability
|
|
166
|
+
saliency = tmp.get_salient_terms(phi, theta)
|
|
167
|
+
```
|
|
143
168
|
|
|
144
|
-
|
|
145
|
-
topics_coords = tmp.prepare_coords(model)
|
|
146
|
-
tmp.plot_scatter_topics(topics_coords, size_col='size', label_col='label')
|
|
169
|
+
### Visualize
|
|
147
170
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
tmp.
|
|
171
|
+
```python
|
|
172
|
+
# Create topic distance matrix with different metrics
|
|
173
|
+
topic_dists = tmp.get_topics_dist(phi, method='jensen-shannon')
|
|
151
174
|
|
|
152
|
-
#
|
|
153
|
-
tmp.
|
|
175
|
+
# Generate coordinates with custom algorithm
|
|
176
|
+
coords = tmp.get_topics_scatter(topic_dists, theta, method='tsne')
|
|
177
|
+
tmp.plot_scatter_topics(coords, topic=3) # Highlight topic 3
|
|
154
178
|
```
|
|
155
179
|
|
|
156
|
-
|
|
180
|
+
## Documentation & Examples
|
|
181
|
+
|
|
182
|
+
- [Complete Tutorial](https://tmplot.readthedocs.io/en/latest/tutorial.html) - Step-by-step guide
|
|
183
|
+
- [API Reference](https://tmplot.readthedocs.io/) - Full documentation
|
|
184
|
+
- [Example Notebooks](https://github.com/maximtrp/tmplot/tree/main/examples) - Jupyter examples
|
|
185
|
+
|
|
186
|
+
## Requirements
|
|
187
|
+
|
|
188
|
+
**Core dependencies:** `numpy`, `scipy`, `scikit-learn`, `pandas`, `altair`, `ipywidgets`
|
|
189
|
+
|
|
190
|
+
**Optional models:** `tomotopy`, `gensim`, `bitermplus`
|
tmplot-0.3.0/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# tmplot
|
|
2
|
+
|
|
3
|
+
[](https://app.codacy.com/gh/maximtrp/tmplot/coverage)
|
|
4
|
+
[](https://app.codacy.com/gh/maximtrp/tmplot)
|
|
5
|
+
[](https://github.com/maximtrp/tmplot/actions/workflows/python-package.yml)
|
|
6
|
+
[](https://tmplot.readthedocs.io/en/latest/?badge=latest)
|
|
7
|
+
[](https://pepy.tech/project/tmplot)
|
|
8
|
+
[](https://pypi.org/project/tmplot)
|
|
9
|
+
[](https://github.com/maximtrp/tmplot/issues)
|
|
10
|
+
|
|
11
|
+
**tmplot** is a comprehensive Python package for **topic modeling analysis and visualization**. Built for data scientists and researchers, it provides powerful interactive reports and advanced analytics that extend beyond traditional LDAvis/pyLDAvis capabilities.
|
|
12
|
+
|
|
13
|
+
**Analyze** • **Visualize** • **Compare** multiple topic models with ease
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Key Features
|
|
18
|
+
|
|
19
|
+
### Interactive Visualization
|
|
20
|
+
|
|
21
|
+
- **Topic scatter plots** with customizable coordinates and sizing
|
|
22
|
+
- **Term probability charts** with relevance weighting
|
|
23
|
+
- **Document analysis** showing top documents per topic
|
|
24
|
+
- **Interactive reports** with real-time parameter adjustment
|
|
25
|
+
|
|
26
|
+
### Advanced Analytics
|
|
27
|
+
|
|
28
|
+
- **Topic stability analysis** across multiple model runs
|
|
29
|
+
- **Model comparison** with sophisticated distance metrics
|
|
30
|
+
- **Saliency calculations** for term importance
|
|
31
|
+
- **Entropy metrics** for model optimization
|
|
32
|
+
|
|
33
|
+
### Model Support
|
|
34
|
+
|
|
35
|
+
- **[tomotopy](https://bab2min.github.io/tomotopy/)**: `LDAModel`, `LLDAModel`, `CTModel`, `DMRModel`, `HDPModel`, `PTModel`, `SLDAModel`, `GDMRModel`
|
|
36
|
+
- **[gensim](https://radimrehurek.com/gensim/)**: `LdaModel`, `LdaMulticore`
|
|
37
|
+
- **[bitermplus](https://github.com/maximtrp/bitermplus)**: `BTM`
|
|
38
|
+
|
|
39
|
+
### Distance Metrics
|
|
40
|
+
|
|
41
|
+
- Kullback-Leibler (symmetric & non-symmetric)
|
|
42
|
+
- Jensen-Shannon divergence
|
|
43
|
+
- Jeffrey's divergence
|
|
44
|
+
- Hellinger & Bhattacharyya distances
|
|
45
|
+
- Total variation distance
|
|
46
|
+
- Jaccard index
|
|
47
|
+
|
|
48
|
+
### Dimensionality Reduction
|
|
49
|
+
|
|
50
|
+
t-SNE, SpectralEmbedding, MDS, LocallyLinearEmbedding, Isomap
|
|
51
|
+
|
|
52
|
+
## Donate
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
<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>
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
### Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# From PyPI (recommended)
|
|
64
|
+
pip install tmplot
|
|
65
|
+
|
|
66
|
+
# Development version
|
|
67
|
+
pip install git+https://github.com/maximtrp/tmplot.git
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Basic Usage
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import tmplot as tmp
|
|
74
|
+
|
|
75
|
+
# Load your topic model and documents
|
|
76
|
+
model = your_fitted_model # tomotopy, gensim, or bitermplus
|
|
77
|
+
docs = your_documents
|
|
78
|
+
|
|
79
|
+
# Create interactive report
|
|
80
|
+
tmp.report(model, docs=docs)
|
|
81
|
+
|
|
82
|
+
# Or create individual visualizations
|
|
83
|
+
coords = tmp.prepare_coords(model)
|
|
84
|
+
tmp.plot_scatter_topics(coords, size_col='size')
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Advanced Examples
|
|
88
|
+
|
|
89
|
+
### Get Stable Topics
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import tmplot as tmp
|
|
93
|
+
|
|
94
|
+
# Find stable topics across multiple models
|
|
95
|
+
models = [model1, model2, model3, model4]
|
|
96
|
+
closest_topics, distances = tmp.get_closest_topics(models)
|
|
97
|
+
stable_topics, stable_distances = tmp.get_stable_topics(closest_topics, distances)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Analyze Model
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Calculate entropy for model selection
|
|
104
|
+
entropy_score = tmp.entropy(phi_matrix)
|
|
105
|
+
|
|
106
|
+
# Analyze topic stability
|
|
107
|
+
saliency = tmp.get_salient_terms(phi, theta)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Visualize
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# Create topic distance matrix with different metrics
|
|
114
|
+
topic_dists = tmp.get_topics_dist(phi, method='jensen-shannon')
|
|
115
|
+
|
|
116
|
+
# Generate coordinates with custom algorithm
|
|
117
|
+
coords = tmp.get_topics_scatter(topic_dists, theta, method='tsne')
|
|
118
|
+
tmp.plot_scatter_topics(coords, topic=3) # Highlight topic 3
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Documentation & Examples
|
|
122
|
+
|
|
123
|
+
- [Complete Tutorial](https://tmplot.readthedocs.io/en/latest/tutorial.html) - Step-by-step guide
|
|
124
|
+
- [API Reference](https://tmplot.readthedocs.io/) - Full documentation
|
|
125
|
+
- [Example Notebooks](https://github.com/maximtrp/tmplot/tree/main/examples) - Jupyter examples
|
|
126
|
+
|
|
127
|
+
## Requirements
|
|
128
|
+
|
|
129
|
+
**Core dependencies:** `numpy`, `scipy`, `scikit-learn`, `pandas`, `altair`, `ipywidgets`
|
|
130
|
+
|
|
131
|
+
**Optional models:** `tomotopy`, `gensim`, `bitermplus`
|
|
@@ -21,12 +21,13 @@ def _dist_klb(a1: np.ndarray, a2: np.ndarray):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _dist_sklb(a1: np.ndarray, a2: np.ndarray):
|
|
24
|
-
dist = kl_div(a1, a2) + kl_div(
|
|
24
|
+
dist = kl_div(a1, a2) + kl_div(a2, a1)
|
|
25
25
|
return dist[np.isfinite(dist)].sum()
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _dist_jsd(a1: np.ndarray, a2: np.ndarray):
|
|
29
|
-
|
|
29
|
+
m = 0.5 * (a1 + a2)
|
|
30
|
+
dist = 0.5 * kl_div(a1, m) + 0.5 * kl_div(a2, m)
|
|
30
31
|
return dist[np.isfinite(dist)].sum()
|
|
31
32
|
|
|
32
33
|
|
|
@@ -36,16 +37,22 @@ def _dist_jef(a1: np.ndarray, a2: np.ndarray):
|
|
|
36
37
|
return vals.sum()
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
# Small value for numerical stability
|
|
41
|
+
EPSILON = 1e-64
|
|
42
|
+
|
|
43
|
+
|
|
39
44
|
def _dist_hel(a1: np.ndarray, a2: np.ndarray):
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
a1_safe = a1.copy()
|
|
46
|
+
a2_safe = a2.copy()
|
|
47
|
+
a1_safe[(a1_safe <= 0) | ~np.isfinite(a1_safe)] = EPSILON
|
|
48
|
+
a2_safe[(a2_safe <= 0) | ~np.isfinite(a2_safe)] = EPSILON
|
|
49
|
+
hel_val = distance.euclidean(np.sqrt(a1_safe), np.sqrt(a2_safe)) / np.sqrt(2)
|
|
43
50
|
return hel_val
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
def _dist_bhat(a1: np.ndarray, a2: np.ndarray):
|
|
47
54
|
pq = a1 * a2
|
|
48
|
-
pq[(pq <= 0) | ~np.isfinite(pq)] =
|
|
55
|
+
pq[(pq <= 0) | ~np.isfinite(pq)] = EPSILON
|
|
49
56
|
dist = -np.log(np.sum(np.sqrt(pq)))
|
|
50
57
|
return dist
|
|
51
58
|
|
|
@@ -92,6 +99,14 @@ def get_topics_dist(
|
|
|
92
99
|
Topics distances matrix.
|
|
93
100
|
"""
|
|
94
101
|
phi_copy = np.array(phi)
|
|
102
|
+
|
|
103
|
+
if phi_copy.ndim != 2:
|
|
104
|
+
raise ValueError("phi must be a 2D array (words x topics)")
|
|
105
|
+
if np.any(phi_copy < 0):
|
|
106
|
+
raise ValueError("phi must contain non-negative probability values")
|
|
107
|
+
if not np.allclose(phi_copy.sum(axis=0), 1.0, atol=1e-6):
|
|
108
|
+
raise ValueError("phi columns must sum to 1 (probability distributions)")
|
|
109
|
+
|
|
95
110
|
topics_num = phi_copy.shape[1]
|
|
96
111
|
topics_pairs = combinations(range(topics_num), 2)
|
|
97
112
|
|
|
@@ -165,6 +180,7 @@ def get_topics_scatter(
|
|
|
165
180
|
elif method == "mds":
|
|
166
181
|
method_kws.setdefault("dissimilarity", "precomputed")
|
|
167
182
|
method_kws.setdefault("normalized_stress", "auto")
|
|
183
|
+
method_kws.setdefault("n_init", 1)
|
|
168
184
|
transformer = MDS(**method_kws)
|
|
169
185
|
|
|
170
186
|
elif method == "lle":
|
|
@@ -156,6 +156,8 @@ def get_theta(model: object, corpus: Optional[List] = None) -> Optional[DataFram
|
|
|
156
156
|
elif _is_gensim(model):
|
|
157
157
|
if corpus is None:
|
|
158
158
|
raise ValueError("`corpus` must be supplied for a gensim model")
|
|
159
|
+
if len(corpus) == 0:
|
|
160
|
+
raise ValueError("corpus cannot be empty")
|
|
159
161
|
tdd = list(map(model.get_document_topics, corpus))
|
|
160
162
|
theta = DataFrame(zeros((len(tdd), model.num_topics)))
|
|
161
163
|
for doc_id, doc_topic in enumerate(tdd):
|
|
@@ -165,6 +167,8 @@ def get_theta(model: object, corpus: Optional[List] = None) -> Optional[DataFram
|
|
|
165
167
|
|
|
166
168
|
elif _is_btmplus(model):
|
|
167
169
|
theta = DataFrame(model.matrix_topics_docs_)
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError(f"Unsupported model type: {type(model)}")
|
|
168
172
|
|
|
169
173
|
if isinstance(theta, DataFrame):
|
|
170
174
|
theta.index.name = "topics"
|
|
@@ -264,9 +268,19 @@ def calc_topics_marg_probs(
|
|
|
264
268
|
Union[pandas.DataFrame, numpy.ndarray]
|
|
265
269
|
Marginal topics probabilities.
|
|
266
270
|
"""
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
theta_arr = array(theta)
|
|
272
|
+
if theta_arr.size == 0:
|
|
273
|
+
raise ValueError("theta matrix cannot be empty")
|
|
274
|
+
|
|
275
|
+
p_t = theta_arr.sum(axis=1)
|
|
276
|
+
total_sum = p_t.sum()
|
|
277
|
+
if total_sum == 0:
|
|
278
|
+
raise ValueError("theta matrix contains all zeros - cannot normalize")
|
|
279
|
+
|
|
280
|
+
p_t /= total_sum
|
|
269
281
|
if topic_id is not None:
|
|
282
|
+
if topic_id < 0 or topic_id >= len(p_t):
|
|
283
|
+
raise IndexError(f"topic_id {topic_id} out of bounds for {len(p_t)} topics")
|
|
270
284
|
return p_t[topic_id]
|
|
271
285
|
return p_t
|
|
272
286
|
|
|
@@ -282,6 +296,8 @@ def calc_terms_marg_probs(
|
|
|
282
296
|
----------
|
|
283
297
|
phi : Union[numpy.ndarray, pandas.DataFrame]
|
|
284
298
|
Words vs topics matrix.
|
|
299
|
+
p_t : Union[numpy.ndarray, pandas.Series]
|
|
300
|
+
Topic marginal probabilities.
|
|
285
301
|
word_id: Optional[int]
|
|
286
302
|
Word index.
|
|
287
303
|
|
|
@@ -290,8 +306,20 @@ def calc_terms_marg_probs(
|
|
|
290
306
|
Union[numpy.ndarray, pandas.Series]
|
|
291
307
|
Marginal terms probabilities.
|
|
292
308
|
"""
|
|
293
|
-
|
|
309
|
+
phi_arr = array(phi)
|
|
310
|
+
p_t_arr = array(p_t)
|
|
311
|
+
|
|
312
|
+
if phi_arr.size == 0:
|
|
313
|
+
raise ValueError("phi matrix cannot be empty")
|
|
314
|
+
if p_t_arr.size == 0:
|
|
315
|
+
raise ValueError("p_t array cannot be empty")
|
|
316
|
+
if phi_arr.shape[1] != p_t_arr.shape[0]:
|
|
317
|
+
raise ValueError(f"phi topics dimension {phi_arr.shape[1]} must match p_t length {p_t_arr.shape[0]}")
|
|
318
|
+
|
|
319
|
+
p_w = (phi_arr * p_t_arr).sum(axis=1)
|
|
294
320
|
if word_id is not None:
|
|
321
|
+
if word_id < 0 or word_id >= len(p_w):
|
|
322
|
+
raise IndexError(f"word_id {word_id} out of bounds for {len(p_w)} words")
|
|
295
323
|
return p_w[word_id]
|
|
296
324
|
return p_w
|
|
297
325
|
|
|
@@ -315,10 +343,17 @@ def get_salient_terms(phi: ndarray, theta: ndarray) -> ndarray:
|
|
|
315
343
|
numpy.ndarray
|
|
316
344
|
Terms saliency values.
|
|
317
345
|
"""
|
|
346
|
+
if phi.size == 0 or theta.size == 0:
|
|
347
|
+
raise ValueError("phi and theta matrices cannot be empty")
|
|
348
|
+
if phi.shape[1] != theta.shape[0]:
|
|
349
|
+
raise ValueError(f"phi topics dimension {phi.shape[1]} must match theta topics dimension {theta.shape[0]}")
|
|
350
|
+
|
|
318
351
|
p_t = calc_topics_marg_probs(theta)
|
|
319
352
|
p_w = calc_terms_marg_probs(phi, p_t)
|
|
320
353
|
|
|
321
354
|
def _p_tw(phi, w, t):
|
|
355
|
+
if p_w[w] == 0:
|
|
356
|
+
return 0 # Avoid division by zero
|
|
322
357
|
return array(phi)[w, t] * p_t[t] / p_w[w]
|
|
323
358
|
|
|
324
359
|
saliency = array(
|
|
@@ -327,6 +362,8 @@ def get_salient_terms(phi: ndarray, theta: ndarray) -> ndarray:
|
|
|
327
362
|
* sum(
|
|
328
363
|
(
|
|
329
364
|
_p_tw(phi, w, t) * log(_p_tw(phi, w, t) / p_t[t])
|
|
365
|
+
if _p_tw(phi, w, t) > 0 and p_t[t] > 0
|
|
366
|
+
else 0 # Handle log(0) cases
|
|
330
367
|
for t in range(phi.shape[1])
|
|
331
368
|
)
|
|
332
369
|
)
|
|
@@ -105,6 +105,9 @@ def report(
|
|
|
105
105
|
ipywidgets.widgets.widget_box.VBox
|
|
106
106
|
Report interface as a VBox instance.
|
|
107
107
|
"""
|
|
108
|
+
# Input validation
|
|
109
|
+
if not docs or len(docs) == 0:
|
|
110
|
+
raise ValueError("docs cannot be empty")
|
|
108
111
|
|
|
109
112
|
_topics_kws = (
|
|
110
113
|
{"chart_kws": {"height": height, "width": width}}
|
|
@@ -117,7 +120,7 @@ def report(
|
|
|
117
120
|
if not words_kws
|
|
118
121
|
else deepcopy(words_kws)
|
|
119
122
|
)
|
|
120
|
-
_top_docs_kws = {} if not
|
|
123
|
+
_top_docs_kws = {} if not top_docs_kws else deepcopy(top_docs_kws)
|
|
121
124
|
_docs_kws = {} if not docs_kws else deepcopy(docs_kws)
|
|
122
125
|
|
|
123
126
|
# Headers init
|
|
@@ -162,8 +165,10 @@ def report(
|
|
|
162
165
|
}
|
|
163
166
|
)
|
|
164
167
|
|
|
168
|
+
# Cache phi matrix to avoid repeated calls
|
|
169
|
+
phi = get_phi(model)
|
|
170
|
+
|
|
165
171
|
if "terms_probs" not in _words_kws:
|
|
166
|
-
phi = get_phi(model)
|
|
167
172
|
terms_probs = calc_terms_probs_ratio(phi, topic=0)
|
|
168
173
|
_words_kws.update({"terms_probs": terms_probs})
|
|
169
174
|
|
|
@@ -106,6 +106,9 @@ def get_closest_topics(
|
|
|
106
106
|
if mid == ref:
|
|
107
107
|
continue
|
|
108
108
|
|
|
109
|
+
# Get phi matrix for current model
|
|
110
|
+
current_phi = get_phi(model)
|
|
111
|
+
|
|
109
112
|
# Distance matrix for all topic pairs
|
|
110
113
|
all_vs_all_dists = np.zeros((topics_num, topics_num))
|
|
111
114
|
|
|
@@ -113,7 +116,7 @@ def get_closest_topics(
|
|
|
113
116
|
for t_ref in range(topics_num):
|
|
114
117
|
for t in range(topics_num):
|
|
115
118
|
all_vs_all_dists[t_ref, t] = dist_func(
|
|
116
|
-
model_ref_phi.iloc[:, t_ref],
|
|
119
|
+
model_ref_phi.iloc[:, t_ref], current_phi.iloc[:, t]
|
|
117
120
|
)
|
|
118
121
|
|
|
119
122
|
# Creating two arrays for the closest topics ids and distance values
|
|
@@ -128,11 +128,15 @@ def plot_scatter_topics(
|
|
|
128
128
|
else {"condition": {"test": f"datum['topic'] == {topic}", "value": "red"}}
|
|
129
129
|
)
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
# Input validation
|
|
132
|
+
if isinstance(topics_coords, ndarray):
|
|
133
|
+
if topics_coords.size == 0:
|
|
134
|
+
raise ValueError("topics_coords cannot be empty")
|
|
135
|
+
data = DataFrame(topics_coords, columns=[x_col, y_col])
|
|
136
|
+
else:
|
|
137
|
+
if topics_coords.empty:
|
|
138
|
+
raise ValueError("topics_coords DataFrame cannot be empty")
|
|
139
|
+
data = topics_coords.copy()
|
|
136
140
|
|
|
137
141
|
if not topic_col:
|
|
138
142
|
topic_col = "topic"
|
|
@@ -225,6 +229,13 @@ def plot_terms(
|
|
|
225
229
|
altair.Chart
|
|
226
230
|
Terms probabilities chart.
|
|
227
231
|
"""
|
|
232
|
+
# Input validation
|
|
233
|
+
if terms_probs.empty:
|
|
234
|
+
raise ValueError("terms_probs DataFrame cannot be empty")
|
|
235
|
+
required_cols = [x_col, y_col, color_col]
|
|
236
|
+
missing_cols = [col for col in required_cols if col not in terms_probs.columns]
|
|
237
|
+
if missing_cols:
|
|
238
|
+
raise ValueError(f"Missing required columns: {missing_cols}")
|
|
228
239
|
if not x_kws:
|
|
229
240
|
x_kws = {"stack": None}
|
|
230
241
|
if not y_kws:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tmplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Visualization of Topic Modeling Results
|
|
5
5
|
Author-email: Maksim Terpilovskii <maximtrp@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -55,6 +55,7 @@ Provides-Extra: models
|
|
|
55
55
|
Requires-Dist: tomotopy>=0.8.0; extra == "models"
|
|
56
56
|
Requires-Dist: gensim; extra == "models"
|
|
57
57
|
Requires-Dist: bitermplus; extra == "models"
|
|
58
|
+
Dynamic: license-file
|
|
58
59
|
|
|
59
60
|
# tmplot
|
|
60
61
|
|
|
@@ -66,35 +67,46 @@ Requires-Dist: bitermplus; extra == "models"
|
|
|
66
67
|
[](https://pypi.org/project/tmplot)
|
|
67
68
|
[](https://github.com/maximtrp/tmplot/issues)
|
|
68
69
|
|
|
69
|
-
**tmplot** is a Python package for
|
|
70
|
+
**tmplot** is a comprehensive Python package for **topic modeling analysis and visualization**. Built for data scientists and researchers, it provides powerful interactive reports and advanced analytics that extend beyond traditional LDAvis/pyLDAvis capabilities.
|
|
71
|
+
|
|
72
|
+
**Analyze** • **Visualize** • **Compare** multiple topic models with ease
|
|
70
73
|
|
|
71
74
|

|
|
72
75
|
|
|
73
|
-
## Features
|
|
76
|
+
## Key Features
|
|
77
|
+
|
|
78
|
+
### Interactive Visualization
|
|
79
|
+
|
|
80
|
+
- **Topic scatter plots** with customizable coordinates and sizing
|
|
81
|
+
- **Term probability charts** with relevance weighting
|
|
82
|
+
- **Document analysis** showing top documents per topic
|
|
83
|
+
- **Interactive reports** with real-time parameter adjustment
|
|
84
|
+
|
|
85
|
+
### Advanced Analytics
|
|
74
86
|
|
|
75
|
-
-
|
|
87
|
+
- **Topic stability analysis** across multiple model runs
|
|
88
|
+
- **Model comparison** with sophisticated distance metrics
|
|
89
|
+
- **Saliency calculations** for term importance
|
|
90
|
+
- **Entropy metrics** for model optimization
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
- [gensim](https://radimrehurek.com/gensim/): `LdaModel`, `LdaMulticore`
|
|
79
|
-
- [bitermplus](https://github.com/maximtrp/bitermplus): `BTM`
|
|
92
|
+
### Model Support
|
|
80
93
|
|
|
81
|
-
-
|
|
94
|
+
- **[tomotopy](https://bab2min.github.io/tomotopy/)**: `LDAModel`, `LLDAModel`, `CTModel`, `DMRModel`, `HDPModel`, `PTModel`, `SLDAModel`, `GDMRModel`
|
|
95
|
+
- **[gensim](https://radimrehurek.com/gensim/)**: `LdaModel`, `LdaMulticore`
|
|
96
|
+
- **[bitermplus](https://github.com/maximtrp/bitermplus)**: `BTM`
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
- Jenson-Shannon divergence
|
|
85
|
-
- Jeffrey's divergence
|
|
86
|
-
- Hellinger distance
|
|
87
|
-
- Bhattacharyya distance
|
|
88
|
-
- Total variation distance
|
|
89
|
-
- Jaccard inversed index
|
|
98
|
+
### Distance Metrics
|
|
90
99
|
|
|
91
|
-
-
|
|
100
|
+
- Kullback-Leibler (symmetric & non-symmetric)
|
|
101
|
+
- Jensen-Shannon divergence
|
|
102
|
+
- Jeffrey's divergence
|
|
103
|
+
- Hellinger & Bhattacharyya distances
|
|
104
|
+
- Total variation distance
|
|
105
|
+
- Jaccard index
|
|
92
106
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- LocallyLinearEmbedding
|
|
97
|
-
- Isomap
|
|
107
|
+
### Dimensionality Reduction
|
|
108
|
+
|
|
109
|
+
t-SNE, SpectralEmbedding, MDS, LocallyLinearEmbedding, Isomap
|
|
98
110
|
|
|
99
111
|
## Donate
|
|
100
112
|
|
|
@@ -102,55 +114,77 @@ If you find this package useful, please consider donating any amount of money. T
|
|
|
102
114
|
|
|
103
115
|
<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
116
|
|
|
105
|
-
##
|
|
117
|
+
## Quick Start
|
|
106
118
|
|
|
107
|
-
|
|
119
|
+
### Installation
|
|
108
120
|
|
|
109
121
|
```bash
|
|
122
|
+
# From PyPI (recommended)
|
|
110
123
|
pip install tmplot
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Or directly from this repository:
|
|
114
124
|
|
|
115
|
-
|
|
125
|
+
# Development version
|
|
116
126
|
pip install git+https://github.com/maximtrp/tmplot.git
|
|
117
127
|
```
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
### Basic Usage
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import tmplot as tmp
|
|
133
|
+
|
|
134
|
+
# Load your topic model and documents
|
|
135
|
+
model = your_fitted_model # tomotopy, gensim, or bitermplus
|
|
136
|
+
docs = your_documents
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- `scikit-learn`
|
|
124
|
-
- `pandas`
|
|
125
|
-
- `altair`
|
|
126
|
-
- `ipywidgets`
|
|
127
|
-
- `tomotopy`, `gensim`, and `bitermplus` (optional)
|
|
138
|
+
# Create interactive report
|
|
139
|
+
tmp.report(model, docs=docs)
|
|
128
140
|
|
|
129
|
-
|
|
141
|
+
# Or create individual visualizations
|
|
142
|
+
coords = tmp.prepare_coords(model)
|
|
143
|
+
tmp.plot_scatter_topics(coords, size_col='size')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Advanced Examples
|
|
147
|
+
|
|
148
|
+
### Get Stable Topics
|
|
130
149
|
|
|
131
150
|
```python
|
|
132
|
-
# Importing packages
|
|
133
151
|
import tmplot as tmp
|
|
134
|
-
import pickle as pkl
|
|
135
|
-
import pandas as pd
|
|
136
152
|
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
# Find stable topics across multiple models
|
|
154
|
+
models = [model1, model2, model3, model4]
|
|
155
|
+
closest_topics, distances = tmp.get_closest_topics(models)
|
|
156
|
+
stable_topics, stable_distances = tmp.get_stable_topics(closest_topics, distances)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Analyze Model
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Calculate entropy for model selection
|
|
163
|
+
entropy_score = tmp.entropy(phi_matrix)
|
|
140
164
|
|
|
141
|
-
#
|
|
142
|
-
|
|
165
|
+
# Analyze topic stability
|
|
166
|
+
saliency = tmp.get_salient_terms(phi, theta)
|
|
167
|
+
```
|
|
143
168
|
|
|
144
|
-
|
|
145
|
-
topics_coords = tmp.prepare_coords(model)
|
|
146
|
-
tmp.plot_scatter_topics(topics_coords, size_col='size', label_col='label')
|
|
169
|
+
### Visualize
|
|
147
170
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
tmp.
|
|
171
|
+
```python
|
|
172
|
+
# Create topic distance matrix with different metrics
|
|
173
|
+
topic_dists = tmp.get_topics_dist(phi, method='jensen-shannon')
|
|
151
174
|
|
|
152
|
-
#
|
|
153
|
-
tmp.
|
|
175
|
+
# Generate coordinates with custom algorithm
|
|
176
|
+
coords = tmp.get_topics_scatter(topic_dists, theta, method='tsne')
|
|
177
|
+
tmp.plot_scatter_topics(coords, topic=3) # Highlight topic 3
|
|
154
178
|
```
|
|
155
179
|
|
|
156
|
-
|
|
180
|
+
## Documentation & Examples
|
|
181
|
+
|
|
182
|
+
- [Complete Tutorial](https://tmplot.readthedocs.io/en/latest/tutorial.html) - Step-by-step guide
|
|
183
|
+
- [API Reference](https://tmplot.readthedocs.io/) - Full documentation
|
|
184
|
+
- [Example Notebooks](https://github.com/maximtrp/tmplot/tree/main/examples) - Jupyter examples
|
|
185
|
+
|
|
186
|
+
## Requirements
|
|
187
|
+
|
|
188
|
+
**Core dependencies:** `numpy`, `scipy`, `scikit-learn`, `pandas`, `altair`, `ipywidgets`
|
|
189
|
+
|
|
190
|
+
**Optional models:** `tomotopy`, `gensim`, `bitermplus`
|
tmplot-0.2.0/README.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# tmplot
|
|
2
|
-
|
|
3
|
-
[](https://app.codacy.com/gh/maximtrp/tmplot/coverage)
|
|
4
|
-
[](https://app.codacy.com/gh/maximtrp/tmplot)
|
|
5
|
-
[](https://github.com/maximtrp/tmplot/actions/workflows/python-package.yml)
|
|
6
|
-
[](https://tmplot.readthedocs.io/en/latest/?badge=latest)
|
|
7
|
-
[](https://pepy.tech/project/tmplot)
|
|
8
|
-
[](https://pypi.org/project/tmplot)
|
|
9
|
-
[](https://github.com/maximtrp/tmplot/issues)
|
|
10
|
-
|
|
11
|
-
**tmplot** is a Python package for analysis and visualization of topic modeling results. It provides the interactive report interface that borrows much from LDAvis/pyLDAvis and builds upon it offering a number of metrics for calculating topic distances and a number of algorithms for calculating scatter coordinates of topics. It can be used to select closest and stable topics across multiple models.
|
|
12
|
-
|
|
13
|
-

|
|
14
|
-
|
|
15
|
-
## Features
|
|
16
|
-
|
|
17
|
-
- Supported models:
|
|
18
|
-
|
|
19
|
-
- [tomotopy](https://bab2min.github.io/tomotopy/): `LDAModel`, `LLDAModel`, `CTModel`, `DMRModel`, `HDPModel`, `PTModel`, `SLDAModel`, `GDMRModel`
|
|
20
|
-
- [gensim](https://radimrehurek.com/gensim/): `LdaModel`, `LdaMulticore`
|
|
21
|
-
- [bitermplus](https://github.com/maximtrp/bitermplus): `BTM`
|
|
22
|
-
|
|
23
|
-
- Supported distance metrics:
|
|
24
|
-
|
|
25
|
-
- Kullback-Leibler (symmetric and non-symmetric) divergence
|
|
26
|
-
- Jenson-Shannon divergence
|
|
27
|
-
- Jeffrey's divergence
|
|
28
|
-
- Hellinger distance
|
|
29
|
-
- Bhattacharyya distance
|
|
30
|
-
- Total variation distance
|
|
31
|
-
- Jaccard inversed index
|
|
32
|
-
|
|
33
|
-
- Supported [algorithms](https://scikit-learn.org/stable/modules/classes.html#module-sklearn.manifold) for calculating topics scatter coordinates:
|
|
34
|
-
|
|
35
|
-
- t-SNE
|
|
36
|
-
- SpectralEmbedding
|
|
37
|
-
- MDS
|
|
38
|
-
- LocallyLinearEmbedding
|
|
39
|
-
- Isomap
|
|
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
|
-
|
|
47
|
-
## Installation
|
|
48
|
-
|
|
49
|
-
The package can be installed from PyPi:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
pip install tmplot
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Or directly from this repository:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
pip install git+https://github.com/maximtrp/tmplot.git
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Dependencies
|
|
62
|
-
|
|
63
|
-
- `numpy`
|
|
64
|
-
- `scipy`
|
|
65
|
-
- `scikit-learn`
|
|
66
|
-
- `pandas`
|
|
67
|
-
- `altair`
|
|
68
|
-
- `ipywidgets`
|
|
69
|
-
- `tomotopy`, `gensim`, and `bitermplus` (optional)
|
|
70
|
-
|
|
71
|
-
## Quick example
|
|
72
|
-
|
|
73
|
-
```python
|
|
74
|
-
# Importing packages
|
|
75
|
-
import tmplot as tmp
|
|
76
|
-
import pickle as pkl
|
|
77
|
-
import pandas as pd
|
|
78
|
-
|
|
79
|
-
# Reading a model from a file
|
|
80
|
-
with open('data/model.pkl', 'rb') as file:
|
|
81
|
-
model = pkl.load(file)
|
|
82
|
-
|
|
83
|
-
# Reading documents from a file
|
|
84
|
-
docs = pd.read_csv('data/docs.txt.gz', header=None).values.ravel()
|
|
85
|
-
|
|
86
|
-
# Plotting topics as a scatter plot
|
|
87
|
-
topics_coords = tmp.prepare_coords(model)
|
|
88
|
-
tmp.plot_scatter_topics(topics_coords, size_col='size', label_col='label')
|
|
89
|
-
|
|
90
|
-
# Plotting terms probabilities
|
|
91
|
-
terms_probs = tmp.calc_terms_probs_ratio(phi, topic=0, lambda_=1)
|
|
92
|
-
tmp.plot_terms(terms_probs)
|
|
93
|
-
|
|
94
|
-
# Running report interface
|
|
95
|
-
tmp.report(model, docs=docs, width=250)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
You can find more examples in the [tutorial](https://tmplot.readthedocs.io/en/latest/tutorial.html).
|
|
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
|