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.
Files changed (26) hide show
  1. {topiclayers-0.2.0 → topiclayers-0.2.1}/PKG-INFO +7 -2
  2. {topiclayers-0.2.0 → topiclayers-0.2.1}/README.md +6 -1
  3. {topiclayers-0.2.0 → topiclayers-0.2.1}/pyproject.toml +1 -1
  4. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/__init__.py +1 -1
  5. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/cli.py +26 -1
  6. topiclayers-0.2.1/src/topiclayers/examples/generic/posts.csv +8 -0
  7. topiclayers-0.2.1/src/topiclayers/examples/generic.yml +15 -0
  8. topiclayers-0.2.1/src/topiclayers/examples/template_csv.yml +44 -0
  9. topiclayers-0.2.1/src/topiclayers/examples/template_twitter.yml +84 -0
  10. topiclayers-0.2.1/src/topiclayers/examples/twitter/toy.json +1 -0
  11. {topiclayers-0.2.0 → topiclayers-0.2.1}/LICENSE +0 -0
  12. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/__init__.py +0 -0
  13. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/csv.py +0 -0
  14. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/registry.py +0 -0
  15. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/adapters/twitter.py +0 -0
  16. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/config.py +0 -0
  17. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/__init__.py +0 -0
  18. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/data.py +0 -0
  19. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/network.py +0 -0
  20. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/core/topic.py +0 -0
  21. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/manifest.py +0 -0
  22. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/schema.py +0 -0
  23. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/status.py +0 -0
  24. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/support/__init__.py +0 -0
  25. {topiclayers-0.2.0 → topiclayers-0.2.1}/src/topiclayers/support/cache.py +0 -0
  26. {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.0
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 -e .` |
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 -e .` |
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.0"
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"
@@ -1,3 +1,3 @@
1
1
  """topiclayers — from posts to topical multilayer networks."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -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) < 3 or sys.argv[1] not in ("run", "status"):
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