feat(eda): project_clusters_2d + describe_clusters_llm para el capitulo MODELOS
project_clusters_2d (pura): PCA(2)+KMeans sobre el MISMO subset estandarizado, devolviendo proyeccion 2D y labels alineados por fila + centroides en espacio PCA + perfiles de cluster desestandarizados. Es la pieza que garantiza la alineacion points<->labels que pca_explained y kmeans_segments no cubren (estandarizan por separado y kmeans descarta los labels). Habilita el scatter PCA coloreado por cluster (MUST-8.1). describe_clusters_llm (impura): micro-analisis LLM de los clusters en una sola llamada a ask_llm (grupo claude-direct), devuelve titulo + descripcion por cluster con degradacion dict-no-throw a titulos genericos si el LLM no responde (MUST-8.2). Ambas re-exportadas en datascience/__init__.py. Tests: 6/6 y 9/9 (sin red). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: project_clusters_2d
|
||||
kind: function
|
||||
lang: py
|
||||
domain: datascience
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def project_clusters_2d(columns: dict, k_min: int = 2, k_max: int = 8, max_points: int = 2000) -> dict"
|
||||
description: "PCA a 2D + KMeans sobre el MISMO subset numerico estandarizado, devolviendo proyeccion 2D y labels de cluster ALINEADOS por fila para pintar un scatter PCA coloreado por cluster. Estandariza una sola vez, elige k por silhouette y proyecta centroides al espacio PCA. Determinista."
|
||||
tags: [eda, models, clustering, pca, kmeans, scatter, dimensionality-reduction, datascience, sklearn]
|
||||
params:
|
||||
- name: columns
|
||||
desc: "Mapa {nombre_columna: [valores numericos]}. Listas alineadas por fila (misma longitud). Columnas no numericas o con <2 valores distintos se descartan; None/NaN descartan la fila completa (listwise)."
|
||||
- name: k_min
|
||||
desc: "Numero minimo de clusters a probar por silhouette (default 2). El minimo de filas validas requerido es max(3, k_min*2)."
|
||||
- name: k_max
|
||||
desc: "Numero maximo de clusters a probar (default 8). Se acota a min(k_max, n_filas_validas-1)."
|
||||
- name: max_points
|
||||
desc: "Tope de puntos devueltos en points/labels (default 2000). Si n_used lo supera, points y labels se submuestrean CONJUNTAMENTE con paso determinista para seguir alineados; el fit usa siempre todas las filas."
|
||||
output: "dict con points (proyeccion 2D, posiblemente submuestreada a max_points), labels (cluster de cada point, alineado con points), centers_2d (centroides en espacio PCA, len==best_k), best_k, silhouette, explained_2d ([var PC1, var PC2]), cluster_sizes (sobre n_used total), cluster_profiles (lista de {cluster, size, pct, centroid_original, distinctive top-3 por |z|, centroid_z}), feature_names, n_used (filas del fit antes de muestreo) y note (\"\" si ok). Con <2 columnas numericas o <max(3, k_min*2) filas validas devuelve best_k=0, listas vacias y note 'datos insuficientes' sin lanzar excepcion."
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: [numpy, scikit-learn]
|
||||
tested: true
|
||||
tests: ["test_golden_three_blobs_aligned_projection_and_clusters", "test_edge_subsampling_keeps_points_labels_aligned", "test_edge_single_numeric_column_insufficient", "test_edge_too_few_rows_insufficient", "test_edge_non_numeric_column_dropped_without_error", "test_edge_constant_column_dropped"]
|
||||
test_file_path: "python/functions/datascience/project_clusters_2d_test.py"
|
||||
file_path: "python/functions/datascience/project_clusters_2d.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from datascience.project_clusters_2d import project_clusters_2d
|
||||
|
||||
# Tres grupos gaussianos bien separados sobre 4 features.
|
||||
import numpy as np
|
||||
rng = np.random.default_rng(0)
|
||||
rows = []
|
||||
for center in (np.full(4, 0.0), np.full(4, 12.0), np.array([0.0, 12.0, 0.0, 12.0])):
|
||||
rows.extend(rng.normal(loc=center, scale=0.4, size=(50, 4)))
|
||||
mat = np.array(rows)
|
||||
columns = {f"f{j}": [float(v) for v in mat[:, j]] for j in range(4)}
|
||||
|
||||
res = project_clusters_2d(columns, k_min=2, k_max=8)
|
||||
print(res["best_k"]) # 3
|
||||
print(len(res["points"]), len(res["labels"])) # 150 150 (alineados)
|
||||
print(len(res["centers_2d"])) # == best_k
|
||||
print([round(v, 2) for v in res["explained_2d"]]) # varianza de PC1, PC2
|
||||
# Pintar: scatter(points[:,0], points[:,1], c=labels) + marcar centers_2d.
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando, durante un EDA, quieres un scatter 2D de un dataset tabular numerico
|
||||
coloreado por segmento descubierto automaticamente, y necesitas que cada punto
|
||||
de la proyeccion lleve su etiqueta de cluster correcta. Usala en vez de
|
||||
combinar `pca_explained` + `kmeans_segments` a mano: esas estandarizan por
|
||||
separado y descartan los labels, asi que sus salidas no se pueden cruzar fila a
|
||||
fila. Esta funcion garantiza esa alineacion (mismo X estandarizado para PCA y
|
||||
KMeans) y ademas proyecta los centroides KMeans al espacio PCA para dibujarlos.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Funcion pura y determinista (StandardScaler + PCA random_state=0 + KMeans
|
||||
random_state=0, n_init=10), pero requiere `numpy` y `scikit-learn` instalados.
|
||||
- `points`/`labels` pueden venir submuestreados si `n_used > max_points` (paso
|
||||
determinista `[::ceil(n_used/max_points)]`); `n_used`, `centers_2d`,
|
||||
`cluster_sizes` y `cluster_profiles` se calculan SIEMPRE sobre todas las filas.
|
||||
Cuando hay submuestreo, `note` lo indica.
|
||||
- `centroid_z` y `distinctive` estan en z-score (espacio escalado);
|
||||
`centroid_original` esta en las unidades originales (via
|
||||
`scaler.inverse_transform`). No mezcles ambos al interpretar.
|
||||
- `centers_2d` esta en el espacio PCA (coordenadas del scatter), no en unidades
|
||||
originales: pintalo sobre el mismo eje que `points`.
|
||||
- Silhouette baja con best_k alto sugiere que no hay estructura de cluster real;
|
||||
el scatter puede no mostrar grupos separados.
|
||||
|
||||
## Notas
|
||||
|
||||
Pieza de composicion que `pca_explained` + `kmeans_segments` no cubren: ambas
|
||||
estandarizan internamente por separado (cada una su propio `StandardScaler`) y
|
||||
`kmeans_segments` no expone los labels por fila, por lo que no se pueden cruzar
|
||||
con la `projection` de `pca_explained`. Esta funcion usa `sklearn` directo
|
||||
(StandardScaler una sola vez compartido por PCA y KMeans) para garantizar la
|
||||
alineacion `points[i] <-> labels[i]` y proyectar los centroides KMeans al
|
||||
espacio PCA. Coercion y listwise deletion siguen el estilo de `pca_explained`
|
||||
(None/NaN -> fila descartada, columnas no parseables o constantes descartadas).
|
||||
Degrada con gracia: con <2 columnas numericas o <max(3, k_min*2) filas validas
|
||||
devuelve `note: "datos insuficientes"` sin lanzar excepcion (try/except
|
||||
defensivo en todo el cuerpo).
|
||||
Reference in New Issue
Block a user