import numpy as np
# Un conjunt de dades senzill amb una característica
data = np.array([[10.0], [20.0], [30.0], [40.0], [50.0]])
print("Dades: ", data.T)Dades: [[10. 20. 30. 40. 50.]]
January Tabaka
20 d’agost de 2025
En el post anterior vam veure per què l’escalat de dades és un pas crític en l’aprenentatge automàtic (machine learning). Ara, passem al com. En aquesta guia explorarem les tres tècniques d’escalat més comunes i la seva implementació tant en Python com en R.
El món de l’estadística té moltes formes de normalització. Tanmateix, en preparar dades específicament per a models d’aprenentatge automàtic, la comunitat generalment utilitza els següents termes, que són els adoptats en aquest post:
Estandardització es refereix a l’escalat Z-score: transformar les dades perquè tinguin una mitjana de 0 i una desviació estàndard d’1.
Normalització sol referir-se a l’escalat Min-Max: transformar les dades perquè s’ajustin a un rang específic, normalment [0, 1].
Tot i que existeixen altres mètodes d’escalat (alguns dels quals es cobriran al post sobre transformadors avançats), aquests dos, juntament amb l’Escalat Robust, són les tècniques fonamentals utilitzades més freqüentment.
Primer, crearem un conjunt de dades senzill per il·lustrar els conceptes.
Principi: L’estandardització reescala les dades perquè tinguin una mitjana de 0 i una desviació estàndard d’1. A això se li sol dir normalització Z-score.
Fórmula: \[ Z = \frac{X - \mu}{\sigma} \] On:
Quan utilitzar-la: Aquest és l’escalador més comú i utilitzat per defecte. És especialment efectiu si les teves dades segueixen una distribució Gaussiana (normal).
Aquí tens com implementar-ho utilitzant les llibreries estàndard en Python i R.
import numpy as np
from sklearn.preprocessing import StandardScaler
# Inicialitzar i ajustar/transformar les dades
std_scaler = StandardScaler()
standardized_data = std_scaler.fit_transform(data)
print("Mitjana apresa:", std_scaler.mean_,
"\nDesviació estàndard apresa:", std_scaler.scale_,
"\nDades estandarditzades:", np.round(standardized_data.T, 3))Mitjana apresa: [30.]
Desviació estàndard apresa: [14.14213562]
Dades estandarditzades: [[-1.414 -0.707 0. 0.707 1.414]]
library(tidymodels)
library(dplyr)
# Definir la recepta
standardization_recipe <- recipe(~ feature, data = data) %>% step_normalize(feature)
# Preparar la recepta
# Aquest pas aprèn la mitjana i la desviació estàndard a partir de les dades.
prepared_standarization_recipe <- prep(standardization_recipe, training = data)
# Aplicar la recepta
# Aquest pas aplica la transformació apresa.
standardized_data <- bake(prepared_standarization_recipe, new_data = data)
# Obtenir els paràmetres apresos
mean_sd <- tidy(prepared_standarization_recipe, number = 1)
# Extreure els valors específics del tibble ordenat
mean_val <- mean_sd %>% filter(statistic == "mean") %>% pull(value)
sd_val <- mean_sd %>% filter(statistic == "sd") %>% pull(value)
cat("\nMitjana apresa: ", mean_val,
'\nDesviació estàndard apresa: ', sd_val,
"\nDades estandarditzades: [",
paste(round(standardized_data$feature, 3), collapse = " "),"]")
Mitjana apresa: 30
Desviació estàndard apresa: 15.81139
Dades estandarditzades: [ -1.265 -0.632 0 0.632 1.265 ]
Si compares els resultats de Python i R, notaràs que són lleugerament diferents. Això no és un error, sinó una distinció estadística clau:
scikit-learn en Python utilitza la desviació estàndard poblacional (dividint entre n), tractant el teu conjunt de dades com un univers complet.tidymodels en R utilitza la desviació estàndard mostral (dividint entre n-1), la qual és la pràctica estàndard en estadística per estimar els paràmetres d’una població més gran.Aquí tens com realitzar l’estandardització manualment.
Els noms de variables mu (\(\mu\)) per a la mitjana i sigma (\(\sigma\)) per a la desviació estàndard s’utilitzen per coincidir directament amb la fórmula.
Una comparació de les sortides de Python i R ressalta un detall estadístic important. Mentre que la mitjana calculada és idèntica (30.0), les desviacions estàndard no ho són.
np.std() en Python: 14.1421sd() en R: 15.8114Això no és un error; és una distinció estadística crucial entre dues maneres diferents de calcular la desviació estàndard.
Desviació estàndard poblacional (l’opció per defecte a NumPy)
Per defecte, np.std() a NumPy calcula la desviació estàndard poblacional. Aquest mètode és apropiat quan un conjunt de dades representa tota la població. La fórmula divideix la suma de les diferències al quadrat entre n, el nombre total d’observacions.
Desviació estàndard mostral (l’opció per defecte a R)
Per defecte, la funció sd() a R calcula la desviació estàndard mostral. Aquest mètode és apropiat quan un conjunt de dades es considera una mostra d’una població més gran. En usar n-1 al denominador, la fórmula ofereix una estimació més precisa i no esbiaixada de la desviació estàndard de la població.
Python pot igualar fàcilment el resultat de R indicant a NumPy que utilitzi n-1. Això s’aconsegueix configurant el paràmetre ddof=1 (“Delta Degrees of Freedom”).
# Establir ddof=1 per indicar a NumPy que utilitzi n-1 al denominador,
# per tal de calcular la desviació estàndard mostral
sigma = np.std(data, ddof=1)
print("Desviació estàndard mostral (σ): ", sigma)Desviació estàndard mostral (σ): 15.811388300841896
La implementació amb un bucle for imita com es podria realitzar el càlcul a mà, processant un número cada vegada.
Aquest mètode és explícit i fàcil de seguir, però pot ser molt lent per a conjunts de dades grans perquè Python ha d’interpretar cada pas del bucle individualment.
El següent codi estandarditza les dades pas a pas:
standardized_data, emmagatzemarà els nous valors escalats calculats.standardized_value, s’afegeix a la llista standardized_data.import numpy as np
# Fer servir la mitjana prèviament calculada (mu) i restablir sigma
# per tal que coincideixi amb scikit-learn (desviació estàndard poblacional)
sigma = np.std(data)
# 1. Crear una llista buida per emmagatzemar els resultats
standardized_data = []
# 2. Recorre cada valor de les dades originals
for x in data:
# 3. Aplicar la fórmula d'estandardització a cada valor
# Fórmula: z = (valor - mu) / sigma
standardized_value = (x - mu) / sigma
# 4. Afegir el resultat a la llista
standardized_data.append(standardized_value)
# 5. Convertir la llista de resultats novament en un array de NumPy
standardized_data = np.array(standardized_data)
print("Dades estandarditzades:\n", np.round(standardized_data.T, 3))Dades estandarditzades:
[[-1.414 -0.707 0. 0.707 1.414]]
El següent codi estandarditza les dades pas a pas:
standardized_data, d’una mida predefinida usant numeric(). Aquesta és una optimització de rendiment comuna a R.for itera sobre els índexs del vector (de l’1 al 5), generats per la funció seq_along().i) s’utilitza per accedir al valor corresponent en les dades originals: data$feature[i]. Per a cada valor individual, s’aplica la fórmula del Z-score restant la mitjana (\(\mu\)) i dividint entre la desviació estàndard (\(\sigma\)).standardized_value, s’assigna directament a la posició i del vector preassignat standardized_data.# Fer servir la mitjana (mu) i sigma prèviament calculats
# 1. Preassignar un vector buit de la mida correcta
standardized_data <- numeric(length(data$feature))
# 2. Recorre cada valor usant el seu índex
for (i in seq_along(data$feature)) {
# 3. Aplicar la fórmula d'estandardització a cada valor
# Fórmula: z = (valor - mu) / sigma
standardized_value <- (data$feature[i] - mu) / sigma
# 4. Assignar el resultat a la posició corresponent del vector
standardized_data[i] <- standardized_value
}
cat("Dades estandarditzades: [", paste(round(standardized_data, 3), collapse = " "), "]")Dades estandarditzades: [ -1.265 -0.632 0 0.632 1.265 ]
Per a aquesta tasca específica (estandardització Z-score), Base R ofereix una funció dedicada i pràctica anomenada scale(). Aquesta realitza tant el centrat com l’escalat en un sol pas i retorna el resultat com una matriu.
La màgia darrere de la vectorització: Broadcasting i Reciclatge
Com operen aquests llenguatges sobre arrays complets sense un bucle? La màgia rau en una característica amb dos noms diferents per a la mateixa idea central: broadcasting en NumPy i reciclatge de vectors en R.
Quan s’utilitza una expressió com data - mu, el llenguatge interpreta que la intenció és restar un sol número (un escalar) d’una col·lecció de números (un array o vector). En lloc de produir un error, “estira” o “recicla” el número únic perquè coincideixi amb la longitud de la col·lecció, i després realitza l’operació element per element.
En efecte, calcula:
[10.0, 20.0, 30.0, 40.0, 50.0]
- [30.0, 30.0, 30.0, 30.0, 30.0] <- El mu difós (broadcasted)
----------------------------------
[-20.0, -10.0, 0.0, 10.0, 20.0]Operacions element per element: El resultat de la resta és un nou array o vector. El llenguatge pren llavors aquest resultat i realitza la següent operació (divisió per \(\sigma\)) en cada element.
Aquest enfocament vectoritzat és superior en la pràctica per dues raons principals:
Rendiment: Tant R com NumPy executen aquestes operacions vectoritzades no pas en els seus intèrprets d’alt nivell, sinó en codi C o Fortran altament optimitzat i precompilat. Això les fa ordres de magnitud més ràpides que un bucle for, especialment a mesura que augmenta la quantitat de dades.
Llegibilitat: El codi esdevé més concís i s’assembla gairebé de manera idèntica a la fórmula matemàtica \(( \frac{x - \mu}{\sigma})\), la qual cosa facilita que científics de dades i matemàtics el puguin llegir i verificar. Compara-ho amb la fórmula:```
(data - mu) / sigma(data$feature - mu) / sigmaPer aquestes raons, l’enfocament vectoritzat és el mètode preferit per a operacions matemàtiques en lloc dels bucles manuals.
Tot i que StandardScaler gairebé sempre s’utilitza amb la seva configuració per defecte, existeixen escenaris específics, com treballar amb dades disperses en anàlisi de text, on es necessita més control. Aquests casos especials, inclosos els paràmetres with_mean i with_std, es cobriran a l’últim post de la sèrie.
Principi: La normalització reescala tots els valors de les dades a un rang fix, normalment entre 0 i 1.
Fórmula: \[X_{norm} = \frac{X - x_{min}}{x_{max} - x_{min}}\]
On:
Quan utilitzar-la: És útil quan un algorisme requereix dades en un interval acotat. També s’utilitza àmpliament en el processament d’imatges, on els valors dels píxels es normalitzen a un rang. Tanmateix, és molt sensible a valors atípics: un sol valor extrem pot comprimir tots els altres punts de dades en un subrang molt petit.
from sklearn.preprocessing import MinMaxScaler
# Inicialitzar i ajustar/transformar les dades
min_max_scaler = MinMaxScaler()
normalized_data = min_max_scaler.fit_transform(data)
print("Mínim après: ", min_max_scaler.data_min_,
"\nMàxim après: ", min_max_scaler.data_max_,
"\nDades normalitzades: ", normalized_data.T)Mínim après: [10.]
Màxim après: [50.]
Dades normalitzades: [[0. 0.25 0.5 0.75 1. ]]
Al framework tidymodels de R, l’equivalent de l’escalat Min-Max és step_range(). Aquest pas transforma les dades a un rang específic, que per defecte és [0, 1].
library(tidymodels)
# Definir la recepta
# - Afegir la funció step_range().
# - El rang per defecte és min = 0 i max = 1.
normalization_recipe <- recipe(~ feature, data = data) %>%
step_range(feature)
# "Preparar" la recepta
# Aquest pas aprèn el mínim i el màxim a partir de les dades.
prepared_normalization_recipe <- prep(normalization_recipe, training = data)
# "Aplicar" la recepta
# Aquest pas aplica la transformació apresa.
normalized_data <- bake(prepared_normalization_recipe, new_data = data)
# Obtenir els paràmetres apresos
min_max <- tidy(prepared_normalization_recipe, number = 1)
cat(paste("Mínim après: ", min_max$min, '\nMàxim après: ', min_max$max),
"\nDades normalitzades: [",
paste(round(normalized_data$feature, 2), collapse = " "),"]")Mínim après: 10
Màxim après: 50
Dades normalitzades: [ 0 0.25 0.5 0.75 1 ]
step_range(): Aquesta és la funció principal. Funciona igual que step_normalize, però en lloc d’estandarditzar, reescala les dades a un nou rang.step_range() escala a un rang de [0, 1], exactament com el MinMaxScaler de scikit-learn, de manera que no cal especificar arguments addicionals per a aquest cas comú.# Trobar el valor mínim
min_val = np.min(data)
# Trobar el valor màxim
max_val = np.max(data)
# Aplicar la fórmula: (x - min) / (max - min)
normalized_data = (data - min_val) / (max_val - min_val)
print("Mínim: ", min_val, "\nMàxim: ", max_val,
"\nDades normalitzades: ", normalized_data.T)Mínim: 10.0
Màxim: 50.0
Dades normalitzades: [[0. 0.25 0.5 0.75 1. ]]
# Trobar el valor mínim
min_val <- min(data$feature)
# Trobar el valor màxim
max_val <- max(data$feature)
# Aplicar la fórmula: (x - min) / (max - min)
normalized_data <- (data$feature - min_val) / (max_val - min_val)
cat("Mínim: ", min_val, "\nMàxim: ", max_val,
"\nDades normalitzades: [", paste(normalized_data, collapse = " "), "]")Mínim: 10
Màxim: 50
Dades normalitzades: [ 0 0.25 0.5 0.75 1 ]
min() i max(), la qual cosa fa que el codi sigui increïblement llegible.L’elecció entre estandardització (escalat Z-score) i normalització (escalat Min-Max) és una decisió crítica en molts fluxos de preprocessament de dades. Tot i que ambdues són comunes, es basen en principis diferents i estan millor adaptades a tasques diferents.
Estandardització sol ser la tècnica recomanada per defecte. El seu enfocament estadístic, que transforma les dades perquè tinguin una mitjana de 0 i una desviació estàndard d’1, resulta beneficiós per a molts algorismes clàssics d’aprenentatge automàtic, com la Regressió Lineal o les Màquines de Vectors de Suport, que funcionen millor quan les característiques estan centrades al voltant de zero. Aquest mètode és particularment eficaç si les dades de les característiques segueixen aproximadament una distribució Gaussiana (en forma de campana).
Normalització, en canvi, és l’opció preferida quan es requereix estrictament un rang de sortida acotat. Això és essencial en aplicacions específiques com el Processament d’Imatges, on els valors dels píxels s’escala a un rang, i en les Xarxes Neuronals. En xarxes neuronals, funcions d’activació com la sigmoide o la tanh poden comportar-se malament amb valors d’entrada grans, per la qual cosa comprimir les característiques en un rang consistent (per exemple, [0, 1] o [-1, 1]) ajuda a garantir un entrenament estable i eficaç. Tanmateix, s’ha d’utilitzar amb precaució, ja que la seva alta sensibilitat a valors atípics pot comprimir la majoria dels punts de dades en un subrang molt petit.
En resum, l’estandardització funciona com una opció robusta per defecte adequada per a la majoria de models d’aprenentatge automàtic. L’ús de la normalització per escalar característiques a un rang estricte i acotat ha de ser una elecció deliberada, generalment reservada per a casos en què l’arquitectura d’un algorisme ho requereixi (com en xarxes neuronals) o quan un rang directament interpretable sigui essencial.
Principi: L’Escalat Robust ajusta les dades d’acord amb el Rang Interquartílic (IQR). Resta la mediana i escala les dades usant la diferència entre el primer quartil (percentil 25) i el tercer quartil (percentil 75).
Fórmula: \[X_{scaled} = \frac{X - q_2}{q_3 - q_1}\]
On:
Quan utilitzar-lo: Aquest mètode és, com el seu nom indica, “robust” davant de valors atípics. Si el teu conjunt de dades té valors extrems significatius, l’estandardització es pot veure esbiaixada. L’Escalat Robust utilitza la mediana i l’IQR, que es veuen molt menys afectats per valors extrems, cosa que el converteix en una millor opció en aquestes situacions.
from sklearn.preprocessing import RobustScaler
# Inicialitzar i ajustar/transformar les dades
robust_scaler = RobustScaler()
scaled_data = robust_scaler.fit_transform(data)
print("Mediana apresa: ", robust_scaler.center_,
"\nIQR après: ", robust_scaler.scale_,
"\nDades escalades: ", scaled_data.T)Mediana apresa: [30.]
IQR après: [20.]
Dades escalades: [[-1. -0.5 0. 0.5 1. ]]
Aquest és un gran exemple de les diferents filosofies entre Python i R. Mentre que scikit-learn ofereix un RobustScaler dedicat, l’ecosistema de tidymodels no té una única funció equivalent.
En lloc d’això, l’enfocament idiomàtic en R és aprofitar la flexibilitat del framework recipes per crear una transformació d’escalat robust. Això es pot fer utilitzant la funció step_mutate() per aplicar la fórmula manualment. Això demostra el poder i l’extensibilitat del flux de treball amb recipes.
library(tidymodels)
# Definir la recepta amb una mutació personalitzada
recipe_robust <- recipe(~ feature, data = data) %>%
step_mutate(feature = (feature - median(feature)) / IQR(feature))
# "Preparar" la recepta
prepared_recipe_robust <- prep(recipe_robust, training = data)
# "Aplicar" la recepta
# Aquest pas aplica la fórmula.
scaled_data <- bake(prepared_recipe_robust, new_data = data)
cat('Mediana: ', median(data$feature), "\nIQR: ", IQR(data$feature),
"\nDades escalades: [", paste(scaled_data$feature, collapse = " "), "]")Mediana: 30
IQR: 20
Dades escalades: [ -1 -0.5 0 0.5 1 ]
step_mutate(feature = ...): Aquest és el nucli de la solució. step_mutate() és una funció del paquet recipes que permet crear o sobreescriure una columna utilitzant un càlcul personalitzat.(feature - median(feature)) / IQR(feature): Aquesta és la fórmula manual per a l’escalat robust. Indica a la recepta: “Per a la columna feature, reemplaça els seus valors actuals amb el resultat d’aquest càlcul.”
median(feature): Calcula la mediana de la columna feature.IQR(feature): Calcula el Rang Interquartílic (IQR) de la columna feature.El flux de treball continua sent el mateix: Encara que s’hagi utilitzat una fórmula personalitzada, el poderós flux “definir, preparar, aplicar” es manté idèntic. Aquesta és la bellesa del framework recipes: proporciona una estructura consistent tant per a transformacions integrades com per a personalitzades.
from scipy.stats import iqr
import numpy as np
# Calcular la mediana
median_val = np.median(data)
# Calcular el Rang Interquartílic (IQR)
iqr_val = iqr(data)
# Aplicar la fórmula: (x - mediana) / IQR
scaled_data = (data - median_val) / iqr_val
print("Mediana: ", median_val,
"\nIQR: ", iqr_val,
"\nDades escalades: ", scaled_data.T)Mediana: 30.0
IQR: 20.0
Dades escalades: [[-1. -0.5 0. 0.5 1. ]]
# Calcular la mediana
median_val <- median(data$feature)
# Calcular el Rang Interquartílic (IQR)
iqr_val <- IQR(data$feature)
# Aplicar la fórmula: (x - mediana) / IQR
scaled_data <- (data$feature - median_val) / iqr_val
cat("Mediana: ", median_val, "\nIQR: ", iqr_val,
"\nDades escalades: [", paste(scaled_data, collapse = " "), "]")Mediana: 30
IQR: 20
Dades escalades: [ -1 -0.5 0 0.5 1 ]
IQR(): R té una funció integrada i dedicada IQR() que calcula directament el Rang Interquartílic, cosa que fa que el codi sigui més concís i llegible.median_val i iqr_val s’utilitzen per evitar conflictes amb les funcions integrades de R median() i IQR(). Aquesta és una pràctica estàndard a R per prevenir conflictes de noms.Perquè aquests conceptes siguin més clars, he representat gràficament la transformació de la mateixa característica sota cada tècnica. No estem comparant entre diferents característiques; l’objectiu aquí és aïllar l’efecte de cada mètode d’escalat en una sola variable.

| Tècnica | Principi | Implementacions clau | Cas d’ús principal | Sensibilitat als valors atípics (outliers) |
|---|---|---|---|---|
| Estandardització (Z-score) | Ajusta mitjana=0, desviació estàndard=1. | sklearn.preprocessing.StandardScalerrecipes::step_normalizebase::scale |
Propòsit general, opció per defecte. Millor per a dades amb distribució Gaussiana. | Moderada |
| Normalització (Escalat Min-Max) | Escala a un rang fix, p. ex. [0, 1]. | sklearn.preprocessing.MinMaxScalerrecipes::step_range |
Xarxes Neuronals, Processament d’Imatges (quan es requereix un rang estricte de sortida). | Alta |
| Escalat Robust | Escala usant la Mediana i l’IQR. | sklearn.preprocessing.RobustScalerrecipes::step_mutate |
Dades amb valors atípics significatius. | Baixa |
Ara ve la part més important. En aquesta secció, mostrarem com reaccionen les tres tècniques d’escalat quan apareix un valor extrem. Aquí és on realment veuràs la diferència pràctica entre elles i entendràs la lògica per escollir-ne una o una altra.
Primer, crearem dos conjunts de dades simples: les dades originals netes i una versió amb un valor atípic significatiu.
import numpy as np
# Les dades originals
original_data = np.array([[10.0], [20.0], [30.0], [40.0], [50.0]])
# Les mateixes dades però amb un valor atípic extrem 500
outlier_data = np.array([[10.0], [20.0], [30.0], [40.0], [500.0] ])
print("Dades originals: ", original_data.T,
"\nDades amb valor atípic: ", outlier_data.T)Dades originals: [[10. 20. 30. 40. 50.]]
Dades amb valor atípic: [[ 10. 20. 30. 40. 500.]]
library(tibble)
# Les dades originals netes
original_data <- tibble(feature = c(10, 20, 30, 40, 50))
# Les mateixes dades però amb un valor atípic extrem
outlier_data <- tibble(feature = c(10, 20, 30, 40, 500))
cat("Dades originals: [",
paste(original_data, collapse = " "),
"]\nDades amb valor atípic: [",
paste(outlier_data, collapse = " "), "]")Dades originals: [ c(10, 20, 30, 40, 50) ]
Dades amb valor atípic: [ c(10, 20, 30, 40, 500) ]
L’estandardització utilitza la mitjana (\(\mu\)) i la desviació estàndard (\(\sigma\)) per als seus càlculs. Com que ambdues estadístiques són fàcilment influenciades per valors extrems, aquesta tècnica és moderadament sensible als valors atípics.
from sklearn.preprocessing import StandardScaler
import numpy as np
# Escalar les dades originals
std_scaler = StandardScaler()
standardized_original_data = std_scaler.fit_transform(original_data)
# Escalar les dades amb valor atípic
std_scaler_outlier = StandardScaler()
standardized_outlier_data = std_scaler_outlier.fit_transform(outlier_data)
print("Mitjana apresa (dades originals): ", std_scaler.mean_,
"\nDesviació estàndard apresa (dades originals): ", std_scaler.scale_,
"\nDades originals estandarditzades: ", np.round(standardized_original_data.T, 3),
"\nMitjana apresa (dades amb valor atípic): ", std_scaler_outlier.mean_,
"\nDesviació estàndard apresa (dades amb valor atípic): ", std_scaler_outlier.scale_,
"\nDades amb valor atípic estandarditzades: ", np.round(standardized_outlier_data.T, 3)
)Mitjana apresa (dades originals): [30.]
Desviació estàndard apresa (dades originals): [14.14213562]
Dades originals estandarditzades: [[-1.414 -0.707 0. 0.707 1.414]]
Mitjana apresa (dades amb valor atípic): [120.]
Desviació estàndard apresa (dades amb valor atípic): [190.2629759]
Dades amb valor atípic estandarditzades: [[-0.578 -0.526 -0.473 -0.42 1.997]]
library(tidymodels)
library(dplyr)
# Recepta, preparació i aplicació per a les dades originals
recipe_orig_std <- recipe(~ feature, data = original_data) %>%
step_normalize(feature)
prep_recipe_orig_std <- prep(recipe_orig_std, training = original_data)
standardized_orig_data <- bake(prep_recipe_orig_std, new_data = original_data)
# Obtenir els paràmetres apresos
mean_sd_orig <- tidy(prep_recipe_orig_std, number = 1)
# Extreure els valors específics del tibble
mean_orig <- mean_sd_orig %>% filter(statistic == "mean") %>% pull(value)
sd_orig <- mean_sd_orig %>% filter(statistic == "sd") %>% pull(value)
# Recepta, preparació i aplicació per a les dades amb valor atípic
recipe_outlier_std <- recipe(~ feature, data = outlier_data) %>%
step_normalize(feature)
prep_recipe_outlier_std <- prep(recipe_outlier_std, training = outlier_data)
standardized_outlier_data <- bake(prep_recipe_outlier_std, new_data = outlier_data)
# Obtenir els paràmetres apresos
mean_sd_out <- tidy(prep_recipe_outlier_std, number = 1)
# Extreure els valors específics del tibble
mean_out <- mean_sd_out %>% filter(statistic == "mean") %>% pull(value)
sd_out <- mean_sd_out %>% filter(statistic == "sd") %>% pull(value)
cat(
"Mitjana apresa (dades originals): ", mean_orig,
"\nDesviació estàndard apresa (dades originals): ", sd_orig,
"\nDades originals estandarditzades: [", paste(round(standardized_orig_data, 3), collapse = " "), "]",
"\nMitjana apresa (dades amb valor atípic): ", mean_out,
"\nDesviació estàndard apresa (dades amb valor atípic): ", sd_out,
"\nDades amb valor atípic estandarditzades: [", paste(round(standardized_outlier_data, 3), collapse = " "), "]"
)Mitjana apresa (dades originals): 30
Desviació estàndard apresa (dades originals): 15.81139
Dades originals estandarditzades: [ c(-1.265, -0.632, 0, 0.632, 1.265) ]
Mitjana apresa (dades amb valor atípic): 120
Desviació estàndard apresa (dades amb valor atípic): 212.7205
Dades amb valor atípic estandarditzades: [ c(-0.517, -0.47, -0.423, -0.376, 1.786) ]
Anàlisi:
El valor atípic 500.0 va incrementar dràsticament tant la mitjana com la desviació estàndard. A causa que la nova desviació estàndard és tan gran, els valors transformats dels punts originals ([10, 20, 30, 40]) ara estan tots agrupats molt a prop entre ells. La tècnica ha reduït efectivament la seva variància, tractant-los com si fossin gairebé el mateix valor.
L’escalat Min-Max es defineix pels valors mínims i màxims absoluts de les dades, cosa que el fa altament sensible als valors atípics.
from sklearn.preprocessing import MinMaxScaler
import numpy as np
# Escalar les dades originals
scaler_minmax = MinMaxScaler()
normalized_orig_data = scaler_minmax.fit_transform(original_data)
# Escalar les dades amb valor atípic
scaler_minmax_outlier = MinMaxScaler()
normalized_outlier_data = scaler_minmax_outlier.fit_transform(outlier_data)
print("Dades originals normalitzades: ", normalized_orig_data.T,
"\nDades amb valor atípic normalitzades: ", np.round(normalized_outlier_data.T, 3))Dades originals normalitzades: [[0. 0.25 0.5 0.75 1. ]]
Dades amb valor atípic normalitzades: [[0. 0.02 0.041 0.061 1. ]]
library(tidymodels)
# Recepta per a les dades originals
recipe_orig_mm <- recipe(~ feature, data = original_data) %>% step_range(feature)
normalized_orig_data <- prep(recipe_orig_mm) %>% bake(new_data = NULL)
# Recepta per a les dades amb valor atípic
recipe_outlier_mm <- recipe(~ feature, data = outlier_data) %>% step_range(feature)
normalized_outlier_data <- prep(recipe_outlier_mm) %>% bake(new_data = NULL)
cat("Dades originals normalitzades: [", paste(normalized_orig_data, collapse = " "),
"]\nDades amb valor atípic normalitzades: [", paste(round(normalized_outlier_data, 3), collapse = " "), "]")Dades originals normalitzades: [ c(0, 0.25, 0.5, 0.75, 1) ]
Dades amb valor atípic normalitzades: [ c(0, 0.02, 0.041, 0.061, 1) ]
Anàlisi:
Observa com el valor atípic 500.0 es converteix en el nou màxim (1.0). Com a resultat, tots els punts de dades originals ara queden comprimids en una petita part del rang (de 0 a ~0.06). Les distàncies relatives entre aquests punts originals es perden gairebé completament. Aquest comportament pot arruïnar el rendiment de qualsevol model que depengui d’aquestes distàncies.
El RobustScaler utilitza la mediana i el Rang Interquartílic (IQR), que són estadístiques que no es veuen significativament afectades per uns pocs valors atípics extrems. Això el fa “robust”.
from sklearn.preprocessing import RobustScaler
# Escalar les dades originals
scaler_robust = RobustScaler()
scaled_orig_data = scaler_robust.fit_transform(original_data)
# Escalar les dades amb valor atípic
scaler_robust_outlier = RobustScaler()
scaled_outlier_data = scaler_robust_outlier.fit_transform(outlier_data)
print("Dades originals escalades: ", scaled_orig_data.T,
"\nDades amb valor atípic escalades: ", np.round(scaled_outlier_data.T, 3))Dades originals escalades: [[-1. -0.5 0. 0.5 1. ]]
Dades amb valor atípic escalades: [[-1. -0.5 0. 0.5 23.5]]
library(tidymodels)
# Recepta per a les dades originals
recipe_orig_robust <- recipe(~ feature, data = original_data) %>%
step_mutate(feature = (feature - median(feature)) / IQR(feature))
scaled_orig_data <- prep(recipe_orig_robust) %>% bake(new_data = NULL)
# Recepta per a les dades amb valor atípic
recipe_outlier_robust <- recipe(~ feature, data = outlier_data) %>%
step_mutate(feature = (feature - median(feature)) / IQR(feature))
scaled_outlier_data <- prep(recipe_outlier_robust) %>% bake(new_data = NULL)
cat("Dades originals escalades: [", paste(scaled_orig_data, collapse = " "),
"]\nDades amb valor atípic escalades: [", paste(round(scaled_outlier_data, 3), collapse = " "), "]")Dades originals escalades: [ c(-1, -0.5, 0, 0.5, 1) ]
Dades amb valor atípic escalades: [ c(-1, -0.5, 0, 0.5, 23.5) ]
Anàlisi:
Mira això! Els valors escalats són idèntics per als primers quatre punts de dades en tots dos escenaris! Com que la mediana (30.0) i el Rang Interquartílic (40.0 - 20.0 = 20) són exactament els mateixos per a tots dos conjunts de dades, l’escalat dels punts sense valors atípics no es veu afectat en absolut. El valor atípic es transforma en un nombre gran, però no distorsiona l’escalat de la resta de valors. Això preserva l’espaiat relatiu de les dades originals sense valors atípics, fet que converteix l’escalat robust en el clar guanyador quan hi ha valors atípics.
Per ampliar informació i veure els detalls oficials de les APIs, aquí tens els enllaços directes dels escaladors i funcions tractats en aquest post.
En aquest post hem vist tres de les tècniques d’escalat més comunes i hem explorat el seu comportament sobre la mateixa característica, incloent-hi com respon cadascuna davant valors atípics.
A la Part 3 de la sèrie Feature Scaling, aprofundirem en transformadors menys comuns però molt efectius: MaxAbsScaler, PowerTransformer i QuantileTransformer, amb una breu ullada a Normalizer, que persegueix un propòsit molt diferent però sovint es menciona al costat dels escaladors.