Escalado de características Parte 3: Técnicas Avanzadas
Más allá de lo básico: explora el escalado MaxAbs, las transformaciones Power y Quantile, la normalización de vectores y estrategias de escalado personalizadas. Incluye ejemplos de código en Python y R con enfoque en datos dispersos y casos prácticos de aprendizaje automático.
Fundamentos
Preprocesamiento de Datos
Escalado
Datos Dispersos
Serie: Escalado de Características
Autor/a
January Tabaka
Fecha de publicación
27 de agosto de 2025
En la entrada anterior cubrimos la estandarización, normalización y escalado robusto. En esta publicación revisaremos otras técnicas de escalado.
Escalado MaxAbs
Si bien es similar al Escalado Min-Max, este método está diseñado específicamente para datos donde el punto cero tiene significado. Se destaca con datos dispersos (por ejemplo, vectores TF-IDF), ya que preserva las entradas críticas nulas, y con datos naturales centrados como ondas de audio (donde 0 significa silencio) o cambios en precios financieros (donde 0 significa sin cambio).
Principio: Escala cada característica por su valor absoluto máximo para que el valor absoluto más grande sea 1 (o −1). El rango real depende de los signos de los datos.
Si los valores de tu característica son todos no negativos, los valores escalados estarán en el rango [0, 1].
Si los valores de tu característica son todos no positivos, los valores escalados estarán en el rango [-1, 0].
\(X\) - es el vector de valores originales (la transformación se aplica elemento a elemento).
Cuándo usarlo: Su principal ventaja es que no desplaza ni centra los datos. Esto es crucial para conjuntos de datos dispersos (matrices con muchos ceros), ya que no querrías convertir todos los ceros en un número distinto de cero. Centrar (restar la media/mediana) rompe la esparsidad.
Precaución
En scikit-learn, usa StandardScaler(with_mean=False) o RobustScaler(with_centering=False) para evitar densificar. Centrar una matriz dispersa no es compatible y genera un error.
from sklearn.preprocessing import MaxAbsScalerimport numpy as np# Datos con valores positivos y negativos, y un cerodata = np.array([[-20], [-10], [0], [0], [0], [10], [100]])# Instanciar el escalador y ajustar-transformar los datosmaxabs_scaler = MaxAbsScaler()scaled_data = maxabs_scaler.fit_transform(data)print("Datos Escalados: ", scaled_data.T)
Datos Escalados: [[-0.2 -0.1 0. 0. 0. 0.1 1. ]]
Escalado Manual MaxAbs
# Encontrar el valor absoluto máximo en los datos# np.abs() obtiene el valor absoluto de cada elemento primeromax_abs_val = np.max(np.abs(data))# Aplicar la fórmula: x / max(|x|)scaled_data = data / max_abs_valprint("Valor Absoluto Máximo:", max_abs_val,"\nDatos Escalados Manualmente (MaxAbsScaler): ", scaled_data.T)
Valor Absoluto Máximo: 100
Datos Escalados Manualmente (MaxAbsScaler): [[-0.2 -0.1 0. 0. 0. 0.1 1. ]]
library(tidymodels)library(dplyr)data <-tibble(feature =c(-20, -10, 0, 0, 0, 10, 100))# Para un ejemplo autocontenido calculamos a partir de 'datos'.# En la práctica: calcular max_abs_val en la partición TRAIN# y reutilizar para validación/pruebamax_abs_val <-max(abs(data$feature))# Definir receta (escalar por valor absoluto máximo)maxabs_recipe <-recipe(~ feature, data = data) %>%step_mutate(feature = feature / max_abs_val)# Preparar y hornearprepared_maxabs_recipe <-prep(maxabs_recipe, training = data)scaled_data <-bake(prepared_maxabs_recipe, new_data = data)cat("Datos Escalados: [", paste(round(scaled_data$feature, 3), collapse =" "), "]")
Datos Escalados: [ -0.2 -0.1 0 0 0 0.1 1 ]
Aprendizaje profundo vs. aprendizaje clásico
Aprendizaje profundo: Rara vez se usa. Las redes neuronales suelen optar por Min-Max (p. ej., píxeles de imagen → [0,1]) o estandarización (p. ej., embeddings, características continuas).
Aprendizaje clásico: Muy relevante cuando se trabaja con características dispersas (TF-IDF, matrices usuario‑item). Mantiene los ceros, lo cual es crucial para la eficiencia.
Transformación de Potencia
Esta es una transformación más avanzada y no lineal. Su objetivo no es solo reescalar, sino cambiar la propia distribución de la variable para que sea más parecida a una gaussiana (en forma de campana).
Principio: Aplica transformaciones matemáticas como Box‑Cox (solo para datos positivos) o Yeo‑Johnson (para cualquier dato) para estabilizar la varianza y minimizar la asimetría.
Cuándo usarla: Cuando quieres estabilizar la varianza y reducir la asimetría para que los residuos del modelo se comporten mejor (común en familias lineales/GLM). Para características fuertemente sesgadas (p. ej., ingresos), las transformaciones de potencia pueden mejorar el ajuste e inferencia.
Una mirada más profunda: Cómo funcionan las transformaciones de potencia
En el núcleo de ambas transformaciones Box‑Cox y Yeo‑Johnson hay un parámetro llamado lambda (\(\lambda\)), que controla la “forma” de la transformación de potencia aplicada a los datos. El objetivo del algoritmo es encontrar automáticamente el \(\lambda\) óptimo que haga que la distribución de tus datos se aproxime lo más posible a una forma gaussiana (campana de Bell).
Box‑Cox es un método potente y bien establecido, pero tiene una limitación importante: solo funciona con datos estrictamente positivos.
Yeo‑Johnson es una versión más moderna y flexible que puede manejar valores positivos y negativos, razón por la que es el predeterminado en el PowerTransformer de scikit-learn.
La matemática subyacente involucra una familia de funciones que incluye transformaciones logarítmicas y de potencia. Para un análisis detallado de las fórmulas, el artículo de Wikipedia sobre transformaciones de potencia es un recurso excelente.
La conclusión clave es que no necesitas elegir \(\lambda\) tú mismo; el método fit() hace todo el trabajo de encontrar el mejor valor para tus datos.
import numpy as npfrom sklearn.preprocessing import PowerTransformer# Datos sesgados (como una distribución exponencial)data = np.array([[-2], [-1], [0], [1], [2], [5], [10],[100]])# method='yeo-johnson' es el valor por defecto y funciona con cualquier dato# standardize=True hace que la salida tenga una media de 0 y una desviación estándar de 1power_scaler = PowerTransformer(standardize=True)transformed_data = power_scaler.fit_transform(data)print("Datos Transformados:\n", np.round(transformed_data.T, 3))
Datos Transformados:
[[-1.765 -0.882 -0.33 -0.057 0.11 0.41 0.686 1.828]]
Tip
PowerTransformer por defecto estandariza automáticamente los datos después de aplicar la transformación. Esto se controla con el parámetro standardize que por defecto es True. Si solo necesitas desasimetrizar (no media cero/varianza unitaria), usa standardize=False.
Puedes ver que la receta de R para esta transformación incluye dos pasos (step_YeoJohnson seguido de step_normalize), que están allí para asegurar que nuestro ejemplo produzca un resultado estandarizado. Si solo necesitas desasimetrizar (no media cero/varianza unitaria) puedes eliminar el paso step_normalize.
Si todos los valores fueran estrictamente positivos, también se podría usar step_BoxCox(feature).
Figura 1: A la izquierda, una distribución fuertemente sesgada. A la derecha, los mismos datos después de una transformación de potencia, que ahora se asemejan a una distribución normal.
Aprendizaje profundo vs. aprendizaje clásico
Aprendizaje profundo: Casi nunca se usa — las redes neuronales no necesitan que las características sean gaussianas; aprenden mapeos no lineales por sí mismas.
Aprendizaje clásico: Muy útil en regresión/modelos clásicos (regresión lineal, GLM) donde la asimetría puede perjudicar el rendimiento o violar supuestos.
Transformación Basada en Cuantiles
Esta es otra transformación no lineal poderosa que puede ser muy efectiva, especialmente con valores atípicos.
Principio: Transforma las características para que sigan una distribución uniforme o normal. Lo hace basado en el cuantil (o rango) de cada punto de datos. Esto significa que los valores atípicos suelen comprimirse más cerca de los demás puntos.
Cuándo usarla: Cuando tienes valores atípicos y quieres dispersar los valores más frecuentes. Puede ser muy eficaz para transformar la distribución de una característica en algo más manejable para el modelo.
import numpy as npfrom sklearn.preprocessing import QuantileTransformer# Conjunto de datos con valores atípicosdata = np.array([[10], [20], [30], [40], [500]])# output_distribution puede ser 'uniform' (predeterminado) o 'normal'# n_quantiles es cuántos cuantiles usar para el mapeoquantile_scaler = QuantileTransformer(n_quantiles=5, output_distribution='normal')transformed_data = quantile_scaler.fit_transform(data)# Los valores ahora están mapeados a cuantiles aproximadamente normalesprint("Datos transformados: ", np.round(transformed_data.T, 3))
Datos transformados: [[-5.199 -0.674 0. 0.674 5.199]]
Precaución
QuantileTransformer es basado en rangos y no lineal; las distancias y relaciones lineales no se conservan.
Precaución con datos dispersos: QuantileTransformer produce matrices densas; evite usarlo en datos dispersos de alta dimensión (p. ej., BoW/TF-IDF) para prevenir sobrecargas de memoria.
Datos Transformados: [ -1.282 -0.524 0 0.524 1.282 ]
step_orderNorm siempre apunta a una distribución normal (no uniforme). Es el equivalente nativo de R más cercano al QuantileTransformer de scikit-learn.
Figura 2: A la izquierda, datos con un clúster denso y valores atípicos extremos. En el centro, una transformación uniforme distribuye los datos de manera uniforme. A la derecha, una transformación normal mapea los datos a una curva de campana, mitigando efectivamente los valores atípicos.
Aprendizaje profundo vs. aprendizaje clásico
Aprendizaje profundo: Rara vez se usa. Los métodos basados en cuantiles rompen la escala relativa de las distancias entre puntos, lo cual no es ideal para optimizadores basados en gradientes.
Aprendizaje clásico: Detección de anomalías y cualquier modelo sensible a colas pesadas/valores atípicos.
Normalización de Vectores
Este concepto a menudo se confunde con los demás, pero es fundamentalmente diferente.
Diferencia Clave: StandardScaler, MinMaxScaler, etc., todos operan columna por columna (en cada característica de forma independiente). Normalizer opera fila por fila (en cada muestra de forma independiente).
Principio: Escala cada muestra (fila) de modo que tenga una norma unitaria (L1 o L2). Esto trata de cambiar la longitud del vector de características de una muestra dada, no de comparar valores entre muestras.
Cuándo usarlo: Cuando la dirección del vector de datos importa más que su magnitud. El caso de uso más común es en clasificación de texto o agrupamiento con características como TF-IDF, donde quieres saber si dos documentos tratan el mismo tema, independientemente de la longitud de los documentos.
No hay un Normalizador directo por fila en recipes. En R, esto suele hacerse manualmente con operaciones básicas, ya que los flujos de trabajo de ML rara vez utilizan la normalización por fila fuera del análisis de texto.
Explicación:
- Cada fila se escala de forma independiente para que su norma L2 = 1.
- Se conserva la dirección del vector, pero se ignora su magnitud.
- Por eso es útil en similaridad de texto: los documentos se comparan por ángulo (similaridad coseno), no por longitud.
Aprendizaje profundo vs. aprendizaje clásico
Aprendizaje profundo: No es un paso de preprocesamiento — en su lugar, suele usarse después de entrenar con embeddings (p. ej. normalizando L2 los embeddings de palabras antes de la similaridad coseno).
Aprendizaje clásico: Esencial en minería de textos, clustering, búsqueda/retrieval — hace que las comparaciones de similaridad coseno tengan sentido.
Resumen
Escalador/Transformador
Principio
Funciona en
Caso de Uso Principal
Sensibilidad a Valores Atípicos
Escalado MaxAbs
Escala por el valor absoluto máximo (el rango depende del signo de los datos: [-1, 1], [0, 1], o [-1, 0])
Columnas
Datos dispersos (conserva entradas cero).
Alta
Transformación de potencia
Transformación no lineal para que los datos sean más similares a una distribución gaussiana
Columnas
Estabilizar la varianza y manejar datos sesgados.
Variable
Transformación basada en cuantiles
Mapea la distribución de datos a una uniforme o normal
Todos los escaladores hasta ahora son integrados. Pero, ¿qué pasa si necesitas algo personalizado? Ahí es donde entran FunctionTransformer (Python) o step_mutate (R).
Por ejemplo, dos transformaciones personalizadas muy prácticas son:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Nota: Si pasas una matriz dispersa, asegúrate de que tu función pueda manejarla (por ejemplo, usa operaciones compatibles con scipy.sparse).FunctionTransformer` pasará la matriz a través de ella, tu función no debe densificarla sin querer.
library(tidymodels)library(dplyr)df <-tibble(count =c(0, 1, 2, 3, 10, 50, 200, 1000),feat =c(2.1, 2.0, 2.3, 2.2, 2.5, 3.0, 3.2, 3.5),y =c(1.2, 1.1, 1.3, 1.2, 1.6, 2.2, 2.6, 3.1))# Ayuda para winsorización (recorte por columna)winsor <-function(x, p =c(0.01, 0.99)) { qs <-quantile(x, probs = p, na.rm =TRUE, names =FALSE)pmin(pmax(x, qs[1]), qs[2])}rec <-recipe(y ~ count + feat, data = df) %>%# Ejemplo 1: log1p para conteos sesgadosstep_mutate(count =log1p(count)) %>%# Ejemplo 2: winsorización de feat en [1%, 99%]step_mutate(feat =winsor(feat)) %>%# Opcionalmente sigue con estandarizaciónstep_normalize(all_numeric_predictors())prep_rec <-prep(rec)baked <-bake(prep_rec, new_data =NULL)print(baked)
¿Cuándo recurrir a transformaciones personalizadas?
Has perfilado una característica y conoces la patología (p. ej., sesgo extremo o unos pocos valores atípicos absurdos).
Los transformadores predefinidos no coinciden con tu necesidad exacta (p. ej., recortar primero, luego escalar).
Quieres que el paso se quede dentro de tu pipeline/receta para que sea reproducible y se aplique de manera consistente a train/validation/test.
Regla general: Aplica log1p para conteos positivos con cola pesada; clip/winsorize antes de escalar cuando un puñado de puntos extremos distorsione los rangos o las desviaciones estándar.
Profundización: Datos dispersos
¿Qué es un dato disperso?
En términos sencillos, los datos dispersos son datos que están compuestos en su mayoría por ceros.
Su opuesto son los datos densos, donde la mayoría de los valores son diferentes de cero.
No existe una regla matemática estricta, pero si un porcentaje muy alto (p. ej., >90 %, >99 %) de las entradas de tus datos son cero, estás tratando con un conjunto de datos disperso.
Una analogía intuitiva: La tienda de alquiler de películas
Imagina una enorme tienda de alquiler de películas con 50 000 títulos únicos. Esto representa características.
Ahora, imagina que seguimos el historial de alquiler de un cliente, “Alice”. Alice solo ha alquilado 15 películas diferentes. Esta es el muestra.
Si representáramos el historial de alquiler de Alice como una fila de datos con 50 000 columnas (una por cada película), se vería algo así:
Esta fila tiene 50 000 valores, pero solo 15 de ellos son diferentes de cero. Los otros 49 985 valores son cero. Este es un vector altamente disperso.
Por qué la esparsidad es especial e importante
Los datos dispersos se manejan de manera diferente por dos razones principales:
Eficiencia de almacenamiento y memoria: Sería increíblemente ineficiente almacenar todos esos ceros. En lugar de un array NumPy estándar, los datos dispersos se almacenan en estructuras especiales (como un scipy.sparse.csr_matrix) que solo guardan los elementos no cero y sus ubicaciones (índice de fila y columna).
Esto puede reducir el uso de memoria de gigabytes a megabytes.
El problema con los escaladores estándar: Esta es la razón por la que necesitamos escaladores especiales para datos dispersos. Veamos qué sucede si usas incorrectamente StandardScaler en datos dispersos.
La fórmula es \(z = \frac{x - \mu}{\sigma}\).
En un conjunto de datos dispersos, la gran mayoría de los valores son 0, así que la media (\(\mu\)) será un número muy pequeño cercano a cero (pero no exactamente cero). Supongamos que \(\mu = 0.01\).
¿Qué ocurre cuando escalas un valor que es 0? \(z = \frac{0 - 0.01}{\sigma}\)
¡El resultado es un número distinto de cero!
Acabas de tomar una matriz que era 99 % ceros y se almacenaba de manera eficiente, y la has convertido en una matriz 100 % densa. El uso de memoria explotará.
Cómo manejar correctamente los datos dispersos
Aquí es donde entran MaxAbsScaler y Normalizer.
MaxAbsScaler: La fórmula es \(x_{scaled} = \frac{x_i}{max(|x|)}\).
Si un valor \(x\) es \(0\), el resultado es $ = 0$.
Mantiene los ceros como ceros! Preserva la esparsidad de tus datos mientras escala los valores no cero.
Normalizer: Este escalador funciona fila por fila, por lo que también maneja la esparsidad de forma inherente al considerar solo los elementos no cero en sus cálculos para esa fila.
Resumen: Disperso vs. Denso
Característica
Datos densos
Datos dispersos
Composición de valores
Mayoría de valores diferentes de cero.
Gran mayoría de valores son ceros.
Ejemplos comunes
Lecturas de sensores, edad, ingresos, mediciones físicas.
Datos de texto (Bag‑of‑Words), interacciones usuario‑producto (p. ej., quién compró qué).
Almacenamiento
Arrays NumPy estándar.
Formatos especiales de matrices dispersas (scipy.sparse).
Escaladores apropiados
StandardScaler, MinMaxScaler, RobustScaler.
MaxAbsScaler, Normalizer.
Ejemplo 1: Datos de texto con Bag-of-Words
Esta es la fuente más común de datos dispersos en aprendizaje automático. El modelo “Bag‑of‑Words” representa el texto contando la ocurrencia de cada palabra conocida, ignorando la gramática y el orden de las palabras.
Escenario: Tenemos un pequeño corpus de tres documentos simples y queremos convertirlos en vectores numéricos.
import pandas as pdfrom sklearn.feature_extraction.text import CountVectorizer# Colección de documentos de textocorpus = ['El Anillo debe ir a Mordor','Frodo lleva el Anillo a Mordor','Sauron gobierna en Mordor']# Inicializa el vectorizador# Este objeto aprenderá el vocabulario y transformará el textovectorizer = CountVectorizer()# Ajusta el vectorizador al corpus y transforma los datos# .fit_transform() aprende el vocabulario# y convierte el corpus en una matriz de conteo de palabras.X = vectorizer.fit_transform(corpus)# Muestra los resultados en un formato legible por humanos# Obtiene el vocabulario aprendido (características)feature_names = vectorizer.get_feature_names_out()# Crea un DataFrame de pandas para visualizar la matriz dispersadf = pd.DataFrame(X.toarray(), columns=feature_names, index=['Doc 1', 'Doc 2', 'Doc 3'])print("Vocabulario aprendido (Características):", feature_names,"\nMatriz Bag-of-Words (Representación Densa):")
print(f"\nForma de la matriz: {X.shape}",f"Número de elementos no nulos: {X.nnz}")
Forma de la matriz: (3, 10) Número de elementos no nulos: 14
recipes todavía no soporta matrices dispersas. En R, los flujos de trabajo con matrices dispersas suelen usar el paquete Matrix, que representa eficientemente grandes conjuntos de datos dispersos (p. ej., dgCMatrix).
library(text2vec)library(Matrix)corpus <-c("El Anillo debe ir a Mordor","Frodo lleva el Anillo a Mordor","Sauron gobierna en Mordor")# Crear un iterador sobre tokenstokens <-space_tokenizer(corpus)# Crear un vocabularioit <-itoken(tokens)vocab <-create_vocabulary(it)# Vectorizadorvectorizer <-vocab_vectorizer(vocab)# Crear matriz documento-término (dispersa)dtm <-create_dtm(it, vectorizer)print(dtm)
cat("Dimensiones de la matriz:", dim(dtm), "\nEntradas no cero:", length(dtm@x))
Dimensiones de la matriz: 3 12
Entradas no cero: 16
Como puedes ver en el DataFrame de salida, el resultado es una matriz donde:
Cada fila es un documento.
Cada columna es una palabra única del vocabulario.
El valor en cada celda es el recuento de cuántas veces apareció esa palabra en ese documento.
Observa la gran cantidad de ceros. Incluso en este pequeño ejemplo, la mayoría de las celdas son cero. Ahora, imagina un vocabulario del mundo real con 50 000 palabras. Una sola oración podría usar solo 10-20 de esas palabras, lo que significa que su representación vectorial tendría más de 49 980 entradas cero. Esto es una matriz dispersa. El CountVectorizer de scikit-learn almacena inteligentemente X en un formato scipy.sparse para ahorrar memoria. Llamamos a .toarray() solo con el propósito de visualización.
Ejemplo 2: Interacciones Usuario-Elemento
Esta es la base de los sistemas de recomendación (como los utilizados por Netflix o Amazon). El objetivo es representar qué usuarios han interactuado con (por ejemplo, comprado, visto o calificado) qué artículos.
Escenario: Tenemos un pequeño sitio de comercio electrónico. Queremos crear una matriz que represente cuáles de nuestros 4 usuarios han comprado cuáles de nuestros 6 productos.
import pandas as pd# Datos en bruto de interacciones usuario-artículo (p.ej., de una base de datos)# Un '1' indica una compra.data = {'user': ['Alice', 'Bob', 'Alice', 'Charlie', 'Alice', 'David', 'Bob'],'item': ['Laptop', 'Mouse', 'Keyboard', 'Mouse', 'Monitor', 'Keyboard', 'Webcam']}df_interactions = pd.DataFrame(data)df_interactions['purchased'] =1print("Datos de Interacción Original:")
Datos de Interacción Original:
print(df_interactions)
user item purchased
0 Alice Laptop 1
1 Bob Mouse 1
2 Alice Keyboard 1
3 Charlie Mouse 1
4 Alice Monitor 1
5 David Keyboard 1
6 Bob Webcam 1
# Crear la matriz de interacción usuario-artículo usando pivot_table# Esto transforma el formato de datos 'largo' en un formato de matriz 'ancho'.# Rellenamos las no-interacciones con 0.user_item_matrix = df_interactions.pivot_table( index='user', columns='item', values='purchased', fill_value=0)print("\nMatriz Dispersa Usuario-Item:")
Matriz Dispersa Usuario-Item:
print(user_item_matrix)
item Keyboard Laptop Monitor Mouse Webcam
user
Alice 1.0 1.0 1.0 0.0 0.0
Bob 0.0 0.0 0.0 1.0 1.0
Charlie 0.0 0.0 0.0 1.0 0.0
David 1.0 0.0 0.0 0.0 0.0
Nota
Los escaladores compatibles con datos dispersos son raros en R; la mayoría de los flujos de trabajo aquí dependen de paquetes como Matrix, text2vec, o frameworks especializados de recomendación en lugar de recipes.
4 x 5 sparse Matrix of class "dgCMatrix"
Pantalla Portátil Ratón Teclado Webcam
Alicia 1 1 . 1 .
Carlos . . 1 . .
David . . . 1 .
Roberto . . 1 . 1
Observa la Matriz Dispersa Usuario-Item resultante.
Cada fila es un usuario.
Cada columna es un artículo disponible para la venta.
Un 1 significa que el usuario ha comprado ese artículo. Un 0 significa que no lo ha hecho.
De nuevo, la matriz está compuesta mayoritariamente por ceros. Alice ha comprado 3 de los 6 artículos disponibles, por lo que su fila es 50 % dispersa. En un sistema real como Amazon, con millones de usuarios y millones de productos, cualquier usuario dado habrá comprado una fracción infinitesimalmente pequeña de los artículos totales disponibles. Su fila correspondiente en la matriz de usuario‑artículo sería increíblemente dispersa.
Esta dispersión es fundamental para el funcionamiento de los algoritmos de filtrado colaborativo. Utilizan esta matriz para encontrar usuarios con patrones similares de entradas no cero y así realizar recomendaciones.
Escalado MaxAbs vs. estandarización en datos dispersos
Este primer ejemplo es denso a propósito para mostrar el efecto numérico; el bloque siguiente muestra una esparsidad CSR real.
import numpy as npfrom sklearn.preprocessing import MaxAbsScaler, StandardScaler# Una matriz DENSE utilizada para ilustrar la escala# Cada fila es un documento, cada columna es un recuento de palabrasdata = np.array([ [5, 0, 0, 1, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 3], [1, 0, 2, 0, 0]])# --- ESCALADO CORRECTO con MaxAbsScaler ---maxabs_scaler = MaxAbsScaler()maxabs_scaled = maxabs_scaler.fit_transform(data)print("Escalado con MaxAbsScaler (los ceros se preservan):\n", maxabs_scaled)
# --- CORRECTO: preserva la esparsidadX_maxabs = MaxAbsScaler().fit_transform(X)print("número de elementos no cero después de MaxAbs:", X_maxabs.nnz)
número de elementos no cero después de MaxAbs: 6
# --- CORRECTO para disperso: sin centradoX_std = StandardScaler(with_mean=False).fit_transform(X)print("número de elementos no cero después de StandardScaler(with_mean=False):", X_std.nnz)
número de elementos no cero después de StandardScaler(with_mean=False): 6
# --- INCORRECTO: el centrado de matrices dispersas no es compatibletry: _ = StandardScaler(with_mean=True).fit_transform(X)exceptValueErroras e:print("El centrado de matrices dispersas no es compatible:", str(e)[:62], "…")
El centrado de matrices dispersas no es compatible: Cannot center sparse matrices: pass `with_mean=False` instead. …
R:text2vec — extracción de características de texto (DTM/TF-IDF) con matrices dispersas.
Próximo paso: Juntando todo
A lo largo de este recorrido de tres partes, hemos explorado todo el panorama del escalado de características, desde el fundamento “por qué” en la Parte 1, los trabajadores cotidianos en la Parte 2, y ahora los especialistas potentes en la Parte 3.
Pero la teoría es una cosa, y ver el impacto es otra.
En la publicación final y concluyente de esta serie, reuniremos todo. Construiremos una tabla de resumen completa de todos los escaladores que hemos discutido, proporcionaremos notebooks descargables en Python y R con todo el código en un solo lugar, y, lo más importante, recorreremos un ejemplo práctico. Entrenaremos un modelo de aprendizaje automático simple sobre datos sin escalar, veremos cómo funciona, y luego aplicaremos la técnica de escalado adecuada para observar la mejora tangible. Esta será la demostración práctica de por qué el escalado es un paso de preprocesamiento crucial cuando el modelo lo exige.