9 Metodos de estandarizacion para la Agronomia

Introduccion

En el modelado de datos siempre existen difentes tipos de variables con una diversidad de unidades de medidad, generalmente hay relaciones entre las definiciones, pero por su metodo de medicion no es frecuente verlo en una tabla como una unidad unica; Para eso existe la estandarización y en este documento estudiaremos 9 metodos que se diferencian por su capacidad estadistica y matematica. Hablaremos de los outliers (valores que se encuentran fuera del rango promedio) y encontraremos ejemplos practicos. Depsues de este articulo seremos capaces de utilizar R como lenguaje de programacion para aplicar estadistica en la agricultura.

# Fijamos una semilla para que los datos generados sean reproducibles
set.seed(123)

n <- 30 #Tamaño de la muestra

datos_agro <- data.frame (
  #pH: distribucion normal (valores tipicos alrededor de 6.5)
  pH = rnorm(n, mean = 6.5, sd = 0.5),
  # Salinidad (dS/m): Distribución asimétrica usando una exponencial
  salinidad = rexp(n, rate = 0.5),
  #erosion (%): Distribucion uniforme entre 0 y 100
  erosion = runif(n, min = 0, max = 100)
)
# Introducimos un par de valores extremos en la salinidad
datos_agro$salinidad[c(5, 12)] <- c(25, 30)

# Vemos las primeras filas
head(datos_agro)
##         pH  salinidad   erosion
## 1 6.219762  0.6604608 14.709469
## 2 6.384911  5.1937842 93.529980
## 3 7.279354  2.4580515 30.122890
## 4 6.535254  1.5813635  6.072057
## 5 6.564644 25.0000000 94.772694
## 6 7.357532  2.5092820 72.059627

Ahora que tenemos los datos, empecemos con la lista de los 9 métodos. El primer método, y el más común de todos, es la Estandarización Clásica (Z-score).

Estandarización Clásica (Z-score).

Primero, para describir lo que es la estandarizacion (z-score) entendemos la diferencia de dos unidades de medida como que tan diferente es un dato de otro; y esa diferencia entre mas lejana o cercana con el valor a otro se puede entender como muy diferente o muy similar. Cuando dividimos lo que buscamos es separar algo en partes iguales, entonces, este modelo de estandarizacion, estoy midiendo cuanto difiere la temperatura y mi media en que tan alejado esta de los demas datos.

# Método 1: Z-score (manual)
z_score_pH <- (datos_agro$pH - mean(datos_agro$pH)) / sd(datos_agro$pH)

z_score_pH
##  [1] -0.5232985 -0.1866137  1.6368622  0.1198863  0.1798022  1.7962422
##  [7]  0.5178431 -1.2415080 -0.6521193 -0.4062648  1.2957653  0.4147858
## [13]  0.4565354  0.1608374 -0.5185744  1.8694796  0.5554915 -1.9566293
## [19]  0.7629319 -0.4339188 -1.0404567 -0.1741751 -0.9978288 -0.6949706
## [25] -0.5891105 -1.6712928  0.9020011  0.2043533 -1.1121295  1.3260734
# Nota de R: También existe una función rápida que hace esto automáticamente
# z_score_pH_rapido <- scale(datos_agro$pH)

Tambien voy a realizar un ejercico con la temparatura, variable fundamental en cualquier ejercicio de analisis de datos.

temp <- c(18, 21, 25, 31, 12)
mean_temp <- mean(temp)
desviasion <- sd(temp)
z_score_temp <- (temp - mean_temp) / desviasion

# Verifiquemos algo importante:
z_score_temp
## [1] -0.47470110 -0.05584719  0.50262469  1.34033251 -1.31240891
mean(z_score_temp)   # ¿Qué espero que salga aquí?
## [1] 1.873501e-16
sd(z_score_temp)     # ¿Y aquí?
## [1] 1

De pronto, puede parecer a simple vista que [1] 1.873501e-16 es un numero extremadamente pequeño, tanto que asi que llega a ser 0, porque si hay 16 ceros por delante del 1, eso es practicamente 0, pero los metodos matematicos que se usan en R no conocen el 0 exacto, por eso se expresa de esa manera.

Y ¿que siginifica que la desviasion estadar del z-score sea 1?, sencillo, porque la temperatura dejo de ser grados centigrados. la unidad de medida ahora es “desviasion estandar” y cuando vemos como avanza el primer resultado, que es el zscore de la temperatura, vemos que avanza de a 1 unidad.

Por lo tanto El Z-score me sirve para comparar temperatura con peso con salarios porque deja todas las variables en la misma unidad de medida

Con esto claro podemos continuar con el siguiente metodo de estandarizacion.

Estandarización Min-Max (Normalización).

Antes de mostrar la formula vamos a pensar en un problema real, imaginemos que tenemos los siguientes salarios en dolares.

(salario <- c(500, 520, 510, 515, 50000))
## [1]   500   520   510   515 50000

El utlimo salario es el del CEO, es un outlier brutal, si estandarizara esos datos con zscore, ¿que prodria suceder?, vamos a hacer la prueba

(z_score_outlier <- scale(salario))
##            [,1]
## [1,] -0.4477219
## [2,] -0.4468182
## [3,] -0.4472700
## [4,] -0.4470441
## [5,]  1.7888543
## attr(,"scaled:center")
## [1] 10409
## attr(,"scaled:scale")
## [1] 22132.04

El valor del salario del CEO es extramedamente elevado en comparacion con los demas y por ende los demas valores quedaron resagados porque el CEO aplasto toda la tabla hacia el

CEO: ████████████████████ +1.79 500: ▌ -0.447 520: ▌ -0.446 510: ▌ -0.447 515: ▌ -0.447

Ese outlier corrompe la estandarización. Acá es donde entra a participar el siguiente metodo de estandarización y la pregunta que uno se plantea es como lograr matematicamente dejar entre 0 y 1 todos los valores de \(x\), entonces pensemos que restamos \(x - min\) definimos \(min\) como el valor minimo, y si divido el valor maximo por el valor minimo logicamente obtendre \(1\), por lo tanto si construimos correctamente la formula tendremos el siguiente resultado.

\(X_{norm} = \frac{X - X_{min}}{X_{max} - X_{min}}\)

Para escribir esta funcion en r debemos conocer que existe la funcion min() y max() y por ende puedo replicar con exito la funcion en R.

minmax <- (salario - min(salario)) / (max(salario) - min(salario))
minmax
## [1] 0.0000000000 0.0004040404 0.0002020202 0.0003030303 1.0000000000

el mismo problema que con Z-score, Los 4 salarios normales quedaron aplastados cerca de 0, prácticamente indistinguibles entre sí, mientras el CEO acapara todo el rango disponible; este metodo de estandarizacion tambien nos esta deformando los datos.

Entonces necesitamos una escala que aunque tengamos numeros grandes y pequeños, no haya perdida de “importancia” de la informacion.

Vamos a recordar en esta ocasion a la mediana que es el valor medio en matriz de valores; el ejemplo actual que vamos a tener en cuenta es el siguiente.

datos_mediana <- c(10, 15, 20, 25, 30, 35, 40, 45)

Como se puede observar tenemos un arreglo de datos donde no existe mediana “definida” ya que la mitad de los datos esta justo aca (10, 15, (20, 25 | 30, 35) , 40, 45), por lo tanto para definir la mediana separamos nuevamente en la mtiad.

Cuando hay un número par de datos en cada mitad, el centro es el promedio de los dos valores medios, entonces resalto los centros, y lo que se debe hacer en estos casos es el promedio de esos valores medios.

Q1 = 22.5 (percentil 25 — centro de la mitad inferior) Q3 = 32.5 (percentil 75 — centro de la mitad superior)

IQR = Q3 - Q1 = 32.5 - 22.5 = 10

Ya que conocemos lo que es el IQR, que forma parte del metodo de estandarizacion que veremos a continuacion, realizaremos el analisis correspondiente con codigo.

Robust Scaling

\(X_{robust} = \frac{X - mediana}{IQR}\)

salario <- c(500, 520, 510, 515, 50000)
robust_scaling <- (salario - median(salario)) / IQR(salario)
robust_scaling
## [1]   -1.5    0.5   -0.5    0.0 4948.5

Z-score: -0.447, -0.446, -0.447, -0.447 ← todos aplastados Min-Max: 0.000, 0.000, 0.000, 0.000 ← indistinguibles Robust Scaling: -1.500, 0.500, -0.500, 0.000 ← ¡se pueden distinguir!

Los 4 salarios normales ahora tienen valores distintos y se pueden comparar entre sí. Eso es la robustez.

Con esto logramos disinguir los datos de una manera correcta sin perder logica en los datos, con esto claro podemos pasar al siguiente metodo de estandarizacion.

Log Transformation

Recordemos que la operacion del logaritmo es la inversa de la potencia, si en la potencia tenemos un x^2 ese valor de x asignado se multuplica dos veces por si mismo y el resultado es la potencia; El logaritmo es lo inverso, saber cuantas veces elevo la base del logaritmo para encontrar el resultado.

Ej: \(log_2 (8) = {x}\) la x de ese logaritmo sera 3

Lo que vamos a conseguir con esto es organizar esos datos grandes para hacerlos mas manejables, para hacer analisis y tomar decisiones en base a eso.

Ej: logaritmo con numeros elevados.

log(10) = 1 log(100) = 2 log(1000) = 3 log(10000) = 4 log(50000) ≈ 4.7

log_salarios <- log(salario)
log_salarios
## [1]  6.214608  6.253829  6.234411  6.244167 10.819778

El problema con este metodo de estandarizacion es si existiera un 0 en los datos, eso en los numeros reales no existe y nos retornaria un error, ya que no hay ningun numero que al elevarse nos de 0.

Por lo anterior cuando hay ceros en la practica se hace

\(log(X + 1)\)

Porque cualquier numero elevado a 0 es 1.

Decimal Scaling

Este es el metodo mas intuitivo de todos, su forma es simplisima, porfavor imaginemos este grupo de datos 1500, 2300, 800, 4200, 950. el numero mas grande tiene 4 digitos.

La transformación divide todos los datos entre 10 elevado a la cantidad de dígitos del máximo.

Entonces si el máximo tiene 4 dígitos → divides entre \(10^4=10000\)

decimal_scalling_data <- c(1500, 2300, 800, 4200, 950)
decimal_scalling <- (decimal_scalling_data / 10**4 )
decimal_scalling
## [1] 0.150 0.230 0.080 0.420 0.095

Nuevamente, este metodo de estandarizacion es practico para hacer manejo de datos que son demasiado grandes, ya que la escala no permite subirse por encima de 1.

Max Absolute Scaling

Este es el metodo mas corto de todos, la formula es:

$X_{scaled} = {| X_{max} |} $

El valor absoluto es para evitar que las dimesiones sean negativas, cada dato como fracción del máximo. Si el máximo es 4200 y tu dato es 2100, cabe exactamente 0.5 veces, es la mitad del máximo.

data <- c(1500, 2300, 800, 4200, 950)

El valor abosluto en R es abs(), ahora vamos a escribir la formula en R.

max_absolute_formula <- data / max(abs(data))
max_absolute_formula
## [1] 0.3571429 0.5476190 0.1904762 1.0000000 0.2261905

Como divido por el valor maximo si hago la operacion del valor maximo sobre el valor maximo es 1, y ahi tenemos el resultado.

Ahora vamos a hacer un analisis con cada uno de los 6 metodos hasta el momento.

data <- c(1500, 2300, 800, 4200, 950)

zscore    <- (data - mean(data)) / sd(data)
minmax    <- (data - min(data)) / (max(data) - min(data))
robust    <- (data - median(data)) / IQR(data)
log_t     <- log(data)
decimal   <- data / 10^4
maxabs    <- data / max(abs(data))

tabla <- data.frame(
  Original = data,
  ZScore   = round(zscore, 3),
  MinMax   = round(minmax, 3),
  Robust   = round(robust, 3),
  Log      = round(log_t, 3),
  Decimal  = round(decimal, 3),
  MaxAbs   = round(maxabs, 3)
)

print(tabla)
##   Original ZScore MinMax Robust   Log Decimal MaxAbs
## 1     1500 -0.324  0.206  0.000 7.313   0.150  0.357
## 2     2300  0.252  0.441  0.593 7.741   0.230  0.548
## 3      800 -0.828  0.000 -0.519 6.685   0.080  0.190
## 4     4200  1.621  1.000  2.000 8.343   0.420  1.000
## 5      950 -0.720  0.044 -0.407 6.856   0.095  0.226

Podemos evidenciar una secuencia interesante en el decimal y el max, y se trata de que ambos conservan las proporciones siempre, eso se llama transformacion lineal.

Quantile Normalization

Este es el más diferente de todos los que hemos visto. No usa fórmulas algebraicas sino rangos y probabilidades. en lugar de preguntar ¿cuánto vale este dato?, pregunta ¿qué proporción de datos está por debajo de este?

Por ejemplo, si en un examen sacamos 85 y el 90% de los estudiantes sacó menos, el cuantil es 0.90.

Entonces si tengo el siguiente grupo de datos: 800, 950, 1500, 2300, 4200 los cuantil seguirian los siguientes:

1500 → 40% de datos por debajo → cuantil = 0.40 ✅ 4200 → 80% de datos por debajo → cuantil = 0.80 ✅

Hay distintas convenciones pero en R la función ecdf() (Empirical Cumulative Distribution Function) lo maneja automáticamente.

En R la Quantile Normalization se hace así:

data <- c(1500, 2300, 800, 4200, 950)

# ecdf nos da una función que calcula el cuantil de cualquier valor
cuantil_func <- ecdf(data)

# Aplicamos esa función a todos los datos
quantile_norm <- cuantil_func(data)
quantile_norm
## [1] 0.6 0.8 0.2 1.0 0.4

800 → 0.2 950 → 0.4 1500 → 0.6 2300 → 0.8 4200 → 1.0

salario <- c(500, 520, 510, 515, 50000)
cuantil_func2 <- ecdf(salario)
cuantil_func2(salario)
## [1] 0.2 0.8 0.4 0.6 1.0

Eso es la gran ventaja del Quantile Normalization — es completamente robusto a outliers porque no le importa el valor del dato sino su posición en el ranking.

Box-Cox

Antes de trabajar con este metodo de estandarizacion tenemos que entender lo que es una distribuicion normal; para comprenderlo veamos el siguiente grafico.

Nota 1-2: ██ (pocos) Nota 3-4: █████ Nota 4-5: ████████████ (muchos) Nota 5-6: ████████████████████ (la mayoría) Nota 6-7: ████████████ (muchos) Nota 7-8: █████ Nota 8-10: ██ (pocos)

En una distribución normal, media ≈ mediana ≈ moda, todas en el centro de la campana. Pero muchos datos en el mundo real no se representanc como una campana de gaus; Por ejemplo los salarios.

Frecuencia │ │██ │████ │██████ │█████████ │_____________→ salario 500 50000

Esta forma se llama distribución sesgada la cola va hacia la derecha. La mayoría gana poco y pocos ganan muchísimo.

Box-Cox es básicamente una generalización del logaritmo. En lugar de aplicar siempre log, busca automáticamente la mejor transformación.

library(MASS)
data <- c(1500, 2300, 800, 4200, 950)
modelo <- lm(data ~ 1)
#boxcox(modelo)

Ahora voy a cambiar el valor de delta de manera manual

lambda_optimo <- -0.5
data_boxcox <- (data^lambda_optimo - 1) / lambda_optimo
print(data_boxcox, digits = 6)
## [1] 1.94836 1.95830 1.92929 1.96914 1.93511
#datos_negativos <- c(-500, -200, 100, 300, 800)
#modelo2 <- lm(datos_negativos ~ 1)
#library(MASS)
#boxcox(modelo2)

Entonces como el valor es negativo box-cox se ve limitado, ademas de que invierte el valor de los numeros porque numeros grandes los redimensiona a valores mas pequeños, y para valores mas pequeños se vuelven mas grandes.

Yeo-Johnson es una extensión de Box-Cox que maneja cualquier valor real.

Yeo-Johnson

Está en el paquete bestNormalize, vamos a instalar el paquete

library(bestNormalize)
## Registered S3 method overwritten by 'generics':
##   method                 from   
##   as.character.dev_topic butcher
## 
## Attaching package: 'bestNormalize'
## The following object is masked from 'package:MASS':
## 
##     boxcox
datos_negativos <- c(-500, -200, 100, 300, 800)

Ahora vamos a usar el noveno y ultimo metodo de estandarizacion de datos.

noveno <- yeojohnson(datos_negativos)
noveno$x.t
## [1] -1.26426952 -0.58575502  0.05115305  0.43150000  1.36737148

Se parecen mucho al Z-score — centrados cerca de 0, con valores negativos y positivos. Eso es porque Yeo-Johnson también normaliza la distribución automáticamente.

Tabla final de comparacion

data <- c(1500, 2300, 800, 4200, 950)

tabla_final <- data.frame(
  Original  = data,
  ZScore    = round((data - mean(data)) / sd(data), 3),
  MinMax    = round((data - min(data)) / (max(data) - min(data)), 3),
  Robust    = round((data - median(data)) / IQR(data), 3),
  Log       = round(log(data), 3),
  Decimal   = round(data / 10^4, 3),
  MaxAbs    = round(data / max(abs(data)), 3),
  Quantile  = round(ecdf(data)(data), 3),
  BoxCox    = round((data^(-0.5) - 1) / (-0.5), 3),
  YeoJohn   = round(yeojohnson(data)$x.t, 3)
)

print(tabla_final)
##   Original ZScore MinMax Robust   Log Decimal MaxAbs Quantile BoxCox YeoJohn
## 1     1500 -0.324  0.206  0.000 7.313   0.150  0.357      0.6  1.948   0.008
## 2     2300  0.252  0.441  0.593 7.741   0.230  0.548      0.8  1.958   0.620
## 3      800 -0.828  0.000 -0.519 6.685   0.080  0.190      0.2  1.929  -1.138
## 4     4200  1.621  1.000  2.000 8.343   0.420  1.000      1.0  1.969   1.301
## 5      950 -0.720  0.044 -0.407 6.856   0.095  0.226      0.4  1.935  -0.791

La estandarización es una herramienta fundamental en el análisis de datos que permite expresar variables en una unidad común, eliminando el efecto de las diferencias de escala entre variables medidas en distintas unidades. La elección del método depende directamente de la naturaleza de los datos:

Cuando los datos son simétricos y sin outliers, Z-score es suficiente. Cuando existen valores extremos, métodos robustos como Robust Scaling o Yeo-Johnson son preferibles, ya que no se ven afectados por ellos. Cuando los datos incluyen valores negativos, Box-Cox queda descartado y Yeo-Johnson es la opción correcta. Cuando se necesita un rango fijo [0,1], Min-Max o Max Absolute Scaling son los indicados. Cuando la distribución es muy sesgada, Log Transformation o Box-Cox ayudan a aproximarla a una distribución normal.

No existe un método universalmente superior — la decisión siempre debe basarse en explorar los datos primero.