toxpol-nlp 0.1.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.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: toxpol-nlp
3
+ Version: 0.1.0
4
+ Summary: NLP toolkit for toxicity and polarization research: synthetic datasets and detection algorithms
5
+ Author-email: SwkratisCS <swkratisgiannoutsos@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/SwkratisCS/polarizedtrees
8
+ Keywords: toxicity,polarization,annotation,synthetic data,nlp,disagreement,crowdsourcing,demographics
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: numpy
20
+ Requires-Dist: pandas
21
+ Provides-Extra: ndfu
22
+ Requires-Dist: ndfu; extra == "ndfu"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: build; extra == "dev"
26
+ Requires-Dist: twine; extra == "dev"
27
+
28
+ # toxpol-nlp
29
+
30
+ NLP toolkit for **toxicity and polarization research**. Provides tools for synthetic dataset generation and polarization detection in human annotation studies.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install toxpol-nlp
36
+ ```
37
+
38
+ ## Repository Structure
39
+
40
+ ```
41
+ data_gen/ synthetic annotation dataset generator
42
+ polarized_trees/ Polarized Trees detection algorithm
43
+ toxpol/ installable package (source code)
44
+ ```
45
+
46
+ ### `data_gen/`
47
+ Tools for generating synthetic annotation datasets with **injected, known polarization**. Real annotation data cannot provide ground truth for which demographic dimensions drive disagreement — this module does. The generated datasets are the primary validation input for the Polarized Trees algorithm.
48
+
49
+ → See [`data_gen/README.md`](data_gen/README.md) for the full API and usage.
50
+
51
+ ### `polarized_trees/`
52
+ The Polarized Trees detection algorithm. Given an annotation dataset, it identifies which demographic dimensions split annotators into opposing rating poles and at what severity.
53
+
54
+ → Coming soon.
55
+
56
+ ## Tools
57
+
58
+ | Module | Description | Status |
59
+ |---|---|---|
60
+ | `toxpol.datagen` | Synthetic annotator pool with injected, ground-truth polarization | Stable |
61
+ | `toxpol.trees` | Polarized Trees detection algorithm | Coming soon |
62
+
@@ -0,0 +1,35 @@
1
+ # toxpol-nlp
2
+
3
+ NLP toolkit for **toxicity and polarization research**. Provides tools for synthetic dataset generation and polarization detection in human annotation studies.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install toxpol-nlp
9
+ ```
10
+
11
+ ## Repository Structure
12
+
13
+ ```
14
+ data_gen/ synthetic annotation dataset generator
15
+ polarized_trees/ Polarized Trees detection algorithm
16
+ toxpol/ installable package (source code)
17
+ ```
18
+
19
+ ### `data_gen/`
20
+ Tools for generating synthetic annotation datasets with **injected, known polarization**. Real annotation data cannot provide ground truth for which demographic dimensions drive disagreement — this module does. The generated datasets are the primary validation input for the Polarized Trees algorithm.
21
+
22
+ → See [`data_gen/README.md`](data_gen/README.md) for the full API and usage.
23
+
24
+ ### `polarized_trees/`
25
+ The Polarized Trees detection algorithm. Given an annotation dataset, it identifies which demographic dimensions split annotators into opposing rating poles and at what severity.
26
+
27
+ → Coming soon.
28
+
29
+ ## Tools
30
+
31
+ | Module | Description | Status |
32
+ |---|---|---|
33
+ | `toxpol.datagen` | Synthetic annotator pool with injected, ground-truth polarization | Stable |
34
+ | `toxpol.trees` | Polarized Trees detection algorithm | Coming soon |
35
+
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "toxpol-nlp"
7
+ version = "0.1.0"
8
+ description = "NLP toolkit for toxicity and polarization research: synthetic datasets and detection algorithms"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "SwkratisCS", email = "swkratisgiannoutsos@gmail.com" }
14
+ ]
15
+ keywords = ["toxicity", "polarization", "annotation", "synthetic data", "nlp", "disagreement", "crowdsourcing", "demographics"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Science/Research",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ ]
26
+ dependencies = [
27
+ "numpy",
28
+ "pandas",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ ndfu = ["ndfu"]
33
+ dev = ["pytest", "build", "twine"]
34
+
35
+ [project.urls]
36
+ Repository = "https://github.com/SwkratisCS/polarizedtrees"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["."]
40
+ include = ["toxpol*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from toxpol.datagen import AnnotatorPool, DEFAULT_DIMENSIONS
2
+
3
+ __all__ = ["AnnotatorPool", "DEFAULT_DIMENSIONS"]
@@ -0,0 +1,373 @@
1
+ """
2
+ Synthetic annotation dataset generator for studying demographic polarization in human labeling.
3
+
4
+ Builds a pool of annotators with explicit demographic identities and generates structured
5
+ disagreement patterns where rating behavior is governed by per-dimension bias configurations.
6
+ """
7
+
8
+ import itertools
9
+ import random
10
+
11
+ import numpy as np
12
+ import pandas as pd
13
+
14
+
15
+ # Default demographic dimensions used in the paper
16
+ DEFAULT_DIMENSIONS = {
17
+ "gender": ["male", "female", "non-binary"],
18
+ "politics": ["left", "center", "right"],
19
+ "age": ["<25", "25-50", ">50"],
20
+ "education": ["low", "medium", "high"],
21
+ "orientation": ["heterosexual", "lgbtq+"],
22
+ }
23
+
24
+
25
+ class AnnotatorPool:
26
+ """
27
+ Synthetic annotator pool for generating polarized rating datasets.
28
+
29
+ Builds a Cartesian-product pool of demographic identities, then generates
30
+ annotation datasets where each dimension is randomly assigned either a
31
+ "polarizing" role (splitting annotators into toxic/civil poles) or a
32
+ "unimodal" role (converging all annotators toward one rating range).
33
+
34
+ Parameters
35
+ ----------
36
+ dimensions : dict[str, list[str]]
37
+ Mapping from dimension name to the list of possible values.
38
+ Example: {"politics": ["left", "center", "right"], "age": ["<25", ">25"]}
39
+
40
+ exclude : list[str] | None
41
+ Dimension names to drop before building identities. Useful for ablations.
42
+
43
+ annotators_per_identity : int
44
+ How many annotators share each unique demographic combination.
45
+ Pool size = product(len(v) for v in dimensions.values()) * annotators_per_identity.
46
+
47
+ scale : int
48
+ Maximum value on the rating scale (ratings are integers in [1, scale]).
49
+
50
+ toxic_range : tuple[int, int]
51
+ (low, high) inclusive range from which toxic-pole annotators draw ratings.
52
+
53
+ civil_range : tuple[int, int]
54
+ (low, high) inclusive range from which civil-pole annotators draw ratings.
55
+
56
+ neutral_range : tuple[int, int]
57
+ (low, high) inclusive range used when a unimodal dimension converges to "neutral".
58
+
59
+ Examples
60
+ --------
61
+ >>> from toxpol.datagen import AnnotatorPool, DEFAULT_DIMENSIONS
62
+ >>> pool = AnnotatorPool(DEFAULT_DIMENSIONS)
63
+ >>> dataset, bias_config = pool.generate_dataset(n_texts=50, n_annotators_per_text=100)
64
+ >>> dataset.head()
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ dimensions,
70
+ exclude=None,
71
+ annotators_per_identity=10,
72
+ scale=5,
73
+ toxic_range=(4, 5),
74
+ civil_range=(1, 2),
75
+ neutral_range=(3, 3),
76
+ ):
77
+ self.annotators_per_identity = annotators_per_identity
78
+ self.identities, self.active_dims = self._get_identities(dimensions, exclude)
79
+ self.pool = self._build_pool()
80
+
81
+ self.scale = scale
82
+ self.toxic_range = toxic_range
83
+ self.civil_range = civil_range
84
+ self.neutral_range = neutral_range
85
+
86
+ # ------------------------------------------------------------------
87
+ # Pool construction
88
+ # ------------------------------------------------------------------
89
+
90
+ def _get_identities(self, dimensions, exclude=None):
91
+ active_dims = {k: v for k, v in dimensions.items() if k not in (exclude or [])}
92
+ identities = [
93
+ dict(zip(active_dims.keys(), combo))
94
+ for combo in itertools.product(*active_dims.values())
95
+ ]
96
+ return identities, active_dims
97
+
98
+ def _build_pool(self):
99
+ pool = []
100
+ for identity in self.identities:
101
+ for _ in range(self.annotators_per_identity):
102
+ pool.append(identity.copy())
103
+ pool = pd.DataFrame(pool)
104
+ pool.index.name = "annotator_id"
105
+ return pool
106
+
107
+ # ------------------------------------------------------------------
108
+ # Bias configuration
109
+ # ------------------------------------------------------------------
110
+
111
+ def _generate_bias_config(self, polarizing_prob=0.7):
112
+ """
113
+ Randomly assign each active dimension a role for one dataset instance.
114
+
115
+ A "polarizing" dimension splits its values into a toxic pole and a civil
116
+ pole. An "unimodal" dimension converges all annotators toward one range.
117
+
118
+ Returns
119
+ -------
120
+ dict
121
+ Keys are dimension names. Each value is a dict with:
122
+ - role: "polarizing" | "unimodal"
123
+ - toxic_pole / civil_pole (if polarizing): lists of dimension values
124
+ - convergence (if unimodal): "toxic" | "civil" | "neutral"
125
+ """
126
+ config = {}
127
+ for dim, values in self.active_dims.items():
128
+ role = random.choices(
129
+ ["polarizing", "unimodal"],
130
+ weights=[polarizing_prob, 1 - polarizing_prob],
131
+ )[0]
132
+ if role == "polarizing":
133
+ shuffled = values.copy()
134
+ random.shuffle(shuffled)
135
+ split = random.randint(1, len(shuffled) - 1)
136
+ config[dim] = {
137
+ "role": "polarizing",
138
+ "toxic_pole": shuffled[:split],
139
+ "civil_pole": shuffled[split:],
140
+ }
141
+ else:
142
+ config[dim] = {
143
+ "role": "unimodal",
144
+ "convergence": random.choice(["toxic", "civil", "neutral"]),
145
+ }
146
+ return config
147
+
148
+ # ------------------------------------------------------------------
149
+ # Public API
150
+ # ------------------------------------------------------------------
151
+
152
+ def generate_dataset(
153
+ self,
154
+ n_texts=100,
155
+ n_annotators_per_text=100,
156
+ noise=0.1,
157
+ polarizing_prob=0.7,
158
+ ):
159
+ """
160
+ Generate a synthetic annotation dataset.
161
+
162
+ A single bias configuration is drawn for the entire dataset (all texts
163
+ share the same demographic polarization structure). Each text is then
164
+ annotated by a random subset of the pool.
165
+
166
+ Parameters
167
+ ----------
168
+ n_texts : int
169
+ Number of texts to annotate. Must be >= 1.
170
+
171
+ n_annotators_per_text : int
172
+ Annotators sampled per text (without replacement).
173
+ Must be <= pool size (annotators_per_identity * number_of_identities).
174
+
175
+ noise : float in [0, 1]
176
+ Probability that any annotator ignores the bias config and draws
177
+ a uniformly random rating instead.
178
+
179
+ polarizing_prob : float in [0, 1]
180
+ Prior probability that each dimension is assigned a "polarizing"
181
+ role in the bias config (vs. "unimodal").
182
+
183
+ Returns
184
+ -------
185
+ dataset : pd.DataFrame
186
+ One row per (text_id, annotator_id) pair. Columns:
187
+ text_id, annotator_id, <all active dimension columns>, rating.
188
+
189
+ bias_config : dict
190
+ The bias configuration used for this dataset. See
191
+ `_generate_bias_config` for the structure.
192
+ """
193
+ if n_annotators_per_text > len(self.pool):
194
+ raise ValueError(
195
+ f"n_annotators_per_text ({n_annotators_per_text}) exceeds pool size "
196
+ f"({len(self.pool)}). Reduce n_annotators_per_text or increase "
197
+ f"annotators_per_identity."
198
+ )
199
+
200
+ bias_config = self._generate_bias_config(polarizing_prob)
201
+
202
+ # Precompute vote lookup for each polarizing dimension:
203
+ # maps annotator value → "toxic" | "civil"
204
+ vote_maps = {}
205
+ unimodal_fallback = None
206
+ for dim, config in bias_config.items():
207
+ if config["role"] == "polarizing":
208
+ vote_maps[dim] = (
209
+ {v: "toxic" for v in config["toxic_pole"]}
210
+ | {v: "civil" for v in config["civil_pole"]}
211
+ )
212
+ elif unimodal_fallback is None:
213
+ unimodal_fallback = config["convergence"]
214
+
215
+ frames = []
216
+ for text_id in range(n_texts):
217
+ sampled = self.pool.sample(n=n_annotators_per_text, replace=False)
218
+
219
+ if vote_maps:
220
+ # Vectorised majority vote across all polarizing dimensions
221
+ toxic = np.zeros(len(sampled), dtype=np.int32)
222
+ civil = np.zeros(len(sampled), dtype=np.int32)
223
+ for dim, vmap in vote_maps.items():
224
+ mapped = sampled[dim].map(vmap)
225
+ toxic += (mapped == "toxic").values
226
+ civil += (mapped == "civil").values
227
+ label = np.where(toxic > civil, 0,
228
+ np.where(civil > toxic, 1, 2)) # 0=toxic,1=civil,2=neutral
229
+ else:
230
+ fb = {"toxic": 0, "civil": 1, "neutral": 2}[unimodal_fallback or "neutral"]
231
+ label = np.full(len(sampled), fb, dtype=np.int32)
232
+
233
+ # Draw ratings from the appropriate range per label
234
+ ranges = [self.toxic_range, self.civil_range, self.neutral_range]
235
+ ratings = np.array([
236
+ np.random.randint(ranges[l][0], ranges[l][1] + 1) for l in label
237
+ ])
238
+
239
+ # Inject noise
240
+ noise_mask = np.random.random(len(sampled)) < noise
241
+ ratings[noise_mask] = np.random.randint(1, self.scale + 1, noise_mask.sum())
242
+
243
+ frame = sampled.copy()
244
+ frame.insert(0, "text_id", text_id)
245
+ frame["rating"] = ratings
246
+ frames.append(frame)
247
+
248
+ dataset = pd.concat(frames)
249
+ dataset.index.name = "annotator_id"
250
+ return dataset, bias_config
251
+
252
+ # ------------------------------------------------------------------
253
+ # Convenience / diagnostics
254
+ # ------------------------------------------------------------------
255
+
256
+ @property
257
+ def pool_size(self):
258
+ """Total number of annotators in the pool."""
259
+ return len(self.pool)
260
+
261
+ @property
262
+ def n_identities(self):
263
+ """Number of unique demographic identity combinations."""
264
+ return len(self.identities)
265
+
266
+ def summary(self):
267
+ """Print a brief summary of the pool configuration."""
268
+ print(f"Active dimensions : {list(self.active_dims.keys())}")
269
+ print(f"Unique identities : {self.n_identities}")
270
+ print(f"Annotators/identity: {self.annotators_per_identity}")
271
+ print(f"Pool size : {self.pool_size}")
272
+ print(f"Rating scale : 1–{self.scale}")
273
+ print(f" toxic_range : {self.toxic_range}")
274
+ print(f" civil_range : {self.civil_range}")
275
+ print(f" neutral_range : {self.neutral_range}")
276
+
277
+ def describe_bias(self, bias_config):
278
+ """Pretty-print the bias config as a readable table."""
279
+ col_w = max(len(d) for d in bias_config) + 2
280
+ print(f"{'dimension':<{col_w}} {'role':<12} details")
281
+ print("-" * 60)
282
+ for dim, config in bias_config.items():
283
+ if config["role"] == "polarizing":
284
+ details = (
285
+ f"toxic={config['toxic_pole']} civil={config['civil_pole']}"
286
+ )
287
+ else:
288
+ details = f"convergence={config['convergence']}"
289
+ print(f"{dim:<{col_w}} {config['role']:<12} {details}")
290
+
291
+ def analyze(self, dataset, bias_config):
292
+ """
293
+ Compute nDFU scores for every text, overall and per dimension value.
294
+
295
+ Requires the `ndfu` package (`pip install toxpol-nlp[ndfu]`).
296
+
297
+ Returns
298
+ -------
299
+ dict
300
+ results[text_id]["overall"] -> float
301
+ results[text_id][dim][value] -> float
302
+ """
303
+ try:
304
+ from ndfu import dfu
305
+ except ImportError:
306
+ raise ImportError(
307
+ "ndfu is required for analyze(). "
308
+ "Install it with: pip install toxpol-nlp[ndfu]"
309
+ )
310
+
311
+ def _ndfu(ratings):
312
+ counts = np.bincount(ratings, minlength=self.scale + 1)[1:]
313
+ hist = counts / counts.sum()
314
+ return dfu(hist)
315
+
316
+ results = {}
317
+ for text_id, text_data in dataset.groupby("text_id"):
318
+ text_results = {"overall": _ndfu(text_data["rating"].values)}
319
+ for dim in self.active_dims:
320
+ text_results[dim] = {
321
+ value: _ndfu(group["rating"].values)
322
+ for value, group in text_data.groupby(dim)
323
+ }
324
+ results[text_id] = text_results
325
+ return results
326
+
327
+ def summarize(self, dataset, bias_config, text_id=0):
328
+ """
329
+ Print nDFU scores for one text, grouped by dimension, with bias roles shown.
330
+
331
+ Calls analyze() internally. Requires `ndfu`.
332
+ """
333
+ results = self.analyze(dataset, bias_config)
334
+ text = results[text_id]
335
+ print(f"Text {text_id} — overall nDFU: {text['overall']:.3f}\n")
336
+ for dim, values in text.items():
337
+ if dim == "overall":
338
+ continue
339
+ role = bias_config[dim]["role"]
340
+ print(f"{dim} ({role}):")
341
+ for value, score in values.items():
342
+ print(f" {value}: {score:.3f}")
343
+ print()
344
+
345
+ def summarize_all(self, dataset, bias_config):
346
+ """
347
+ Print mean nDFU per dimension value, aggregated across all texts.
348
+
349
+ Gives a compact cross-text view: instead of per-text scores, each
350
+ dimension value shows its average nDFU and the spread (min–max).
351
+ Useful for seeing which demographic groups consistently disagree
352
+ more across the whole dataset.
353
+
354
+ Calls analyze() internally. Requires `ndfu`.
355
+ """
356
+ results = self.analyze(dataset, bias_config)
357
+ n_texts = len(results)
358
+
359
+ overall_scores = [results[t]["overall"] for t in results]
360
+ print(f"Overall nDFU — mean: {np.mean(overall_scores):.3f} "
361
+ f"min: {np.min(overall_scores):.3f} "
362
+ f"max: {np.max(overall_scores):.3f} "
363
+ f"(across {n_texts} texts)\n")
364
+
365
+ for dim in self.active_dims:
366
+ role = bias_config[dim]["role"]
367
+ print(f"{dim} ({role}):")
368
+ for value in self.active_dims[dim]:
369
+ scores = [results[t][dim][value] for t in results if value in results[t][dim]]
370
+ print(f" {value:<15} mean: {np.mean(scores):.3f} "
371
+ f"min: {np.min(scores):.3f} "
372
+ f"max: {np.max(scores):.3f}")
373
+ print()
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: toxpol-nlp
3
+ Version: 0.1.0
4
+ Summary: NLP toolkit for toxicity and polarization research: synthetic datasets and detection algorithms
5
+ Author-email: SwkratisCS <swkratisgiannoutsos@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/SwkratisCS/polarizedtrees
8
+ Keywords: toxicity,polarization,annotation,synthetic data,nlp,disagreement,crowdsourcing,demographics
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: numpy
20
+ Requires-Dist: pandas
21
+ Provides-Extra: ndfu
22
+ Requires-Dist: ndfu; extra == "ndfu"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: build; extra == "dev"
26
+ Requires-Dist: twine; extra == "dev"
27
+
28
+ # toxpol-nlp
29
+
30
+ NLP toolkit for **toxicity and polarization research**. Provides tools for synthetic dataset generation and polarization detection in human annotation studies.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install toxpol-nlp
36
+ ```
37
+
38
+ ## Repository Structure
39
+
40
+ ```
41
+ data_gen/ synthetic annotation dataset generator
42
+ polarized_trees/ Polarized Trees detection algorithm
43
+ toxpol/ installable package (source code)
44
+ ```
45
+
46
+ ### `data_gen/`
47
+ Tools for generating synthetic annotation datasets with **injected, known polarization**. Real annotation data cannot provide ground truth for which demographic dimensions drive disagreement — this module does. The generated datasets are the primary validation input for the Polarized Trees algorithm.
48
+
49
+ → See [`data_gen/README.md`](data_gen/README.md) for the full API and usage.
50
+
51
+ ### `polarized_trees/`
52
+ The Polarized Trees detection algorithm. Given an annotation dataset, it identifies which demographic dimensions split annotators into opposing rating poles and at what severity.
53
+
54
+ → Coming soon.
55
+
56
+ ## Tools
57
+
58
+ | Module | Description | Status |
59
+ |---|---|---|
60
+ | `toxpol.datagen` | Synthetic annotator pool with injected, ground-truth polarization | Stable |
61
+ | `toxpol.trees` | Polarized Trees detection algorithm | Coming soon |
62
+
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ toxpol/__init__.py
4
+ toxpol/datagen.py
5
+ toxpol_nlp.egg-info/PKG-INFO
6
+ toxpol_nlp.egg-info/SOURCES.txt
7
+ toxpol_nlp.egg-info/dependency_links.txt
8
+ toxpol_nlp.egg-info/requires.txt
9
+ toxpol_nlp.egg-info/top_level.txt
@@ -0,0 +1,10 @@
1
+ numpy
2
+ pandas
3
+
4
+ [dev]
5
+ pytest
6
+ build
7
+ twine
8
+
9
+ [ndfu]
10
+ ndfu
@@ -0,0 +1 @@
1
+ toxpol