clustereasy 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.
- clustereasy-0.1.0/LICENSE +21 -0
- clustereasy-0.1.0/PKG-INFO +305 -0
- clustereasy-0.1.0/README.md +274 -0
- clustereasy-0.1.0/pyproject.toml +43 -0
- clustereasy-0.1.0/setup.cfg +4 -0
- clustereasy-0.1.0/src/clustereasy/__init__.py +15 -0
- clustereasy-0.1.0/src/clustereasy/cluster.py +391 -0
- clustereasy-0.1.0/src/clustereasy/evaluation.py +66 -0
- clustereasy-0.1.0/src/clustereasy/exceptions.py +17 -0
- clustereasy-0.1.0/src/clustereasy/preprocessing.py +124 -0
- clustereasy-0.1.0/src/clustereasy/utils.py +10 -0
- clustereasy-0.1.0/src/clustereasy/visualization.py +175 -0
- clustereasy-0.1.0/src/clustereasy.egg-info/PKG-INFO +305 -0
- clustereasy-0.1.0/src/clustereasy.egg-info/SOURCES.txt +21 -0
- clustereasy-0.1.0/src/clustereasy.egg-info/dependency_links.txt +1 -0
- clustereasy-0.1.0/src/clustereasy.egg-info/requires.txt +10 -0
- clustereasy-0.1.0/src/clustereasy.egg-info/top_level.txt +1 -0
- clustereasy-0.1.0/tests/test_agglomerative.py +65 -0
- clustereasy-0.1.0/tests/test_dataframe_output.py +58 -0
- clustereasy-0.1.0/tests/test_evaluation.py +54 -0
- clustereasy-0.1.0/tests/test_input_validation.py +99 -0
- clustereasy-0.1.0/tests/test_kmeans.py +97 -0
- clustereasy-0.1.0/tests/test_visualization.py +54 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shashikant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clustereasy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A beginner-friendly, consistent API for K-Means and Agglomerative Clustering on pandas DataFrames.
|
|
5
|
+
Author: Shashikant
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/shashikant-ds636/easycluster
|
|
8
|
+
Project-URL: Issues, https://github.com/shashikant-ds636/easycluster/issues
|
|
9
|
+
Keywords: clustering,kmeans,hierarchical,machine-learning,pandas
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
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
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pandas>=1.5
|
|
22
|
+
Requires-Dist: numpy>=1.23
|
|
23
|
+
Requires-Dist: scikit-learn>=1.2
|
|
24
|
+
Requires-Dist: scipy>=1.9
|
|
25
|
+
Requires-Dist: matplotlib>=3.6
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
28
|
+
Requires-Dist: build; extra == "dev"
|
|
29
|
+
Requires-Dist: twine; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# clustereasy
|
|
33
|
+
|
|
34
|
+
A beginner-friendly, consistent Python API that combines **K-Means** and
|
|
35
|
+
**Agglomerative (Hierarchical) Clustering** on pandas DataFrames into one
|
|
36
|
+
class: `Cluster`.
|
|
37
|
+
|
|
38
|
+
Instead of re-learning different scikit-learn/SciPy calling conventions for
|
|
39
|
+
each algorithm, you get one object with one set of methods for fitting,
|
|
40
|
+
visualizing, evaluating, and exporting results — for both algorithms.
|
|
41
|
+
|
|
42
|
+
## 1. Why clustereasy
|
|
43
|
+
|
|
44
|
+
- One consistent interface for two different clustering algorithms.
|
|
45
|
+
- Keeps your original DataFrame untouched.
|
|
46
|
+
- Clear, explicit errors instead of silently doing the wrong thing.
|
|
47
|
+
- Advanced users can still pass through scikit-learn parameters.
|
|
48
|
+
|
|
49
|
+
## 2. Installation
|
|
50
|
+
|
|
51
|
+
Local development install (editable):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/shashikant-ds636/easycluster.git
|
|
55
|
+
cd easycluster
|
|
56
|
+
pip install -e ".[dev]"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Once published, it will be installable with:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install clustereasy
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 3. Requirements
|
|
66
|
+
|
|
67
|
+
- Python >= 3.9
|
|
68
|
+
- pandas >= 1.5
|
|
69
|
+
- numpy >= 1.23
|
|
70
|
+
- scikit-learn >= 1.2
|
|
71
|
+
- scipy >= 1.9
|
|
72
|
+
- matplotlib >= 3.6
|
|
73
|
+
|
|
74
|
+
## 4. Quick start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import pandas as pd
|
|
78
|
+
from clustereasy import Cluster
|
|
79
|
+
|
|
80
|
+
df = pd.read_csv("customers.csv")
|
|
81
|
+
|
|
82
|
+
cluster = Cluster(
|
|
83
|
+
data=df,
|
|
84
|
+
features=["Annual Income", "Spending Score"],
|
|
85
|
+
scale=True,
|
|
86
|
+
missing="median",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
cluster.elbow_method(max_clusters=10)
|
|
90
|
+
cluster.fit_kmeans(n_clusters=4, random_state=42)
|
|
91
|
+
cluster.plot_clusters(x="Annual Income", y="Spending Score")
|
|
92
|
+
|
|
93
|
+
print(cluster.evaluate())
|
|
94
|
+
result_df = cluster.get_clustered_data(column_name="Segment")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 5. DataFrame input requirements
|
|
98
|
+
|
|
99
|
+
`Cluster(data=df, features=[...], scale=..., missing=...)`:
|
|
100
|
+
|
|
101
|
+
- `data` must be a non-empty `pandas.DataFrame`. It is never modified.
|
|
102
|
+
- `features`: optional list of column names to cluster on. Defaults to all
|
|
103
|
+
columns. Columns must exist, be unique, and be numeric — clustereasy will
|
|
104
|
+
**not** silently convert text/categorical values into numbers. Encode
|
|
105
|
+
categorical columns yourself first (e.g. `pd.get_dummies`) if you want to
|
|
106
|
+
include them.
|
|
107
|
+
- `missing`: one of `"raise"` (default), `"drop"`, `"mean"`, `"median"`.
|
|
108
|
+
Controls how missing values in the selected features are handled.
|
|
109
|
+
- Infinite values always raise an explicit error — they are never silently
|
|
110
|
+
dropped or converted.
|
|
111
|
+
- `scale`: `False` (default), `True` (same as `"standard"`), or one of
|
|
112
|
+
`"standard"`, `"minmax"`, `"robust"`.
|
|
113
|
+
|
|
114
|
+
## 6. K-Means tutorial
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
cluster = Cluster(data=df, features=["Income", "Score"], scale=True)
|
|
118
|
+
|
|
119
|
+
# Explore how many clusters might make sense
|
|
120
|
+
elbow_table = cluster.elbow_method(max_clusters=10, random_state=42)
|
|
121
|
+
|
|
122
|
+
# Fit
|
|
123
|
+
cluster.fit_kmeans(n_clusters=4, init="k-means++", n_init=10, random_state=42)
|
|
124
|
+
|
|
125
|
+
cluster.cluster_centers() # DataFrame, original feature scale by default
|
|
126
|
+
cluster.inertia() # WCSS for the fitted model
|
|
127
|
+
cluster.predict(new_df) # assign new rows to existing clusters
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Supported `fit_kmeans` parameters: `n_clusters`, `init`, `n_init`, `max_iter`,
|
|
131
|
+
`tol`, `random_state`, `algorithm` — all pass straight through to
|
|
132
|
+
scikit-learn's `KMeans`.
|
|
133
|
+
|
|
134
|
+
## 7. Elbow Method
|
|
135
|
+
|
|
136
|
+
`elbow_method(max_clusters=10, random_state=42, show=True)` fits K-Means for
|
|
137
|
+
k = 1..max_clusters, plots k vs. WCSS (inertia), and returns a DataFrame of
|
|
138
|
+
the results so you can reuse them without refitting.
|
|
139
|
+
|
|
140
|
+
**The elbow is a heuristic**, not a guaranteed optimum — use it together with
|
|
141
|
+
`silhouette_score()` and your domain knowledge, not in isolation.
|
|
142
|
+
|
|
143
|
+
## 8. Hierarchical Clustering tutorial
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
Z = cluster.plot_dendrogram(method="ward", metric="euclidean")
|
|
147
|
+
cluster.fit_agglomerative(n_clusters=3, linkage="ward")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Supported linkage methods: `ward`, `complete`, `average`, `single`.
|
|
151
|
+
`ward` linkage requires `metric="euclidean"` — using any other metric with
|
|
152
|
+
`ward` raises a clear error instead of silently producing wrong results.
|
|
153
|
+
|
|
154
|
+
You can also cluster by distance instead of a fixed cluster count:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
cluster.fit_agglomerative(n_clusters=None, distance_threshold=5.0)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`distance_threshold` and `n_clusters` are mutually exclusive, matching
|
|
161
|
+
scikit-learn's own requirement.
|
|
162
|
+
|
|
163
|
+
**Important:** `AgglomerativeClustering` has no native `predict()` method in
|
|
164
|
+
scikit-learn, and `Cluster.predict()` will raise an explicit error if called
|
|
165
|
+
on an agglomerative model rather than faking support for it.
|
|
166
|
+
|
|
167
|
+
## 9. Dendrogram interpretation
|
|
168
|
+
|
|
169
|
+
- Each leaf is one observation.
|
|
170
|
+
- Each join (inverted "U") is a merge of two clusters; its height is the
|
|
171
|
+
distance at which they were merged.
|
|
172
|
+
- Cutting the dendrogram horizontally at a given height determines how many
|
|
173
|
+
clusters you get at that distance.
|
|
174
|
+
- For large datasets, pass `truncate_mode="lastp"` with a small `p` to avoid
|
|
175
|
+
an unreadable plot — clustereasy will warn you above ~2000 samples if you
|
|
176
|
+
don't.
|
|
177
|
+
|
|
178
|
+
## 10. Evaluation metrics and their limitations
|
|
179
|
+
|
|
180
|
+
All metrics below work without ground-truth labels:
|
|
181
|
+
|
|
182
|
+
| Method | Notes |
|
|
183
|
+
|---|---|
|
|
184
|
+
| `silhouette_score()` | Needs 2..n-1 distinct clusters. |
|
|
185
|
+
| `silhouette_samples()` | Per-sample values, same constraints. |
|
|
186
|
+
| `davies_bouldin_score()` | Lower is better. Needs >= 2 clusters. |
|
|
187
|
+
| `calinski_harabasz_score()` | Higher is better. Needs >= 2 clusters. |
|
|
188
|
+
| `inertia()` | K-Means only — not available for Agglomerative models. |
|
|
189
|
+
| `evaluate()` | One DataFrame with every metric applicable to the fitted model. |
|
|
190
|
+
|
|
191
|
+
These are **diagnostic tools**, not proof of a "correct" clustering — always
|
|
192
|
+
sanity-check results against domain knowledge. If a metric can't be computed
|
|
193
|
+
(e.g. only 1 distinct cluster), `evaluate()` shows `NaN` with a note instead
|
|
194
|
+
of crashing; the individual metric methods raise a `ParameterError`.
|
|
195
|
+
|
|
196
|
+
## 11. Feature scaling and missing-value handling
|
|
197
|
+
|
|
198
|
+
- Distances (and therefore both K-Means and Agglomerative results) are scale
|
|
199
|
+
-sensitive. If your features are on different scales (e.g. income in
|
|
200
|
+
thousands vs. a 1-100 score), set `scale=True`.
|
|
201
|
+
- K-Means cluster centers are available both in the transformed (scaled)
|
|
202
|
+
space (`cluster._model.cluster_centers_`) and inverse-transformed back to
|
|
203
|
+
the original feature scale (`cluster.cluster_centers()`, the default).
|
|
204
|
+
- Missing values are never silently dropped by default — you must choose
|
|
205
|
+
`missing="drop"/"mean"/"median"` explicitly, or clean the data yourself.
|
|
206
|
+
|
|
207
|
+
## 12. Visualization examples
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
cluster.plot_clusters(x="Income", y="Score", show_centers=True)
|
|
211
|
+
cluster.plot_cluster_sizes()
|
|
212
|
+
cluster.plot_silhouette()
|
|
213
|
+
cluster.plot_pca() # for > 2 features; clearly labeled as a 2D projection
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`plot_clusters` requires exactly two selected feature columns to display;
|
|
217
|
+
for higher-dimensional data use `plot_pca()` instead, which is explicitly
|
|
218
|
+
labeled as a projection, not the original feature space.
|
|
219
|
+
|
|
220
|
+
## 13. Cluster labels and DataFrame output
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
labels = cluster.get_labels() # pd.Series, original index
|
|
224
|
+
clustered_df = cluster.get_clustered_data("Segment") # original df + label column
|
|
225
|
+
summary = cluster.cluster_summary() # size, proportion, per-feature mean
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Cluster IDs (0, 1, 2, ...) are arbitrary identifiers — they carry no
|
|
229
|
+
inherent ranking or meaning.
|
|
230
|
+
|
|
231
|
+
## 14. Common errors and solutions
|
|
232
|
+
|
|
233
|
+
| Error | Likely cause | Fix |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `InputValidationError: not numeric` | Selected a text/categorical column | Encode it first, or exclude it from `features` |
|
|
236
|
+
| `InputValidationError: missing values` | NaNs present, `missing="raise"` (default) | Pass `missing="drop"/"mean"/"median"` |
|
|
237
|
+
| `ParameterError: Ward linkage requires metric='euclidean'` | `linkage="ward"` with another metric | Use `metric="euclidean"` or a different linkage |
|
|
238
|
+
| `NotFittedError` | Called an evaluation/plot/output method before `fit_kmeans`/`fit_agglomerative` | Fit a model first |
|
|
239
|
+
| `ParameterError` on `predict()` | Called `predict()` on an Agglomerative model | Not supported — only K-Means supports `predict()` |
|
|
240
|
+
|
|
241
|
+
## 15. Computational limitations
|
|
242
|
+
|
|
243
|
+
- Dendrograms require O(n²) memory/time for the distance matrix and are
|
|
244
|
+
impractical much past a few thousand samples.
|
|
245
|
+
- `AgglomerativeClustering` scales worse than `KMeans` for large n.
|
|
246
|
+
- No GPU acceleration; this wraps scikit-learn/SciPy CPU implementations.
|
|
247
|
+
|
|
248
|
+
## 16. API reference (summary)
|
|
249
|
+
|
|
250
|
+
**Construction:** `Cluster(data, features=None, scale=False, missing="raise")`
|
|
251
|
+
|
|
252
|
+
**K-Means:** `elbow_method`, `fit_kmeans`, `cluster_centers`, `inertia`, `predict`
|
|
253
|
+
|
|
254
|
+
**Agglomerative:** `plot_dendrogram`, `get_linkage_matrix`, `fit_agglomerative`
|
|
255
|
+
|
|
256
|
+
**Shared:** `plot_clusters`, `plot_cluster_sizes`, `plot_silhouette`, `plot_pca`
|
|
257
|
+
|
|
258
|
+
**Evaluation:** `silhouette_score`, `silhouette_samples`, `davies_bouldin_score`,
|
|
259
|
+
`calinski_harabasz_score`, `evaluate`
|
|
260
|
+
|
|
261
|
+
**Output:** `get_labels`, `get_clustered_data`, `cluster_summary`, `elbow_results`
|
|
262
|
+
|
|
263
|
+
## 17. Testing
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
pip install -e ".[dev]"
|
|
267
|
+
pytest -q
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Current suite: **59 tests, all passing**, covering validation, both
|
|
271
|
+
algorithms, visualization, evaluation edge cases, and DataFrame-output
|
|
272
|
+
guarantees (index preservation, immutability, reproducibility).
|
|
273
|
+
|
|
274
|
+
## 18. Contribution guidelines
|
|
275
|
+
|
|
276
|
+
1. Fork, branch from `main`.
|
|
277
|
+
2. Add/update tests for any behavior change.
|
|
278
|
+
3. Run `pytest -q` before opening a PR.
|
|
279
|
+
4. Keep public methods documented with docstrings.
|
|
280
|
+
|
|
281
|
+
## 19. Roadmap
|
|
282
|
+
|
|
283
|
+
- `DBSCAN`, `OPTICS`, `GaussianMixture` support behind the same API.
|
|
284
|
+
- Automatic comparison across multiple `k` / linkage configurations.
|
|
285
|
+
- Model save/load (`joblib`).
|
|
286
|
+
- Export results directly to CSV.
|
|
287
|
+
- Optional connectivity-constrained agglomerative clustering helpers.
|
|
288
|
+
|
|
289
|
+
## 20. Preparing a PyPI release
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
pip install build twine
|
|
293
|
+
python -m build # creates dist/*.whl and dist/*.tar.gz
|
|
294
|
+
twine check dist/*
|
|
295
|
+
pip install dist/clustereasy-0.1.0-py3-none-any.whl # test locally
|
|
296
|
+
twine upload --repository testpypi dist/* # TestPyPI first
|
|
297
|
+
twine upload dist/* # then real PyPI
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Bump the `version` in `pyproject.toml` before each release and update
|
|
301
|
+
`CHANGELOG.md`.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# clustereasy
|
|
2
|
+
|
|
3
|
+
A beginner-friendly, consistent Python API that combines **K-Means** and
|
|
4
|
+
**Agglomerative (Hierarchical) Clustering** on pandas DataFrames into one
|
|
5
|
+
class: `Cluster`.
|
|
6
|
+
|
|
7
|
+
Instead of re-learning different scikit-learn/SciPy calling conventions for
|
|
8
|
+
each algorithm, you get one object with one set of methods for fitting,
|
|
9
|
+
visualizing, evaluating, and exporting results — for both algorithms.
|
|
10
|
+
|
|
11
|
+
## 1. Why clustereasy
|
|
12
|
+
|
|
13
|
+
- One consistent interface for two different clustering algorithms.
|
|
14
|
+
- Keeps your original DataFrame untouched.
|
|
15
|
+
- Clear, explicit errors instead of silently doing the wrong thing.
|
|
16
|
+
- Advanced users can still pass through scikit-learn parameters.
|
|
17
|
+
|
|
18
|
+
## 2. Installation
|
|
19
|
+
|
|
20
|
+
Local development install (editable):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/shashikant-ds636/easycluster.git
|
|
24
|
+
cd easycluster
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Once published, it will be installable with:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install clustereasy
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 3. Requirements
|
|
35
|
+
|
|
36
|
+
- Python >= 3.9
|
|
37
|
+
- pandas >= 1.5
|
|
38
|
+
- numpy >= 1.23
|
|
39
|
+
- scikit-learn >= 1.2
|
|
40
|
+
- scipy >= 1.9
|
|
41
|
+
- matplotlib >= 3.6
|
|
42
|
+
|
|
43
|
+
## 4. Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import pandas as pd
|
|
47
|
+
from clustereasy import Cluster
|
|
48
|
+
|
|
49
|
+
df = pd.read_csv("customers.csv")
|
|
50
|
+
|
|
51
|
+
cluster = Cluster(
|
|
52
|
+
data=df,
|
|
53
|
+
features=["Annual Income", "Spending Score"],
|
|
54
|
+
scale=True,
|
|
55
|
+
missing="median",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
cluster.elbow_method(max_clusters=10)
|
|
59
|
+
cluster.fit_kmeans(n_clusters=4, random_state=42)
|
|
60
|
+
cluster.plot_clusters(x="Annual Income", y="Spending Score")
|
|
61
|
+
|
|
62
|
+
print(cluster.evaluate())
|
|
63
|
+
result_df = cluster.get_clustered_data(column_name="Segment")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 5. DataFrame input requirements
|
|
67
|
+
|
|
68
|
+
`Cluster(data=df, features=[...], scale=..., missing=...)`:
|
|
69
|
+
|
|
70
|
+
- `data` must be a non-empty `pandas.DataFrame`. It is never modified.
|
|
71
|
+
- `features`: optional list of column names to cluster on. Defaults to all
|
|
72
|
+
columns. Columns must exist, be unique, and be numeric — clustereasy will
|
|
73
|
+
**not** silently convert text/categorical values into numbers. Encode
|
|
74
|
+
categorical columns yourself first (e.g. `pd.get_dummies`) if you want to
|
|
75
|
+
include them.
|
|
76
|
+
- `missing`: one of `"raise"` (default), `"drop"`, `"mean"`, `"median"`.
|
|
77
|
+
Controls how missing values in the selected features are handled.
|
|
78
|
+
- Infinite values always raise an explicit error — they are never silently
|
|
79
|
+
dropped or converted.
|
|
80
|
+
- `scale`: `False` (default), `True` (same as `"standard"`), or one of
|
|
81
|
+
`"standard"`, `"minmax"`, `"robust"`.
|
|
82
|
+
|
|
83
|
+
## 6. K-Means tutorial
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
cluster = Cluster(data=df, features=["Income", "Score"], scale=True)
|
|
87
|
+
|
|
88
|
+
# Explore how many clusters might make sense
|
|
89
|
+
elbow_table = cluster.elbow_method(max_clusters=10, random_state=42)
|
|
90
|
+
|
|
91
|
+
# Fit
|
|
92
|
+
cluster.fit_kmeans(n_clusters=4, init="k-means++", n_init=10, random_state=42)
|
|
93
|
+
|
|
94
|
+
cluster.cluster_centers() # DataFrame, original feature scale by default
|
|
95
|
+
cluster.inertia() # WCSS for the fitted model
|
|
96
|
+
cluster.predict(new_df) # assign new rows to existing clusters
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Supported `fit_kmeans` parameters: `n_clusters`, `init`, `n_init`, `max_iter`,
|
|
100
|
+
`tol`, `random_state`, `algorithm` — all pass straight through to
|
|
101
|
+
scikit-learn's `KMeans`.
|
|
102
|
+
|
|
103
|
+
## 7. Elbow Method
|
|
104
|
+
|
|
105
|
+
`elbow_method(max_clusters=10, random_state=42, show=True)` fits K-Means for
|
|
106
|
+
k = 1..max_clusters, plots k vs. WCSS (inertia), and returns a DataFrame of
|
|
107
|
+
the results so you can reuse them without refitting.
|
|
108
|
+
|
|
109
|
+
**The elbow is a heuristic**, not a guaranteed optimum — use it together with
|
|
110
|
+
`silhouette_score()` and your domain knowledge, not in isolation.
|
|
111
|
+
|
|
112
|
+
## 8. Hierarchical Clustering tutorial
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
Z = cluster.plot_dendrogram(method="ward", metric="euclidean")
|
|
116
|
+
cluster.fit_agglomerative(n_clusters=3, linkage="ward")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Supported linkage methods: `ward`, `complete`, `average`, `single`.
|
|
120
|
+
`ward` linkage requires `metric="euclidean"` — using any other metric with
|
|
121
|
+
`ward` raises a clear error instead of silently producing wrong results.
|
|
122
|
+
|
|
123
|
+
You can also cluster by distance instead of a fixed cluster count:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
cluster.fit_agglomerative(n_clusters=None, distance_threshold=5.0)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`distance_threshold` and `n_clusters` are mutually exclusive, matching
|
|
130
|
+
scikit-learn's own requirement.
|
|
131
|
+
|
|
132
|
+
**Important:** `AgglomerativeClustering` has no native `predict()` method in
|
|
133
|
+
scikit-learn, and `Cluster.predict()` will raise an explicit error if called
|
|
134
|
+
on an agglomerative model rather than faking support for it.
|
|
135
|
+
|
|
136
|
+
## 9. Dendrogram interpretation
|
|
137
|
+
|
|
138
|
+
- Each leaf is one observation.
|
|
139
|
+
- Each join (inverted "U") is a merge of two clusters; its height is the
|
|
140
|
+
distance at which they were merged.
|
|
141
|
+
- Cutting the dendrogram horizontally at a given height determines how many
|
|
142
|
+
clusters you get at that distance.
|
|
143
|
+
- For large datasets, pass `truncate_mode="lastp"` with a small `p` to avoid
|
|
144
|
+
an unreadable plot — clustereasy will warn you above ~2000 samples if you
|
|
145
|
+
don't.
|
|
146
|
+
|
|
147
|
+
## 10. Evaluation metrics and their limitations
|
|
148
|
+
|
|
149
|
+
All metrics below work without ground-truth labels:
|
|
150
|
+
|
|
151
|
+
| Method | Notes |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `silhouette_score()` | Needs 2..n-1 distinct clusters. |
|
|
154
|
+
| `silhouette_samples()` | Per-sample values, same constraints. |
|
|
155
|
+
| `davies_bouldin_score()` | Lower is better. Needs >= 2 clusters. |
|
|
156
|
+
| `calinski_harabasz_score()` | Higher is better. Needs >= 2 clusters. |
|
|
157
|
+
| `inertia()` | K-Means only — not available for Agglomerative models. |
|
|
158
|
+
| `evaluate()` | One DataFrame with every metric applicable to the fitted model. |
|
|
159
|
+
|
|
160
|
+
These are **diagnostic tools**, not proof of a "correct" clustering — always
|
|
161
|
+
sanity-check results against domain knowledge. If a metric can't be computed
|
|
162
|
+
(e.g. only 1 distinct cluster), `evaluate()` shows `NaN` with a note instead
|
|
163
|
+
of crashing; the individual metric methods raise a `ParameterError`.
|
|
164
|
+
|
|
165
|
+
## 11. Feature scaling and missing-value handling
|
|
166
|
+
|
|
167
|
+
- Distances (and therefore both K-Means and Agglomerative results) are scale
|
|
168
|
+
-sensitive. If your features are on different scales (e.g. income in
|
|
169
|
+
thousands vs. a 1-100 score), set `scale=True`.
|
|
170
|
+
- K-Means cluster centers are available both in the transformed (scaled)
|
|
171
|
+
space (`cluster._model.cluster_centers_`) and inverse-transformed back to
|
|
172
|
+
the original feature scale (`cluster.cluster_centers()`, the default).
|
|
173
|
+
- Missing values are never silently dropped by default — you must choose
|
|
174
|
+
`missing="drop"/"mean"/"median"` explicitly, or clean the data yourself.
|
|
175
|
+
|
|
176
|
+
## 12. Visualization examples
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
cluster.plot_clusters(x="Income", y="Score", show_centers=True)
|
|
180
|
+
cluster.plot_cluster_sizes()
|
|
181
|
+
cluster.plot_silhouette()
|
|
182
|
+
cluster.plot_pca() # for > 2 features; clearly labeled as a 2D projection
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`plot_clusters` requires exactly two selected feature columns to display;
|
|
186
|
+
for higher-dimensional data use `plot_pca()` instead, which is explicitly
|
|
187
|
+
labeled as a projection, not the original feature space.
|
|
188
|
+
|
|
189
|
+
## 13. Cluster labels and DataFrame output
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
labels = cluster.get_labels() # pd.Series, original index
|
|
193
|
+
clustered_df = cluster.get_clustered_data("Segment") # original df + label column
|
|
194
|
+
summary = cluster.cluster_summary() # size, proportion, per-feature mean
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Cluster IDs (0, 1, 2, ...) are arbitrary identifiers — they carry no
|
|
198
|
+
inherent ranking or meaning.
|
|
199
|
+
|
|
200
|
+
## 14. Common errors and solutions
|
|
201
|
+
|
|
202
|
+
| Error | Likely cause | Fix |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `InputValidationError: not numeric` | Selected a text/categorical column | Encode it first, or exclude it from `features` |
|
|
205
|
+
| `InputValidationError: missing values` | NaNs present, `missing="raise"` (default) | Pass `missing="drop"/"mean"/"median"` |
|
|
206
|
+
| `ParameterError: Ward linkage requires metric='euclidean'` | `linkage="ward"` with another metric | Use `metric="euclidean"` or a different linkage |
|
|
207
|
+
| `NotFittedError` | Called an evaluation/plot/output method before `fit_kmeans`/`fit_agglomerative` | Fit a model first |
|
|
208
|
+
| `ParameterError` on `predict()` | Called `predict()` on an Agglomerative model | Not supported — only K-Means supports `predict()` |
|
|
209
|
+
|
|
210
|
+
## 15. Computational limitations
|
|
211
|
+
|
|
212
|
+
- Dendrograms require O(n²) memory/time for the distance matrix and are
|
|
213
|
+
impractical much past a few thousand samples.
|
|
214
|
+
- `AgglomerativeClustering` scales worse than `KMeans` for large n.
|
|
215
|
+
- No GPU acceleration; this wraps scikit-learn/SciPy CPU implementations.
|
|
216
|
+
|
|
217
|
+
## 16. API reference (summary)
|
|
218
|
+
|
|
219
|
+
**Construction:** `Cluster(data, features=None, scale=False, missing="raise")`
|
|
220
|
+
|
|
221
|
+
**K-Means:** `elbow_method`, `fit_kmeans`, `cluster_centers`, `inertia`, `predict`
|
|
222
|
+
|
|
223
|
+
**Agglomerative:** `plot_dendrogram`, `get_linkage_matrix`, `fit_agglomerative`
|
|
224
|
+
|
|
225
|
+
**Shared:** `plot_clusters`, `plot_cluster_sizes`, `plot_silhouette`, `plot_pca`
|
|
226
|
+
|
|
227
|
+
**Evaluation:** `silhouette_score`, `silhouette_samples`, `davies_bouldin_score`,
|
|
228
|
+
`calinski_harabasz_score`, `evaluate`
|
|
229
|
+
|
|
230
|
+
**Output:** `get_labels`, `get_clustered_data`, `cluster_summary`, `elbow_results`
|
|
231
|
+
|
|
232
|
+
## 17. Testing
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
pip install -e ".[dev]"
|
|
236
|
+
pytest -q
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Current suite: **59 tests, all passing**, covering validation, both
|
|
240
|
+
algorithms, visualization, evaluation edge cases, and DataFrame-output
|
|
241
|
+
guarantees (index preservation, immutability, reproducibility).
|
|
242
|
+
|
|
243
|
+
## 18. Contribution guidelines
|
|
244
|
+
|
|
245
|
+
1. Fork, branch from `main`.
|
|
246
|
+
2. Add/update tests for any behavior change.
|
|
247
|
+
3. Run `pytest -q` before opening a PR.
|
|
248
|
+
4. Keep public methods documented with docstrings.
|
|
249
|
+
|
|
250
|
+
## 19. Roadmap
|
|
251
|
+
|
|
252
|
+
- `DBSCAN`, `OPTICS`, `GaussianMixture` support behind the same API.
|
|
253
|
+
- Automatic comparison across multiple `k` / linkage configurations.
|
|
254
|
+
- Model save/load (`joblib`).
|
|
255
|
+
- Export results directly to CSV.
|
|
256
|
+
- Optional connectivity-constrained agglomerative clustering helpers.
|
|
257
|
+
|
|
258
|
+
## 20. Preparing a PyPI release
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
pip install build twine
|
|
262
|
+
python -m build # creates dist/*.whl and dist/*.tar.gz
|
|
263
|
+
twine check dist/*
|
|
264
|
+
pip install dist/clustereasy-0.1.0-py3-none-any.whl # test locally
|
|
265
|
+
twine upload --repository testpypi dist/* # TestPyPI first
|
|
266
|
+
twine upload dist/* # then real PyPI
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Bump the `version` in `pyproject.toml` before each release and update
|
|
270
|
+
`CHANGELOG.md`.
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "clustereasy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A beginner-friendly, consistent API for K-Means and Agglomerative Clustering on pandas DataFrames."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Shashikant" }]
|
|
13
|
+
keywords = ["clustering", "kmeans", "hierarchical", "machine-learning", "pandas"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pandas>=1.5",
|
|
26
|
+
"numpy>=1.23",
|
|
27
|
+
"scikit-learn>=1.2",
|
|
28
|
+
"scipy>=1.9",
|
|
29
|
+
"matplotlib>=3.6",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=7", "build", "twine"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/shashikant-ds636/easycluster"
|
|
37
|
+
Issues = "https://github.com/shashikant-ds636/easycluster/issues"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""easycluster: a beginner-friendly, consistent API for K-Means and
|
|
2
|
+
Agglomerative Clustering on pandas DataFrames."""
|
|
3
|
+
|
|
4
|
+
from .cluster import Cluster
|
|
5
|
+
from .exceptions import EasyClusterError, InputValidationError, NotFittedError, ParameterError
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Cluster",
|
|
11
|
+
"EasyClusterError",
|
|
12
|
+
"InputValidationError",
|
|
13
|
+
"NotFittedError",
|
|
14
|
+
"ParameterError",
|
|
15
|
+
]
|