Text clustering with embeddings, PCA, DBSCAN/KMeans, and reporting.
Project description
optimal-text-clustering
A practical, configurable toolkit for turning raw text into meaningful clusters with high‑quality reports. It pipelines modern sentence embeddings, dimensionality reduction, and clustering (DBSCAN or K‑Means), then produces an audit‑friendly JSON/CSV report of clusters, exemplars, and per‑sample assignments.
This package is published on PyPI as optimal-text-clustering and is imported as opticlust.
The distribution name reflects the goal of selecting optimal hyper‑parameters for clustering (see below).
Note: The algorithm shines on medium to large datasets (thousands of texts). It works on small toy sets, but structure becomes clearer as data grows.
Features
- Embeddings: Pluggable backends, including Sentence-Transformers (multilingual) and a lightweight TF‑IDF fallback.
- Reduction: PCA with adaptive component selection; optional 2D projection (PCA or t‑SNE) for visualization.
- Clustering: DBSCAN (with epsilon sweep) or K‑Means; cosine or euclidean metrics.
- Reporting: Per-cluster stats, top representatives, per-sample assignments, noise cluster handling, and export to JSON/CSV.
- CLI + Python API: Use it from the command line or integrate in Python notebooks/pipelines.
- CPU‑friendly defaults: Works without a GPU. When available, can auto‑select GPU for faster embeddings.
What does “Optimal” mean?
The term optimal here does not imply finding the mathematically global optimum across all possible clusterings—an intractable problem for arbitrary text data. Instead, this package searches over a family of clustering algorithms and hyper‑parameter settings and scores each candidate using a blend of internal quality metrics and stability checks. By default, the following metrics are combined:
- Silhouette Score (higher is better) – cohesion versus separation of clusters.
- Calinski–Harabasz Score (higher is better) – the ratio of between‑cluster dispersion to within‑cluster dispersion.
- Davies–Bouldin Index (lower is better) – average similarity between each cluster and its most similar peer.
- Clustering Stability (higher is better) – how consistent cluster assignments are under bootstrap resampling.
The package computes a weighted aggregate score for each configuration:
score = α * silhouette + β * CH - γ * DB + δ * stability
where α, β, γ, δ are tunable weights (default to 1) and the Davies–Bouldin index is negated because lower values are better. The configuration with the highest final score is chosen as “optimal.” You can customise the metrics, weights, or optimisation strategy via the API.
In summary, optimal refers to selecting the best available parameter set for a given dataset under the chosen scoring criteria, not a universal optimum.
Installation
From PyPI (recommended)
pip install optimal-text-clustering
Optional dependencies
If you want Sentence-Transformers and PyTorch for high‑quality multilingual embeddings:
pip install "optimal-text-clustering[embeddings]"
This installs dependencies like transformers, sentence-transformers, and torch.
Without extras, the package falls back to TF‑IDF embeddings. This is fast but less semantically rich.
From source
git clone https://github.com/your-org/optimal-text-clustering.git
cd optimal-text-clustering
pip install -e .
Quickstart (Python)
Below is a complete example that you can run as-is. It constructs a small synthetic dataset, fits the pipeline, and prints the report and assignments.
from opticlust import Text2Clusters, EmbeddingConfig, ReductionConfig, ClusterConfig, ReportingConfig
import pandas as pd
# Synthetic dataset for a quick demo
texts = [
"Best pizza in town, the crust is amazing!",
"I love this pizzeria. Great sauce and crispy base.",
"Terrible service at the restaurant. Waited 40 minutes.",
"The waiter was rude and the food arrived cold.",
"The museum exhibition on impressionism was breathtaking.",
"I enjoyed the modern art gallery, especially the sculptures.",
"Football match was exciting, our team scored twice!",
"The coach changed tactics and we won the game.",
"New GPU benchmarks show impressive ray tracing performance.",
]
df = pd.DataFrame({"text": texts})
tc = Text2Clusters(
embedding=EmbeddingConfig(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
device="auto",
batch_size=8,
max_length=256
),
reduction=ReductionConfig(
var_ratio=0.95,
max_components=10,
plot_2d_method="pca" # "pca" or "tsne"
),
clustering=ClusterConfig(
method="dbscan", # "dbscan" or "kmeans"
min_samples=2,
metric="euclidean", # "euclidean" or "cosine"
eps_start=0.1,
eps_end=200.0,
eps_lr=0.1,
use_tsne=False # set True to compute a 2D t-SNE projection
),
reporting_cfg=ReportingConfig(
top_representatives=5
)
)
fit = tc.fit(df, text_col="text")
assignments = fit.result_df # DataFrame with text, label, and any projections
report = fit.report # JSON-like dict with cluster summary
embeddings = fit.embeddings # np.ndarray of embedding vectors
reduced = fit.reduced # np.ndarray of reduced components (e.g., PCA)
print("=== REPORT (summary) ===")
print(report)
print("\n=== ASSIGNMENTS (head) ===")
print(assignments.head())
Using your own CSV
If you have a CSV with a text column:
import pandas as pd
from opticlust import Text2Clusters, EmbeddingConfig, ReductionConfig, ClusterConfig, ReportingConfig
df = pd.read_csv("your_texts.csv") # must contain a 'text' column
tc = Text2Clusters(
embedding=EmbeddingConfig(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
device="auto",
batch_size=8,
max_length=256
),
reduction=ReductionConfig(var_ratio=0.95, max_components=20, plot_2d_method="pca"),
clustering=ClusterConfig(method="dbscan", min_samples=5, metric="cosine", eps_start=0.1, eps_end=50.0, eps_lr=0.2, use_tsne=False),
reporting_cfg=ReportingConfig(top_representatives=5)
)
fit = tc.fit(df, text_col="text")
fit.result_df.to_csv("out_assignments.csv", index=False)
fit.save_report("out_report.json")
Command Line Interface (CLI)
After installation, a console script (e.g., opticlust) should be available. Typical usage:
# Minimal run: read CSV, detect text column automatically if only one string column
opticlust fit \
--input data.csv \
--text-col text \
--method dbscan \
--out-assignments out_assignments.csv \
--out-report out_report.json
More options:
opticlust fit \
--input data.csv \
--text-col text \
--embedding-model "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" \
--device auto \
--batch-size 16 \
--max-length 256 \
--reduction-var-ratio 0.95 \
--reduction-max-components 20 \
--plot-2d pca \
--method dbscan \
--metric cosine \
--min-samples 5 \
--eps-start 0.1 \
--eps-end 50.0 \
--eps-lr 0.2 \
--use-tsne false \
--top-representatives 5 \
--out-assignments out_assignments.csv \
--out-report out_report.json
If you prefer K‑Means:
opticlust fit \
--input data.csv \
--text-col text \
--method kmeans \
--k 8 \
--metric cosine \
--out-assignments out_assignments.csv \
--out-report out_report.json
API Reference (Configs)
EmbeddingConfig
model_name: strSentence-Transformers model id. Use"tfidf"for TF‑IDF fallback.device: str"auto","cpu", or specific like"cuda:0".batch_size: intEmbedding batch size.max_length: intTruncation length for transformer models (ignored for TF‑IDF).
ReductionConfig
var_ratio: floatTarget explained variance ratio for PCA; number of components is auto-chosen.max_components: intHard cap on PCA components.plot_2d_method: str"pca"or"tsne"for 2D projection saved inresult_df.
ClusterConfig
method: str"dbscan"or"kmeans".metric: str"euclidean"or"cosine".- DBSCAN-only:
min_samples: intMinimum points to form a core point.eps_start, eps_end, eps_lr: floatRange and learning rate for epsilon sweep.use_tsne: boolIfTrue, compute a 2D t‑SNE projection for visualization (costly on large data).
- K‑Means-only:
k: int | NoneNumber of clusters. IfNone, the algorithm may pick a heuristic (e.g., sqrt(n)).
ReportingConfig
top_representatives: intNumber of exemplar texts per cluster in the report.- Additional fields may include saving keyword summaries, noise cluster handling, etc., depending on version.
FitResult
result_df: pd.DataFrameOne row per sample with columns liketext,label,pca_x,pca_yortsne_x,tsne_y.report: dictCluster‑level statistics and exemplar texts.embeddings: np.ndarrayHigh‑dimensional embeddings.reduced: np.ndarray | NonePCA‑reduced array (if reduction enabled).
Tips and Guidance
- Scale matters: structure becomes more reliable as you approach thousands of texts. With only dozens, expect less stable clusters.
- Metric: if you normalize embeddings (default when using cosine), try
metric="cosine"for DBSCAN and K‑Means. - DBSCAN tuning: sweep
epsacross a sensible range. Start narrow if your data is dense; widen for varied topics. - Hardware: on CPU, transformer embeddings can be slow. If you have a GPU, set
device="cuda:0"or keepautoand let the library decide. - Caching: for repeated runs on the same data, consider caching embeddings to disk.
- Reproducibility: set
random_statewhere available for deterministic behavior.
Troubleshooting
- ValueError: perplexity must be less than n_samples
You asked for t‑SNE on too few samples. Lowerperplexity(if configurable) or disableuse_tsneuntil you have more data. - Out of memory during embedding
Reducebatch_size, switch to a smaller model, or use TF‑IDF fallback (model_name="tfidf"). - All points labeled -1 (noise) in DBSCAN
Increaseeps, decreasemin_samples, or switch to cosine distance.
License
This project is released under the MIT License. See LICENSE for details.
Changelog
See CHANGELOG.md for notable changes between releases.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file optimal_text_clustering-0.1.0.tar.gz.
File metadata
- Download URL: optimal_text_clustering-0.1.0.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
651381b295075d180eb47aa976702add93e486bae19b4b6c52ec013dbd2ad472
|
|
| MD5 |
e4b2db40c8d23bbe04f4419e840b5dfb
|
|
| BLAKE2b-256 |
6e42b9f2cd740e39323e8f52bdcb1ca2d1fb6e1ddc176b6fdd97f4c53ac419de
|
File details
Details for the file optimal_text_clustering-0.1.0-py3-none-any.whl.
File metadata
- Download URL: optimal_text_clustering-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
626eb9f3179799708f86a8e8537a9ab5275bf8e46e9df78294a87a03b8f0b36a
|
|
| MD5 |
36ea10f8fddeedc413ed58e069681470
|
|
| BLAKE2b-256 |
70a1eed73f40b01d0ba07451ddcd42815a2b1d346601f56cb26651c164103950
|