topiclayers 0.2.0__tar.gz → 0.2.1__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.
- {topiclayers-0.2.0 → topiclayers-0.2.1}/PKG-INFO +7 -2
- {topiclayers-0.2.0 → topiclayers-0.2.1}/README.md +6 -1
- {topiclayers-0.2.0 → topiclayers-0.2.1}/pyproject.toml +1 -1
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/__init__.py +1 -1
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/cli.py +26 -1
- topiclayers-0.2.1/src/topiclayers/examples/generic/posts.csv +8 -0
- topiclayers-0.2.1/src/topiclayers/examples/generic.yml +15 -0
- topiclayers-0.2.1/src/topiclayers/examples/template_csv.yml +44 -0
- topiclayers-0.2.1/src/topiclayers/examples/template_twitter.yml +84 -0
- topiclayers-0.2.1/src/topiclayers/examples/twitter/toy.json +1 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/LICENSE +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/__init__.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/csv.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/registry.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/twitter.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/config.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/__init__.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/data.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/network.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/topic.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/manifest.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/schema.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/status.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/support/__init__.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/support/cache.py +0 -0
- {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/support/repro.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topiclayers
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: From any collection of posts to topical multilayer networks — hardened and social-scientist-friendly
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -43,6 +43,10 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
```bash
|
|
44
44
|
# Requires Python 3.12
|
|
45
45
|
pip install topiclayers
|
|
46
|
+
|
|
47
|
+
# copy runnable examples (configs + toy data) into ./examples/
|
|
48
|
+
topiclayers init
|
|
49
|
+
|
|
46
50
|
topiclayers run examples/generic.yml
|
|
47
51
|
```
|
|
48
52
|
|
|
@@ -161,7 +165,8 @@ CSV optional columns: `created_at`, `lang`, `interaction_type` (`original`/`repo
|
|
|
161
165
|
| Symptom | Likely cause | Fix |
|
|
162
166
|
|---------|-------------|-----|
|
|
163
167
|
| "All topics are -1" / "no topics found" | `min_topic_size` too high or dataset too small | Halve `min_topic_size` in config, or use a larger dataset (>100 posts) |
|
|
164
|
-
| "Module not found" | topiclayers not installed | `pip install
|
|
168
|
+
| "Module not found" | topiclayers not installed | `pip install topiclayers` |
|
|
169
|
+
| `topiclayers: command not found` | installed in a venv that isn't active | Activate the venv (`source .venv/bin/activate`) or use `python -m topiclayers.cli`; on install with `--user` add `~/.local/bin` to PATH |
|
|
165
170
|
| "OpenAI API key not set" | Using OpenAI embedder without key | Switch `embedder` to `all-MiniLM-L6-v2` (default, works offline) |
|
|
166
171
|
| "Qdrant connection refused" | Qdrant vector DB not running | Ignore — Qdrant is optional. Set `QDRANT_URL` in `.env` to enable |
|
|
167
172
|
| "Cannot create multilayer network" | All posts are outliers | Reduce `min_topic_size` or provide more data |
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
```bash
|
|
8
8
|
# Requires Python 3.12
|
|
9
9
|
pip install topiclayers
|
|
10
|
+
|
|
11
|
+
# copy runnable examples (configs + toy data) into ./examples/
|
|
12
|
+
topiclayers init
|
|
13
|
+
|
|
10
14
|
topiclayers run examples/generic.yml
|
|
11
15
|
```
|
|
12
16
|
|
|
@@ -125,7 +129,8 @@ CSV optional columns: `created_at`, `lang`, `interaction_type` (`original`/`repo
|
|
|
125
129
|
| Symptom | Likely cause | Fix |
|
|
126
130
|
|---------|-------------|-----|
|
|
127
131
|
| "All topics are -1" / "no topics found" | `min_topic_size` too high or dataset too small | Halve `min_topic_size` in config, or use a larger dataset (>100 posts) |
|
|
128
|
-
| "Module not found" | topiclayers not installed | `pip install
|
|
132
|
+
| "Module not found" | topiclayers not installed | `pip install topiclayers` |
|
|
133
|
+
| `topiclayers: command not found` | installed in a venv that isn't active | Activate the venv (`source .venv/bin/activate`) or use `python -m topiclayers.cli`; on install with `--user` add `~/.local/bin` to PATH |
|
|
129
134
|
| "OpenAI API key not set" | Using OpenAI embedder without key | Switch `embedder` to `all-MiniLM-L6-v2` (default, works offline) |
|
|
130
135
|
| "Qdrant connection refused" | Qdrant vector DB not running | Ignore — Qdrant is optional. Set `QDRANT_URL` in `.env` to enable |
|
|
131
136
|
| "Cannot create multilayer network" | All posts are outliers | Reduce `min_topic_size` or provide more data |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "topiclayers"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "From any collection of posts to topical multilayer networks — hardened and social-scientist-friendly"
|
|
5
5
|
authors = ["alessiogandelli <alessiogandelli99@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -168,12 +168,37 @@ def _run_pipeline_stages(config: RunConfig, input_path: Path, status) -> None:
|
|
|
168
168
|
print(f" Run manifest: {config.output_dir / 'run_manifest.json'}")
|
|
169
169
|
|
|
170
170
|
|
|
171
|
+
def _cmd_init(dest: Path) -> None:
|
|
172
|
+
import shutil
|
|
173
|
+
from importlib.resources import as_file, files
|
|
174
|
+
|
|
175
|
+
if dest.exists():
|
|
176
|
+
print(f"Directory '{dest}' already exists — remove it or pass another path.",
|
|
177
|
+
file=sys.stderr)
|
|
178
|
+
sys.exit(1)
|
|
179
|
+
|
|
180
|
+
with as_file(files("topiclayers") / "examples") as bundled:
|
|
181
|
+
shutil.copytree(bundled, dest)
|
|
182
|
+
|
|
183
|
+
print(f"Examples copied to {dest.resolve()}")
|
|
184
|
+
print("Try it:")
|
|
185
|
+
print(f" topiclayers run {dest / 'generic.yml'}")
|
|
186
|
+
|
|
187
|
+
|
|
171
188
|
def main() -> None:
|
|
172
|
-
if len(sys.argv) <
|
|
189
|
+
if len(sys.argv) < 2 or (
|
|
190
|
+
sys.argv[1] not in ("run", "status", "init")
|
|
191
|
+
or (sys.argv[1] != "init" and len(sys.argv) < 3)
|
|
192
|
+
):
|
|
173
193
|
print("Usage: topiclayers run <config.yaml>", file=sys.stderr)
|
|
174
194
|
print(" topiclayers status <output_dir>", file=sys.stderr)
|
|
195
|
+
print(" topiclayers init [dest=./examples]", file=sys.stderr)
|
|
175
196
|
sys.exit(1)
|
|
176
197
|
|
|
198
|
+
if sys.argv[1] == "init":
|
|
199
|
+
_cmd_init(Path(sys.argv[2]) if len(sys.argv) > 2 else Path("examples"))
|
|
200
|
+
return
|
|
201
|
+
|
|
177
202
|
if sys.argv[1] == "status":
|
|
178
203
|
from topiclayers.status import print_status
|
|
179
204
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
post_id,user_id,text,created_at,lang,interaction_type,target_post_id
|
|
2
|
+
t1,a,tweet1,2022-01-01,en,original,
|
|
3
|
+
t2,b,tweet2,2022-01-02,en,repost,t1
|
|
4
|
+
t3,c,tweet3,2022-01-03,en,repost,t2
|
|
5
|
+
t4,c,tweet4,2022-01-04,en,original,
|
|
6
|
+
t5,a,tweet5,2022-01-05,en,repost,t7
|
|
7
|
+
t6,d,tweet6,2022-01-06,en,repost,t7
|
|
8
|
+
t7,d,tweet7,2022-01-07,en,original,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
input:
|
|
2
|
+
format: csv_posts
|
|
3
|
+
# paths are resolved relative to this config file's directory
|
|
4
|
+
posts: generic/posts.csv
|
|
5
|
+
|
|
6
|
+
name: generic_toy
|
|
7
|
+
|
|
8
|
+
topic_model:
|
|
9
|
+
embedder: all-MiniLM-L6-v2
|
|
10
|
+
min_topic_size: 2
|
|
11
|
+
|
|
12
|
+
network:
|
|
13
|
+
type: multilayer_repost
|
|
14
|
+
|
|
15
|
+
output_dir: ./out/generic_toy
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# TEMPLATE — Generic CSV/Parquet posts (any platform)
|
|
3
|
+
# Copy this file, rename it, adjust the values below.
|
|
4
|
+
# Run: topiclayers run examples/<your_config>.yml
|
|
5
|
+
# Paths in `input` are resolved relative to THIS file's directory.
|
|
6
|
+
# ============================================================
|
|
7
|
+
|
|
8
|
+
input:
|
|
9
|
+
format: csv_posts # or parquet_posts
|
|
10
|
+
posts: generic/posts.csv # required columns: post_id, user_id, text
|
|
11
|
+
# optional columns: created_at, lang,
|
|
12
|
+
# interaction_type ("original"|"repost"|"quote"|"reply"),
|
|
13
|
+
# target_post_id, mentions (semicolon-separated),
|
|
14
|
+
# any extra_* column is carried through as metadata
|
|
15
|
+
# users: path/to/users.csv # optional user metadata file
|
|
16
|
+
|
|
17
|
+
name: my_dataset # prefixes outputs and cache keys; changing it starts
|
|
18
|
+
# a fresh cache
|
|
19
|
+
|
|
20
|
+
topic_model:
|
|
21
|
+
embedder: all-MiniLM-L6-v2 # sentence-transformers model, or OpenAI
|
|
22
|
+
# "text-embedding-3-small"/"-large" (needs
|
|
23
|
+
# OPENAI_API_KEY in .env)
|
|
24
|
+
min_topic_size: 10 # small datasets need small values; start low and raise
|
|
25
|
+
# until topics look coherent
|
|
26
|
+
nr_topics: auto # "auto", or an int to force reduction after fit
|
|
27
|
+
random_state: 42
|
|
28
|
+
umap_n_neighbors: 15
|
|
29
|
+
umap_n_components: 5
|
|
30
|
+
exact_probabilities: false # true = full membership matrix (slow on big corpora)
|
|
31
|
+
repro_mode: fast # "fast" = parallel, non-reproducible;
|
|
32
|
+
# "strict" = seeded single-threaded, bit-reproducible
|
|
33
|
+
umap_init: pca # default; ~20x faster than spectral, deterministic
|
|
34
|
+
# when seeded (docs/UMAP_INIT_BENCHMARK.md)
|
|
35
|
+
label_model: ollama/qwen2.5:7b # optional LLM topic labeling (LiteLLM string);
|
|
36
|
+
# "" disables. See template_twitter.yml for setup
|
|
37
|
+
|
|
38
|
+
network:
|
|
39
|
+
type: retweet # for CSV data usually "retweet" (or "multilayer_repost"
|
|
40
|
+
# / "ttn" / "all"); requires target_post_id +
|
|
41
|
+
# interaction_type=repost rows to build edges
|
|
42
|
+
project_ttn: true
|
|
43
|
+
|
|
44
|
+
output_dir: ./out/my_dataset
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# TEMPLATE — Twitter/X JSONL input (Academic API dump)
|
|
3
|
+
# Copy this file, rename it, adjust the values below.
|
|
4
|
+
# Run: topiclayers run examples/<your_config>.yml
|
|
5
|
+
# Status from another terminal while running:
|
|
6
|
+
# topiclayers status <output_dir>
|
|
7
|
+
# Paths in `input` are resolved relative to THIS file's directory.
|
|
8
|
+
# ============================================================
|
|
9
|
+
|
|
10
|
+
input:
|
|
11
|
+
format: twitter_jsonl # one JSON tweet per line (Twitter API v2 fields:
|
|
12
|
+
# id, author_id, text, referenced_tweets, entities...)
|
|
13
|
+
posts: twitter/toy.json # required — path to the .json/.jsonl tweet file
|
|
14
|
+
# users: path/to/users.dat # optional — JSONL of user objects
|
|
15
|
+
# ({id, username, public_metrics}); used to map
|
|
16
|
+
# author ids -> usernames in outputs.
|
|
17
|
+
# Delete the line if you don't have it.
|
|
18
|
+
|
|
19
|
+
name: cop22 # dataset name: prefixes output files and cache keys.
|
|
20
|
+
# Changing it starts a fresh cache.
|
|
21
|
+
|
|
22
|
+
topic_model:
|
|
23
|
+
embedder: all-MiniLM-L6-v2 # any sentence-transformers model, or an OpenAI
|
|
24
|
+
# embedding model ("text-embedding-3-small" /
|
|
25
|
+
# "text-embedding-3-large"; requires OPENAI_API_KEY
|
|
26
|
+
# in .env)
|
|
27
|
+
min_topic_size: 50 # smallest cluster HDBSCAN will accept (= BERTopic's
|
|
28
|
+
# granularity knob). Too high -> everything is outlier
|
|
29
|
+
# -1; too low -> thousands of micro-topics.
|
|
30
|
+
# Rule of thumb: ~1/2000 of originals for large sets.
|
|
31
|
+
nr_topics: auto # "auto", or an int to force topic reduction after fit
|
|
32
|
+
random_state: 42 # seed; keep fixed for reproducibility
|
|
33
|
+
umap_n_neighbors: 15 # UMAP locality (kept for reference; pipeline uses
|
|
34
|
+
# adaptive values for small datasets)
|
|
35
|
+
umap_n_components: 5 # UMAP output dims (same note as above)
|
|
36
|
+
exact_probabilities: false # true = full membership matrix (much slower on big
|
|
37
|
+
# corpora); topics are identical either way
|
|
38
|
+
repro_mode: fast # "fast" = parallel UMAP+HDBSCAN (~20 min saved on
|
|
39
|
+
# 180k docs), results vary between runs;
|
|
40
|
+
# "strict" = seeded single-threaded UMAP,
|
|
41
|
+
# bit-reproducible, slower
|
|
42
|
+
umap_init: pca # default; deterministic when seeded and ~20x faster
|
|
43
|
+
# than spectral (see docs/UMAP_INIT_BENCHMARK.md).
|
|
44
|
+
# Pass "spectral" only if you specifically need it.
|
|
45
|
+
label_model: ollama/qwen2.5:7b # LLM that writes a short human-readable label per
|
|
46
|
+
# topic (any LiteLLM model string). Local & free
|
|
47
|
+
# via Ollama (https://ollama.com):
|
|
48
|
+
# ollama serve && ollama pull qwen2.5:7b
|
|
49
|
+
# Cloud examples: "gpt-4o-mini",
|
|
50
|
+
# "anthropic/claude-3-haiku-20240307".
|
|
51
|
+
# Custom OpenAI-compatible server (Unsloth,
|
|
52
|
+
# llama.cpp, vLLM...): prefix with "openai/" and
|
|
53
|
+
# set label_api_base, e.g.
|
|
54
|
+
# label_model: openai/unsloth/Qwen3.8-27B-GGUF
|
|
55
|
+
# label_api_base: http://localhost:8888/v1
|
|
56
|
+
# Requires the labeling extra:
|
|
57
|
+
# pip install 'topiclayers[labeling]'
|
|
58
|
+
# API key from LABEL_API_KEY / OPENAI_LIKE_API_KEY
|
|
59
|
+
# / OPENAI_API_KEY in .env. Set label_model to ""
|
|
60
|
+
# to disable; non-fatal if unreachable.
|
|
61
|
+
# label_context: "" # optional context sentence ("all documents are ...");
|
|
62
|
+
# by default derived from `name` for cop* datasets.
|
|
63
|
+
# Helps the LLM label the specific SUBTOPIC instead
|
|
64
|
+
# of the general domain. Topics are labeled in
|
|
65
|
+
# batches (~40 per call); use a strong model for
|
|
66
|
+
# publishable labels (e.g. gpt-4o-mini, <1 min and
|
|
67
|
+
# cents for a full dataset)
|
|
68
|
+
label_api_base: "" # base URL of a custom OpenAI-compatible endpoint;
|
|
69
|
+
# leave "" for Ollama/OpenAI. Can also be set via
|
|
70
|
+
# LABEL_API_BASE in .env
|
|
71
|
+
|
|
72
|
+
network:
|
|
73
|
+
type: multilayer_repost # one of:
|
|
74
|
+
# multilayer_repost -> retweet graph + multilayer
|
|
75
|
+
# network by topic (uunet)
|
|
76
|
+
# retweet -> retweet graph only (.gml)
|
|
77
|
+
# ttn -> topic-targeted network
|
|
78
|
+
# all -> all of the above
|
|
79
|
+
project_ttn: true # ttn only: also export the projected monopartite
|
|
80
|
+
# version
|
|
81
|
+
|
|
82
|
+
output_dir: ./out/cop22 # everything lands here: networks/, cache/
|
|
83
|
+
# (embeddings, model, labeled df), run_manifest.json,
|
|
84
|
+
# run_status.json
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"lang": "en", "possibly_sensitive": false, "public_metrics": {"retweet_count": 8, "reply_count": 0, "like_count": 0, "quote_count": 0, "impression_count": 0}, "conversation_id": "815345170468208642", "entities": {"mentions": [{"start": 3, "end": 14, "username": "pablorodas", "id": "52119056"}], "hashtags": [{"start": 16, "end": 30, "tag": "CLIMATEchange"}, {"start": 31, "end": 34, "tag": "p2"}, {"start": 112, "end": 118, "tag": "COP22"}, {"start": 119, "end": 124, "tag": "tcot"}, {"start": 125, "end": 128, "tag": "2A"}], "urls": [{"start": 88, "end": 111, "url": "https://t.co/f5lY9m4sE2", "expanded_url": "http://dlvr.it/N0sp69", "display_url": "dlvr.it/N0sp69"}], "annotations": [{"start": 113, "end": 117, "probability": 0.775, "type": "Other", "normalized_text": "COP22"}]}, "referenced_tweets": [{"type": "retweeted", "id": "815344316495237120"}], "text": "RT @pablorodas: #CLIMATEchange #p2 RT Our best environment stories from this past\u00a0year. https://t.co/f5lY9m4sE2 #COP22 #tcot #2A https://t.\u2026", "id": "815345170468208642", "created_at": "2016-12-31T23:53:31.000Z", "author_id": "17227307", "context_annotations": [{"domain": {"id": "25", "name": "Events", "description": ""}, "entity": {"id": "10040144401", "name": "Extreme Weather + Climate Change"}}, {"domain": {"id": "25", "name": "Events", "description": ""}, "entity": {"id": "10040529339", "name": "Discovery VCN - Earth"}}], "edit_history_tweet_ids": ["815345170468208642"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|