FIUBA · Ingeniería · 75.06 / 95.58

Resumen para estudiar

Contenido organizado por tema. Cada sección agrupa la teoría y su práctica correspondiente para estudiar de forma continua.

📖 Teoría ⌨️ Práctica Prof. Dr. Ing. Rodríguez
🎓
01

Introducción y Contexto

Presentación de la materia, el equipo docente, qué es la Ciencia de Datos y por qué Machine Learning ahora.

4 secciones

Equipo docente

Titular

Juan M. Rodríguez

Dr. Ing. — responsable de la cátedra.

Jefa de TPs

Azul Villanueva

Coordinación de trabajos prácticos.

Profesores

Mateo Suster · Franco Mastelli

Clases teóricas y prácticas.

La ciencia de datos no es un campo aislado — convive y se superpone con la estadística clásica, el datamining, el machine learning y la inteligencia artificial. Entender dónde termina uno y empieza el otro ayuda a elegir la herramienta correcta para cada problema.

El ecosistema de modelos

Estadística / Datamining

Regresión lineal · Análisis discriminante · PCA

Métodos clásicos basados en fórmulas y estadística (desde el 1700). Interpretables y reproducibles.

Intersección

Id3 · K-Means · Bayes Naive

Algoritmos compartidos entre datamining y machine learning. Son la base de muchos sistemas modernos.

Machine Learning / IA

Redes Neuronales · SVM

Aprenden patrones complejos a partir de datos, sin reglas programadas explícitamente. Requieren entrenamiento.

Clave conceptual: los modelos de Datamining usan fórmulas y estadística. Los de Machine Learning usan entrenamiento. Muchos modelos (K-Means, Bayes) son usados por ambos campos.

Taxonomía del Machine Learning Clásico

Todos los modelos de ML clásico se dividen en dos grandes grupos según si los datos tienen etiquetas o no:

Supervised — datos pre-categorizados

Clasificación: predecir una categoría. Ej: "¿de qué color es el calcetín?" → divide por color.

Regresión: predecir un número. Ej: "¿qué largo tiene la corbata?" → divide por largo.

Unsupervised — datos sin etiquetas

Clustering: agrupar por similitud. Ej: separar ropa similar en pilas.

Association: encontrar dependencias ocultas. Ej: qué ropa suelo usar junta.

Dimensionality Reduction: generalizar. Ej: hacer los mejores outfits con la ropa disponible.

La ciencia de datos combina estadística, programación y conocimiento del dominio para extraer conocimiento útil de los datos. El punto de partida es siempre entender qué tipo de datos se tienen y qué tipo de problema se quiere resolver.

Tipos de problemas

Variable dep. cualitativa

Clasificación

El modelo predice a cuál categoría pertenece una observación. Las categorías son finitas y conocidas de antemano.

Variable dep. cuantitativa

Regresión

El modelo predice un valor continuo para ciertos datos de entrada. Ej: precio de una casa, temperatura.

Sin variable dependiente

Agrupamiento (Clustering)

No hay etiquetas. El modelo descubre grupos naturales en los datos por características similares.

Definiciones

Géron, 2019

Aprendizaje desde datos

Es la ciencia (y el arte) de programar computadoras para que aprendan a partir de datos.

Mitchell, 1997

Experiencia + Tarea + Rendimiento

Un programa aprende de la experiencia E respecto de una tarea T y una medida R, si su rendimiento en T medido por R mejora con la experiencia E.

Samuel, 1959

Sin programación explícita

Campo de estudio que da a las computadoras la capacidad de aprender sin ser programadas de manera explícita.

🐼
02

Herramientas — Pandas y EDA

Manipulación de datos con Pandas, exploración inicial, filtrado, agrupamiento y análisis exploratorio.

8 secciones

Todo modelo trabaja con variables de entrada (independientes) y variables de salida (dependientes). Entender el tipo de cada variable define qué técnicas se pueden aplicar.

Taxonomía según nivel de medición

TipoSubtipoCaracterística claveEjemplos
Categórica (cualitativa) Nominal Las modalidades solo se distinguen por ser diferentes. No hay orden posible entre ellas. Color de cabello, tipo de auto, sexo
Ordinal Se pueden ordenar las modalidades, pero no medir la distancia entre ellas. Calificación (A, B, C, D, E), estadío de enfermedad (I, II, III, IV)
Cuantitativa Discreta Entre dos valores consecutivos no hay valores intermedios. Asociada al proceso de contar. Cantidad de hijos, materias aprobadas
Continua Entre dos valores cualesquiera existen infinitos valores intermedios. Asociada al proceso de medir. Peso, edad, temperatura

La estadística descriptiva nos permite resumir y entender un conjunto de datos de forma numérica antes de construir cualquier modelo.

Medidas de posición (centro)

MedidaDefiniciónNota
Media (μ̂)Promedio aritmético.Sensible a outliers.
Mediana (Q2)Divide la población en dos mitades.Robusta ante valores extremos.
Q1Deja el 25% de los datos por debajo.Primer cuartil.
Q3Deja el 75% de los datos por debajo.Tercer cuartil.
IQRQ3 − Q1Robusto ante outliers.

Varianza y Desviación Estándar

Miden la dispersión de los datos. La varianza muestral usa n−1 (corrección de Bessel) para dar un estimador más preciso de la varianza poblacional.

s² = Σ(xᵢ − x̄)² / (n−1) ← varianza muestral (usar esta)
s = √s² ← desviación estándar

Correlación de Pearson

Mide la relación lineal entre dos variables. Siempre está entre −1 y 1.

ρ(X,Y) = Cov(X,Y) / (σX · σY) ∈ [−1, 1]

¡Atención! Correlación NO implica causalidad. Que dos variables tengan alto índice de correlación no significa que una cause a la otra.

Pandas (Panel Data System) es la biblioteca principal para manipulación de datos tabulares en Python. Opera de forma vectorizada sobre arrays de NumPy.

1D

Series

Array unidimensional etiquetado. Homogéneo. Tamaño inmutable, contenido mutable.

2D

DataFrame

Estructura bidimensional. Cada columna es una Series. Análogo a una tabla SQL.

El EDA es el proceso de investigar y resumir las características principales de un dataset. Su objetivo es entender la estructura de los datos, detectar anomalías y formular hipótesis antes de modelar.

Flujo típico de EDA

  1. Cargar y explorar: info(), shape, head(), describe()
  2. Detectar valores faltantes: isna().sum()
  3. Detectar duplicados: duplicated().any()
  4. Análisis univariado: distribuciones, value_counts
  5. Análisis bivariado: correlaciones, groupby, crosstab
  6. Visualización: histogramas, boxplots, scatter plots

Acceso por posición y nombre

df.iloc[0]              # fila 0 por posición
df.iloc[0, 2]           # fila 0, columna 2 por posición
df.loc[1, 'apellido']   # fila índice 1, columna 'apellido'
df.iloc[:, [3, 8]]      # todas las filas, columnas 3 y 8

Boolean Indexing — Máscara booleana

# Máscara simple
df_cultura = df[df['reparticion'] == 'Ministerio de Cultura']

# Múltiples condiciones (& = AND, | = OR, ~ = NOT)
df[(df['mes'] == 2) & (df['reparticion'] == 'SECR Ciencia')]

Funciones de agregación

df['mes'].max()
df['salario'].mean()
df.groupby('reparticion')['salario'].mean()
df.sort_values('salario', ascending=False)
df_tracks = pd.read_csv('tracks.csv')
df_tracks.info()
df_tracks.describe()

# Faltantes y duplicados
df_tracks.isna().sum()
df_tracks.dropna()
df_tracks.drop_duplicates()

# Correlación de Pearson
from scipy.stats import pearsonr
corr, _ = pearsonr(df_1['popularity'], df_1['danceability'])

# Matriz de correlación completa
import seaborn as sns
matriz_corr = df_tracks.select_dtypes(include=np.number).corr(method='pearson')
sns.heatmap(matriz_corr, annot=True, cmap='coolwarm')
📊
03

Visualización y Falacias

Tipos de gráficos, cuándo usar cada uno, y errores frecuentes al interpretar datos.

3 secciones

La visualización no es decoración: es una herramienta de análisis y comunicación. El Datasaurus demuestra que conjuntos de datos radicalmente distintos pueden tener exactamente los mismos estadísticos (media, desviación, correlación).

Conclusión: los estadísticos resumen son insuficientes. Siempre visualizar los datos antes de sacar conclusiones.

Incluso con datos reales y correctos se pueden sacar conclusiones completamente equivocadas.

Paradoja de Simpson

Una tendencia global puede invertirse o desaparecer al segmentar por subgrupos. Ocurre cuando hay una variable confundidora no controlada. Ejemplo: un tratamiento puede parecer peor globalmente, pero al segmentar por tamaño del cálculo renal, resulta ser mejor en cada subgrupo.

Regla práctica: antes de sacar conclusiones de datos agregados, siempre preguntarse si hay subgrupos relevantes que puedan invertir el resultado.

import seaborn as sns
import matplotlib.pyplot as plt

# Barras
sns.countplot(x='species', data=iris)

# Histograma
sns.histplot(data=df['Age'], alpha=0.5)

# Scatter con colores por clase
sns.scatterplot(x='petal_length', y='petal_width', hue='species', data=iris)

# Boxplot
sns.boxplot(x='species', y='sepal_length', data=iris)

# Pairplot completo (EDA inicial)
sns.pairplot(iris, hue='species', height=2)

# Heatmap de correlación
sns.heatmap(publicidad.corr(), annot=True)
📈
04

Regresión

Regresión lineal simple y múltiple, métricas de error (MSE, RMSE, MAE) y covarianza/correlación.

2 secciones

En regresión se busca predecir un valor continuo dado ciertos valores de entrada. Ejemplos: temperatura de mañana, valor de una propiedad, ventas del próximo mes.

ŷ = a + b·x (regresión lineal simple)

Métricas de regresión

MétricaFórmulaCaracterística
RMSE√( (1/m) · Σ(ŷᵢ − yᵢ)² )Mismas unidades que y. Penaliza errores grandes. Sensible a outliers.
MAE(1/m) · Σ|ŷᵢ − yᵢ|Robusto ante outliers. Penaliza todos los errores por igual.
MSE(1/m) · Σ(ŷᵢ − yᵢ)²Base del RMSE. Usado internamente en el entrenamiento.
from sklearn.linear_model import LinearRegression
from sklearn import metrics

modelo_tv = LinearRegression()
tv     = publicidad['tv'].values.reshape(-1, 1)
ventas = publicidad['ventas'].values.reshape(-1, 1)

modelo_tv.fit(tv, ventas)

B0 = round(modelo_tv.intercept_[0], 2)   # intercepto
B1 = round(modelo_tv.coef_[0][0], 2)     # pendiente

print(f"ventas = {B1} * tv + {B0}")

Interpretación: β₀ = ventas promedio cuando no se invierte en TV. β₁ = incremento en ventas por cada millón adicional invertido en TV.

🎯
05

Clasificación y Métricas

Clasificación binaria y múltiple, matriz de confusión, precisión, recall, F1, curva ROC y AUC. Con ejemplos del práctico.

7 secciones

Se busca determinar a cuál categoría del conjunto C pertenece la observación. Las categorías son finitas y conocidas de antemano. El modelo trabaja con un dataset etiquetado (supervisado): cada fila tiene un valor real conocido que el modelo aprende a predecir.

Diferencia con Regresión: en clasificación el resultado es una categoría (spam / no spam, perro / gato / pájaro). En regresión el resultado es un número continuo (precio, temperatura).

Cuando creamos distintos modelos de clasificación nos interesa conocer si el modelo está clasificando correctamente lo que queremos. La matriz de confusión es la herramienta base para entender los errores del modelo.

Trabajamos con modelos de clasificación supervisada: el modelo predice una categoría y la data está pre-categorizada (labeled dataset). Es muy importante tener bien claro qué es lo que estamos midiendo con cada métrica, ya que distintas métricas miden cosas distintas.

Los 4 componentes

SiglaNombreDescripción
TPTrue PositiveCantidad de clasificaciones positivas correctas. El modelo predijo positivo y era positivo. ✓
TNTrue NegativeCantidad de clasificaciones negativas correctas. El modelo predijo negativo y era negativo. ✓
FPFalse PositiveCantidad de clasificaciones positivas incorrectas. El modelo predijo positivo pero era negativo. ✗
FNFalse NegativeCantidad de clasificaciones negativas incorrectas. El modelo predijo negativo pero era positivo. ✗

Objetivo: queremos que la diagonal principal (TP y TN) sea lo más alta posible — significa que el modelo está acertando. Los cuadros fuera de la diagonal son los errores.

Fórmulas de todas las métricas

Precision = TP / (TP + FP) ← De lo que predije positivo, ¿cuánto era realmente positivo?
Recall = TPR = TP / (TP + FN) ← De todo lo positivo real, ¿cuánto encontré?
FPR = FP / (FP + TN) ← De todo lo negativo real, ¿cuánto clasifiqué mal?
F₁-score = 2 × (Precision × Recall) / (Precision + Recall)
Accuracy = (TP + TN) / (TP + TN + FP + FN)

Ejemplo numérico del práctico

Supongamos esta matriz de confusión:

Ejemplo: TP=2, TN=1, FP=1, FN=1

Precision = TP/(TP+FP) = 2/(2+1) = 0.667

Recall = TPR = TP/(TP+FN) = 2/(2+1) = 0.667

FPR = FP/(FP+TN) = 1/(1+1) = 0.5

¿Cuándo usar Precisión vs Recall?

ObjetivoMétrica a maximizarEjemplo
Quiero que lo que predigo positivo sea correctoPrecisión"Los partidos que apuesto, ganar"
Quiero capturar todos los positivos realesRecall"No quiero perderme ningún caso de cáncer"
Balance entre ambasF1-scoreCuando ambas importan por igual

Limitación de la Accuracy: puede ser engañosa con clases desbalanceadas. Si el 95% de los emails son legítimos y el modelo siempre predice "legítimo", tiene 95% de Accuracy pero no detecta ningún spam. En esos casos, Precisión, Recall y F1 son más informativas.

La tabla de confusión no tiene por qué ser fija. Si modificamos el modelo (le cambiamos hiperparámetros) puede obtenerse predicciones diferentes, haciendo que cambie su tabla de confusión. Cada configuración del modelo tiene su propio par (TPR, FPR).

¿Qué es la curva ROC?

Al hacer esto muchas veces para distintas configuraciones y graficar la proporción entre TPR (eje Y) y FPR (eje X) para cada tabla de confusión, nos quedamos con una curva ROC (Receiver Operating Characteristic). Cada punto de la curva corresponde a un umbral de clasificación diferente.

TPR es sinónimo de Recall. Graficar TPR vs FPR para todos los umbrales posibles nos muestra el tradeoff del modelo.

AUC — Área Bajo la Curva

El AUC (Area Under Curve) resume la curva ROC en un solo número:

AUC = 1.0

Clasificador perfecto

Distingue perfectamente todas las clases. La curva pasa por el punto (0, 1) — TPR=1, FPR=0.

AUC = 0.5

Clasificador aleatorio

Equivale a tirar una moneda. La curva es la diagonal. No aporta información.

AUC > 0.7

Clasificador útil

Cuanto más alto el AUC, mejor el modelo en general. Un buen modelo suele tener AUC ≥ 0.8.

Curva Precision-Recall vs ROC: cuando las clases están muy desbalanceadas (ej: muy pocos casos positivos), la curva Precision-Recall es más informativa que la ROC.

Antes de entrenar un modelo es fundamental dividir bien los datos. Si usáramos todos los datos para entrenar, no tendríamos forma de saber si el modelo realmente aprendió o simplemente memorizó.

División del dataset

~60–80% — Train

Entrenamiento

El modelo aprende los parámetros ajustando sus pesos con estos datos. Nunca se usa para evaluar.

~20% — Validation

Validación

Se usa para ajustar hiperparámetros y comparar modelos durante el desarrollo.

~20% — Test

Prueba

Se usa una sola vez al final para medir el rendimiento real.

Validación Cruzada (Cross-Validation)

En vez de dividir los datos una sola vez, la validación cruzada hace múltiples divisiones rotando qué parte es el conjunto de prueba. Esto da una estimación más robusta.

K-Fold Cross Validation

Se divide el dataset en K grupos (folds). En cada iteración, uno de los K grupos actúa como datos de prueba y los K−1 restantes como datos de entrenamiento. Se entrena K veces y se promedia el resultado.

Overfitting y Underfitting

Problema¿Qué ocurre?Error trainError test
Underfitting El modelo es demasiado simple. No captura los patrones del dataset. Alto Alto
Overfitting El modelo memorizó los datos de entrenamiento incluyendo el ruido. Funciona perfecto en train pero falla en datos nuevos. Muy bajo Alto
Balanced El modelo aprendió los patrones generales sin memorizar el ruido. Generaliza bien. Bajo Bajo

Datasets desbalanceados

Undersampling

Se reducen los ejemplos de la clase mayoritaria hasta igualar la cantidad de la clase minoritaria. Se pierde información pero se balancea el dataset.

Oversampling

Se duplican los ejemplos de la clase minoritaria hasta igualar la clase mayoritaria. No se pierde información pero puede causar overfitting.

from sklearn.metrics import (confusion_matrix, precision_score,
                             recall_score, f1_score, accuracy_score)

y_true = [1,1,0,1,1,1,1,1,0,1]   # resultados reales
y_pred = [1,1,1,1,1,1,1,1,1,1]   # predicciones del modelo

cm = confusion_matrix(y_true, y_pred)
print("Matriz de confusión:\n", cm)

precision = precision_score(y_true, y_pred)   # 0.80
recall    = recall_score(y_true, y_pred)      # 1.00
f1        = f1_score(y_true, y_pred)          # 0.89
accuracy  = accuracy_score(y_true, y_pred)    # 0.80

Conclusión práctica: el modelo que siempre dice "True" (Modelo 1) gana en todas las métricas al modelo aleatorio. Esto ilustra que casi siempre un modelo aleatorio es el peor caso posible. La forma de mejorar un modelo es ajustando sus hiperparámetros.

from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import RocCurveDisplay
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split

X, y = load_wine(return_X_y=True)
y = (y == 2)   # clasificador binario: tipo 2 vs resto

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# Modelo SVC
svc = SVC(random_state=42)
svc.fit(X_train, y_train)
svc_disp = RocCurveDisplay.from_estimator(svc, X_test, y_test)

# Comparar con Random Forest
rfc = RandomForestClassifier(n_estimators=10, random_state=42)
rfc.fit(X_train, y_train)
ax = plt.gca()
RocCurveDisplay.from_estimator(rfc, X_test, y_test, ax=ax, alpha=0.8)
svc_disp.plot(ax=ax, alpha=0.8)
plt.show()

Calcular AUC

from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

X, y = load_breast_cancer(return_X_y=True)
clf = LogisticRegression(solver='liblinear', random_state=0).fit(X, y)
auc = roc_auc_score(y, clf.predict_proba(X)[:, 1])
print(f'AUC = {auc:.4f}')

El Gradient Descent (GD) es el algoritmo iterativo que usan muchos modelos de ML para encontrar los parámetros óptimos. El objetivo es minimizar una función de pérdida (Loss) — una función que mide qué tan mal se está comportando el modelo.

Paso 1

Tomar un valor aleatorio para el parámetro. Calcular el Loss inicial.

Paso 2

Calcular la derivada de la función de pérdida en ese punto. Indica en qué dirección crece el error.

Paso 3

Moverse en la dirección opuesta al gradiente. Tamaño del paso: learning rate × derivada.

Paso 4

Repetir hasta convergencia: cuando la derivada es cercana a cero (estamos en el mínimo).

¡Cuidado con el learning rate! Si es muy grande podemos saltarnos el mínimo. Si es muy pequeño el entrenamiento es muy lento. 0.1 es un valor estándar de punto de partida.

GD clásico — lento

Usa todos los ejemplos para calcular cada paso. Con millones de datos, cada iteración es muy costosa.

SGD — eficiente

Usa 1 ejemplo al azar (o mini-batch) por paso. Mucho más rápido. El SGDClassifier de sklearn implementa esto.

📐
06

Regresión Logística

Clasificación binaria y multiclase con función sigmoide, frontera de decisión, interpretación de coeficientes, one-hot encoding, preprocesamiento y validación cruzada.

6 secciones

Es tentador usar regresión lineal para clasificar: asignar 0 a una clase y 1 a la otra, ajustar una recta y luego usar un umbral (ej: si predice ≥ 0.5 → clase 1). Para casos simples puede funcionar, pero tiene problemas fundamentales:

  • No genera probabilidades: un modelo lineal solo interpola números entre los puntos. No podemos interpretar la salida como una probabilidad real.
  • Predice valores fuera de [0,1]: puede dar valores negativos o mayores a 1, lo que no tiene sentido como probabilidad.
  • El umbral pierde sentido: si hay outliers extremos, la recta se desplaza y el punto de corte deja de funcionar.
  • Mal con múltiples clases: al etiquetar las clases como 0, 1, 2… se impone un orden numérico artificial que puede no existir en la realidad.

Para resolver estos problemas se usa la regresión logística, que modela directamente la probabilidad de pertenencia a una clase usando una función que siempre da valores en [0, 1].

En lugar de ajustar una línea recta, la regresión logística usa la función logística (sigmoide) para comprimir la salida entre 0 y 1. Esa salida se interpreta como una probabilidad.

s(z) = 1 / (1 + e^(-z))

Donde z es la combinación lineal de los inputs: z = β₀ + β₁·x₁ + β₂·x₂ + ...

La función sigmoide tiene forma de "S": cuando z → +∞, s(z) → 1; cuando z → −∞, s(z) → 0; cuando z = 0, s(z) = 0.5.

Umbral de decisión: si s(x) ≥ 0.5 → clase 1. Si s(x) < 0.5 → clase 0. El umbral 0.5 es el estándar, pero se puede cambiar según el problema (por ej, en medicina puede preferirse un umbral más bajo para no perder casos).

La Frontera de Decisión

El umbral 0.5 corresponde exactamente a cuando z = 0, es decir: β₀ + β₁·x₁ + β₂·x₂ = 0. Esto define un hiperplano (en 2D, una línea recta) que separa el espacio en dos regiones:

  • De un lado: s(x) ≥ 0.5 → clase 1.
  • Del otro lado: s(x) < 0.5 → clase 0.
Ejemplo con 2 variables (x₁, x₂)

La frontera es: β₀ + β₁·x₁ + β₂·x₂ = 0

Despejando x₂: x₂ = −(β₁/β₂)·x₁ − (β₀/β₂)

La pendiente es −β₁/β₂ y el intercepto es −β₀/β₂. Esta recta divide el espacio de características en dos clases.

La interpretación de los coeficientes en regresión logística es más compleja que en regresión lineal porque la salida no es lineal en probabilidades.

¿Qué son los Log-Odds?

Para interpretar los coeficientes hay que transformar la probabilidad en odds y luego en log-odds:

Odds = P / (1 − P) → "probabilidad de que ocurra / de que no ocurra"
Log-odds = ln(P / (1 − P)) = β₀ + β₁·X₁ + β₂·X₂ + ...

La regresión logística predice linealmente los log-odds. Esto implica:

  • Si una variable Xⱼ aumenta en 1 unidad → el log-odds aumenta en βⱼ.
  • En términos de odds: los odds se multiplican por e^βⱼ.
Ejemplo de interpretación

Si β = 0.7, entonces e^0.7 ≈ 2. Esto significa que los odds se duplican al aumentar X en una unidad. Es decir, la probabilidad de la clase positiva aumenta, pero cuánto depende del valor actual de la probabilidad.

Tipos de variables

  • Variable numérica: al aumentar 1 unidad, los odds cambian por un factor de e^β.
  • Variable categórica binaria: cambiar de la categoría de referencia (0) a la otra (1) cambia los odds por e^β.
  • Variable categórica múltiple: se usa one-hot encoding con una categoría de referencia.

No es lineal en probabilidades: aunque el modelo es lineal en log-odds, en términos de probabilidad el efecto de aumentar una variable es diferente si estamos en probabilidades bajas vs probabilidades altas.

Para usar variables categóricas en regresión logística (y muchos otros modelos), es necesario transformarlas. La técnica más usada es el one-hot encoding.

¿Qué es One-Hot Encoding?

Convierte una variable categórica con N categorías en N−1 columnas binarias (0 o 1). Se omite una categoría (la "de referencia") para evitar redundancia.

Ejemplo: columna "Sexo" con valores ["M", "F"]

Se convierte en una columna "Sex_female": 1 si es femenino, 0 si es masculino. La categoría masculino es la "de referencia".

# En pandas se hace con get_dummies
ds_trabajo = pd.get_dummies(ds_trabajo, columns=["Pclass", "Sex", "Embarked"])

# Resultado: columnas Pclass_1, Pclass_2, Sex_female, Embarked_C, Embarked_Q, Embarked_S

¿Por qué N−1 y no N columnas?

Si usáramos N columnas, una de ellas sería redundante (la última es siempre la que queda cuando las otras son 0). Esto crea problemas de multicolinealidad perfecta en el modelo. Por eso se descarta una categoría (la de referencia).

Dataset: Titanic. Objetivo: predecir si un pasajero sobrevivió (Survived = 1 o 0).

1. Carga y exploración

ds_train = pd.read_csv('ds_titanic.csv')
ds_trabajo = ds_train.copy()

ds_trabajo.shape        # (891, 12)
ds_trabajo.columns.tolist()
ds_trabajo.head()

2. Limpieza de datos

# Eliminar columnas no útiles
columnas_eliminar = ['PassengerId', 'Name', 'Ticket']
ds_trabajo.drop(columnas_eliminar, axis='columns', inplace=True)

# Ver porcentaje de nulos
filas_totales = ds_trabajo.shape[0]
print(ds_trabajo.isna().sum() / filas_totales * 100)

# Eliminar columnas con demasiados nulos (ej: Cabin tiene ~77%)
ds_trabajo.drop(['Cabin'], axis='columns', inplace=True)

# Eliminar filas con nulos restantes
ds_trabajo = ds_trabajo.dropna()

3. One-Hot Encoding

# Convertir variables categóricas a columnas numéricas
ds_trabajo = pd.get_dummies(ds_trabajo, columns=["Pclass", "Sex", "Embarked"])
# Resultado: Pclass_1, Pclass_2, Sex_female, Embarked_C, Embarked_Q, Embarked_S

4. División Train / Test

from sklearn.model_selection import train_test_split

columnas_modelo = ds_trabajo.columns.tolist()
columnas_modelo.remove('Survived')    # target

X = ds_trabajo[columnas_modelo]
y = ds_trabajo['Survived']

x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2)
# 80% entrenamiento, 20% test

5. Modelo 1: Variables Pclass, Sex y Age

from sklearn.linear_model import LogisticRegression

columnas_modelo1 = ['Pclass_1', 'Pclass_2', 'Sex_female', 'Age']

modelo1 = LogisticRegression()
modelo1.fit(x_train[columnas_modelo1], y_train)

# Ver coeficientes
coeficientes = pd.DataFrame(modelo1.coef_[0], columnas_modelo1, columns=['coef'])
print(coeficientes)

# Predecir
y_pred = modelo1.predict(x_test[columnas_modelo1])
y_pred_proba = modelo1.predict_proba(x_test[columnas_modelo1])[:, 1]

6. Evaluar con matriz de confusión y ROC

from yellowbrick.classifier import ROCAUC
import seaborn as sns, matplotlib.pyplot as plt, pandas as pd

# Tabla de contingencia (confusión)
ds_cm = pd.DataFrame(zip(y_test, y_pred_proba), columns=["Survived", "Prob"])
ds_cm["Survived_predict"] = (ds_cm["Prob"] >= 0.50).astype(int)

tabla = pd.crosstab(ds_cm['Survived'], ds_cm['Survived_predict'])
sns.heatmap(tabla, annot=True, cmap='Blues')
plt.show()

# Curva ROC-AUC
visualizer = ROCAUC(modelo1, binary=True)
visualizer.fit(x_train[columnas_modelo1], y_train)
visualizer.score(x_test[columnas_modelo1], y_test)
visualizer.show()

7. Validación cruzada

from sklearn.linear_model import LogisticRegressionCV

# CV=5 folds, métrica AUC
modelo_cv = LogisticRegressionCV(cv=5, scoring='roc_auc')
modelo_cv.fit(x_train[columnas_modelo1], y_train)

# Ver AUC de cada fold
for i, auc_score in enumerate(modelo_cv.scores_[1], 1):
    print(f"Fold {i}: AUC = {auc_score:.4f}")

print(f"Promedio AUC: {modelo_cv.scores_[1].mean():.4f}")
print(f"C elegido: {modelo_cv.C_}")

¿Qué es C? Es el parámetro de regularización de la regresión logística. Controla qué tan "estricto" es el modelo. C pequeño → modelo más simple (regularización fuerte). C grande → modelo más complejo (regularización débil). La validación cruzada elige el mejor C automáticamente.

Dataset: Iris (3 clases: setosa, versicolor, virginica). La regresión logística también funciona con múltiples clases.

Carga y exploración visual

from sklearn.datasets import load_iris

iris = load_iris()
nombres_columnas = [x.replace('(cm)','').rstrip().replace(" ","_")
                    for x in iris['feature_names']]

df_iris = pd.DataFrame(iris['data'], columns=nombres_columnas)
y_iris  = iris['target']

# Violin plot por clase
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 6))
for f, f_name in enumerate(iris['feature_names']):
    plt.subplot(1, 4, f+1)
    data = np.zeros((50, 3))
    for k in range(3):
        data[:, k] = iris.data[iris.target==k, f]
    plt.violinplot(data)
    plt.ylabel(f_name)
    plt.xticks([1,2,3], labels=iris['target_names'], rotation=70)
plt.show()

Se puede observar que la categoría Setosa se distingue del resto fácilmente usando 2 atributos. Sin embargo Versicolor y Virginica no son tan fácilmente separables.

Modelo con 2 variables

df_iris_2_vars = df_iris[['petal_length', 'petal_width']]

clf_2_vars = LogisticRegression(multi_class='multinomial')
clf_2_vars.fit(df_iris_2_vars, y_iris)

# Visualizar la frontera de decisión (malla de predicciones)
x1_min, x1_max = df_iris.petal_length.min(), df_iris.petal_length.max()
x2_min, x2_max = df_iris.petal_width.min(),  df_iris.petal_width.max()
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1),
                       np.arange(x2_min, x2_max, 0.1))
XX   = np.c_[xx1.ravel(), xx2.ravel()]
pred = clf_2_vars.predict(XX).reshape(xx1.shape)

plt.contourf(xx1, xx2, pred, cmap=plt.cm.Pastel2)
plt.scatter(df_iris_2_vars.petal_length, df_iris_2_vars.petal_width,
            c=colors, marker='.')
plt.show()

Modelo completo con train/test + métricas

X_train, X_test, y_train, y_test = train_test_split(df_iris, y_iris,
                                                    test_size=0.2, random_state=1)

clf_iris = LogisticRegressionCV(cv=5, multi_class='multinomial')
clf_iris.fit(X_train, y_train)

y_pred = clf_iris.predict(X_test)

# Métricas multiclase (requieren parámetro average)
from sklearn import metrics
print(f"Accuracy:  {metrics.accuracy_score(y_test, y_pred):.4f}")
print(f"Recall:    {metrics.recall_score(y_test, y_pred, average='micro'):.4f}")
print(f"Precision: {metrics.precision_score(y_test, y_pred, average='micro'):.4f}")

¿Por qué average='micro'? Para la clasificación multiclase hay que agregar los resultados de todas las clases. micro cuenta todos los TP, FP, FN globalmente. Otras opciones: macro (promedio sin ponderar), weighted (promedio ponderado por cantidad de ejemplos).

Visualizar la matriz de confusión

from sklearn.metrics import confusion_matrix
import seaborn as sns

cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, cmap='Blues',
            xticklabels=iris['target_names'],
            yticklabels=iris['target_names'])
plt.show()

La diagonal de la matriz de confusión multiclase muestra los aciertos por clase. Los valores fuera de la diagonal indican qué clases se confunden entre sí. Típicamente versicolor y virginica se confunden más que setosa (que es muy diferente a las otras dos).

🔵
07

Clustering

Aprendizaje no supervisado: K-Means, método del codo (Elbow), coeficiente de Silhouette, estadístico de Hopkins y ejemplos prácticos.

5 secciones

El clustering es aprendizaje no supervisado: no hay etiquetas. El objetivo es agrupar observaciones para un número predefinido de grupos, de modo que los elementos de cada grupo sean similares entre sí. El significado de los grupos lo interpreta el analista.

Diferencia clave con clasificación: en clasificación tenemos etiquetas (sabemos a qué clase pertenece cada dato y el modelo aprende de eso). En clustering no hay etiquetas — el modelo descubre la estructura por sí solo.

K-Means es el algoritmo de clustering más popular. Agrupa observaciones en K grupos donde el centro de cada grupo es la media aritmética de todas las observaciones pertenecientes a ese grupo.

Pasos del algoritmo

Paso 1

El usuario elige K. K-Means elige K centroides al azar.

Paso 2

Cada punto se asigna al centroide más cercano (basado en distancia euclidiana).

Paso 3

Se recalcula cada centroide como el promedio de todos los puntos de su grupo.

Paso 4

Se reasignan los puntos. Repetir pasos 2 y 3 hasta convergencia (cuando los centroides ya no se mueven).

Problema de inicialización: como los centroides iniciales son al azar, el resultado puede variar entre ejecuciones. Usar random_state fija la semilla para reproducibilidad.

Limitaciones de K-Means

  • Hay que definir K antes: no es obvio cuántos grupos hay en los datos. Para esto se usan el método del Codo y Silhouette.
  • Fronteras circulares: K-Means tiende a detectar grupos con formas circulares. Falla con formas complejas (como medias lunas o espirales).
  • Sensible a la escala: como se basa en distancias, variables con distintas unidades afectan el resultado. Hay que estandarizar antes de aplicar.

En clustering no hay etiquetas reales, entonces ¿cómo sabemos si el resultado es bueno? Se usan métricas que miden la cohesión (qué tan compactos son los grupos) y la separación (qué tan distintos son entre sí).

Método del Codo (Elbow)

Calcula el WCSS (Within-Cluster Sum of Squares) — la suma de distancias al cuadrado de cada punto a su centroide — para diferentes valores de K. A medida que K aumenta, el WCSS disminuye. El punto donde la mejora deja de ser significativa forma un "codo" en el gráfico → ese K es el óptimo.

# Calcular WCSS para K de 1 a 9
sse = []
for k in range(1, 10):
    km = KMeans(n_clusters=k)
    km.fit(X)
    sse.append(km.inertia_)   # .inertia_ es el WCSS

plt.plot(range(1, 10), sse, '-o')
plt.xlabel('Cantidad de clusters K')
plt.ylabel('WCSS (Inercia)')
plt.show()

Índice de Silhouette

Evalúa la calidad del clustering midiendo para cada punto:

  • a(i): distancia promedio de i a todos los puntos de su propio cluster (cohesión — qué tan bien ajusta).
  • b(i): distancia promedio de i a todos los puntos del cluster más cercano distinto al suyo (separación).
s(i) = (b(i) − a(i)) / max{a(i), b(i)}

El índice oscila en [−1, 1]:

s(i) ≈ 1

Bien asignado

El punto está bien dentro de su cluster y lejos de los demás. El clustering es bueno.

s(i) ≈ 0

En la frontera

El punto está en el límite entre dos clusters. Podría pertenecer a cualquiera.

s(i) < 0

Mal asignado

El punto probablemente pertenece al cluster vecino, no al que se le asignó.

from sklearn.metrics import silhouette_score

for k in range(2, 6):
    km    = KMeans(n_clusters=k)
    preds = km.fit_predict(X)
    score = silhouette_score(X, preds)
    print(f"K={k} → Silhouette = {score:.4f}")

¿Cómo elegir K? Usar ambas métricas: el Elbow da el K donde la mejora se estabiliza; el Silhouette da el K con mejor calidad de clusters. Si ambos coinciden, es una buena señal.

Antes de aplicar K-Means, conviene verificar si los datos realmente tienen tendencia a formar clusters o si están distribuidos aleatoriamente. Para esto existe el estadístico de Hopkins.

¿Cómo funciona?

  1. Generar m puntos distribuidos al azar en el espacio muestral.
  2. Extraer m puntos reales del dataset.
  3. Para ambos conjuntos, calcular las distancias al vecino más cercano (u y w respectivamente).
  4. Calcular Hopkins:
H = Σuᵢ / (Σuᵢ + Σwᵢ)

Interpretación

H > 0.75

Estructura clara

Los datos presentan clusters bien definidos. El clustering tiene sentido.

H ≈ 0.5

Distribución aleatoria

Los datos no tienen estructura de clusters. Aplicar K-Means no tiene sentido.

H < 0.5

Regularidad

Los datos están distribuidos de forma regular (casi cuadrícula). Poco común.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score

# Dataset sintético con 4 grupos naturales
X, y = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)

# Aplicar K-Means
kmeans = KMeans(n_clusters=4)
kmeans.fit(X)
y_kmeans = kmeans.predict(X)

# Visualizar grupos y centroides
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], c='black', s=200, alpha=0.5)
plt.show()

Método del Codo

sse  = []
k_range = range(1, 10)
for k in k_range:
    km = KMeans(n_clusters=k)
    km.fit(X)
    sse.append(km.inertia_)

plt.plot(k_range, sse, '-o')
plt.xlabel('Cantidad de clusters K')
plt.ylabel('WCSS (Inercia)')
plt.show()
# El "codo" se ve en K=4 → ese es el número óptimo

Silhouette

for k in range(2, 6):
    km    = KMeans(n_clusters=k, random_state=42)
    preds = km.fit_predict(X)
    score = silhouette_score(X, preds)
    print(f"K={k} → Silhouette = {score:.4f}")

Validación externa con Iris

from sklearn.datasets import load_iris

iris = load_iris()
df_iris = pd.DataFrame(iris['data'], columns=nombres_columnas)
y_iris  = iris['target']

kmeans = KMeans(3, random_state=0)
kmeans.fit(df_iris)
clusters = kmeans.predict(df_iris)

# Comparar clusters vs etiquetas reales
validacion = pd.DataFrame(zip(y_iris, clusters), columns=['real', 'cluster'])
pd.crosstab(validacion.real, validacion.cluster)

Validación externa: cuando sí tenemos etiquetas previas, podemos comparar los clusters obtenidos con las etiquetas reales usando un crosstab. No siempre los números de cluster coinciden con las clases (ej: cluster 0 puede ser la clase 2 real), pero la tabla de contingencia revela la correspondencia.

Estandarización antes de clustering

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 1))
X_escalado = scaler.fit_transform(X)

# Siempre escalar antes de K-Means para evitar que variables con
# unidades grandes dominen el cálculo de distancias