pacman::p_load(dplyr,
sjmisc,
sjPlot,
kableExtra,
summarytools,
psych,
ggplot2)Práctico 05. Estandarización de coeficientes
Métodos estadísticos para Ciencias Sociales III
1 Presentación
1.1 Objetivo de la práctica
En el práctico anterior estimamos modelos de regresión múltiple y quedó planteada una pregunta que no pudimos responder: si un modelo tiene varios predictores medidos en unidades distintas, ¿cuál de ellos pesa más?
Esta guía responde esa pregunta mediante la estandarización de variables. Al terminar, usted debería ser capaz de:
- Explicar por qué dos coeficientes de regresión no siempre son comparables entre sí
- Calcular un puntaje Z a mano y con la función
scale() - Estimar un modelo con variables estandarizadas e interpretar sus coeficientes
- Verificar qué cambia y qué permanece idéntico al estandarizar
- Convertir un coeficiente de una escala a la otra sin reestimar el modelo
- Decidir cuándo conviene reportar coeficientes estandarizados y cuándo no
Esta mañana trabajaremos el procedimiento en R. En la sesión de la tarde revisaremos con más detalle sus fundamentos y sus límites.
1.2 Los datos
Continuamos con el estatus social subjetivo en ELSOC 2016, el mismo modelo del práctico anterior. Más adelante incorporaremos un segundo ejemplo con una variable construida a partir de una escala.
2 Preparación
2.1 Librerías
rm(list = ls())
options(scipen = 999)
# Ajustes para que las tablas de summarytools se vean bien en Quarto
st_options(bootstrap.css = FALSE,
plain.ascii = FALSE,
style = "rmarkdown",
dfSummary.silent = TRUE,
footnote = NA,
subtitle.emphasis = FALSE)2.2 Carga y procesamiento
load(url("https://github.com/Kevin-carrasco/metod3-unab/raw/refs/heads/main/files/data/elsoc16-22.RData"))proc_data <- elsoc_long_2016_2022.2 %>%
filter(ola == 1) %>%
select(d01_01, m01, m29, m0_edad) %>%
set_na(., na = c(-999, -888, -777, -666)) %>%
rename("estatus" = d01_01,
"educacion" = m01,
"ingreso" = m29,
"edad" = m0_edad) %>%
na.omit() %>%
filter(ingreso >= 50000, ingreso <= 5000000) %>%
mutate(ing100 = ingreso / 100000) %>%
select(estatus, educacion, ing100, edad)
dim(proc_data)[1] 2301 4
2.3 El modelo del práctico anterior
Retomamos el modelo con el que cerramos el práctico anterior:
modelo <- lm(estatus ~ educacion + ing100 + edad, data = proc_data)tab_model(modelo,
show.se = TRUE,
show.ci = FALSE,
digits = 4,
p.style = "stars",
dv.labels = c("Estatus social subjetivo"),
pred.labels = c("(Intercepto)", "Nivel educacional",
"Ingreso (cientos de miles)", "Edad"),
string.pred = "Predictores",
string.est = "β")| Estatus social subjetivo | ||
|---|---|---|
| Predictores | β | std. Error |
| (Intercepto) | 2.9215 *** | 0.1465 |
| Nivel educacional | 0.1611 *** | 0.0165 |
| Ingreso (cientos de miles) | 0.0483 *** | 0.0061 |
| Edad | 0.0063 ** | 0.0021 |
| Observations | 2301 | |
| R2 / R2 adjusted | 0.113 / 0.111 | |
| * p<0.05 ** p<0.01 *** p<0.001 | ||
Los tres predictores son estadísticamente significativos y el modelo explica algo más de un 11% de la varianza. Hasta aquí, todo conocido.
3 El problema
3.1 ¿Cuál predictor pesa más?
Observe la columna de coeficientes. El de educación es aproximadamente veinticinco veces mayor que el de edad. ¿Significa eso que la educación es veinticinco veces más importante?
La respuesta es que no, y la razón está en las unidades de medida de cada variable. Revisemos los descriptivos:
dfSummary(proc_data,
varnumbers = FALSE,
valid.col = FALSE,
graph.magnif = 0.75,
headings = FALSE) %>%
print(method = "render")| Variable | Stats / Values | Freqs (% of Valid) | Graph | Missing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| estatus [numeric] |
|
11 distinct values | 0 (0.0%) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| educacion [numeric] |
|
|
0 (0.0%) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ing100 [numeric] |
|
178 distinct values | 0 (0.0%) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| edad [numeric] |
|
63 distinct values | 0 (0.0%) |
Un año adicional de edad es un cambio muy pequeño dentro de una variable que recorre más de cincuenta años. Un nivel educacional adicional, en cambio, es un cambio grande dentro de una escala de diez categorías. Comparar ambos coeficientes es como comparar una distancia medida en milímetros con otra medida en kilómetros.
3.2 Una comprobación
Para ver que el problema es de unidades y no sustantivo, hagamos un experimento: expresemos la edad en décadas en lugar de años.
proc_data$edad_decadas <- proc_data$edad / 10
modelo_decadas <- lm(estatus ~ educacion + ing100 + edad_decadas,
data = proc_data)
c(edad_en_anios = coef(modelo)[4],
edad_en_decadas = coef(modelo_decadas)[4]) edad_en_anios.edad edad_en_decadas.edad_decadas
0.006267146 0.062671459
El coeficiente se multiplicó por diez sin que nada haya cambiado en los datos ni en la relación entre las variables. Lo único que cambió fue la unidad en que medimos el predictor.
c(r2_original = summary(modelo)$r.squared,
r2_decadas = summary(modelo_decadas)$r.squared)r2_original r2_decadas
0.1126182 0.1126182
El ajuste del modelo es idéntico. Esto confirma que la magnitud de un coeficiente, por sí sola, no informa nada sobre la importancia de un predictor.
proc_data$edad_decadas <- NULL # eliminamos la variable auxiliar4 La estandarización
4.1 El puntaje Z
La solución consiste en llevar todas las variables a una escala común. El procedimiento es el puntaje Z: a cada valor se le resta el promedio de su variable y se divide por su desviación estándar.
\[z_x=\frac{x-\bar{x}}{sd(x)}\]
El resultado se interpreta en desviaciones estándar: un valor de \(z = 1\) indica que el caso está una desviación estándar por sobre el promedio, y uno de \(z = -0{,}5\), media desviación estándar por debajo.
4.2 Cálculo manual
Hagámoslo primero a mano, para una sola variable:
media_educacion <- mean(proc_data$educacion)
de_educacion <- sd(proc_data$educacion)
proc_data$educacion_z <- (proc_data$educacion - media_educacion) / de_educacionComparemos los primeros casos:
head(proc_data %>% select(educacion, educacion_z), 8) %>%
kable(digits = 3,
col.names = c("Educación (original)", "Educación (estandarizada)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| Educación (original) | Educación (estandarizada) |
|---|---|
| 2 | -1.449 |
| 4 | -0.532 |
| 5 | -0.074 |
| 5 | -0.074 |
| 5 | -0.074 |
| 9 | 1.760 |
| 9 | 1.760 |
| 4 | -0.532 |
Verifiquemos que la variable transformada tiene las propiedades esperadas:
c(media = mean(proc_data$educacion_z), de = sd(proc_data$educacion_z)) media de
0.0000000000000001779002 1.0000000000000000000000
El promedio es cero (salvo error de redondeo computacional) y la desviación estándar es uno.
4.3 La función scale()
Naturalmente, no haremos esto variable por variable. La función scale() estandariza todas las columnas de una base de datos:
datos_std <- data.frame(scale(datos))
La función data.frame() es necesaria porque scale() devuelve una matriz, y lm() espera un data.frame.
proc_data$educacion_z <- NULL # eliminamos la variable que creamos a mano
proc_data_std <- data.frame(scale(proc_data))Comparemos los descriptivos antes y después:
descr(proc_data,
stats = c("mean", "sd", "min", "med", "max", "n.valid"),
transpose = TRUE,
order = "preserve") %>%
kable(digits = 2, caption = "Variables originales") %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| Mean | Std.Dev | Min | Median | Max | N.Valid | |
|---|---|---|---|---|---|---|
| estatus | 4.33 | 1.53 | 0.0 | 5.0 | 10 | 2301 |
| educacion | 5.16 | 2.18 | 1.0 | 5.0 | 10 | 2301 |
| ing100 | 5.88 | 5.58 | 0.5 | 4.5 | 50 | 2301 |
| edad | 46.07 | 15.14 | 18.0 | 46.0 | 88 | 2301 |
descr(proc_data_std,
stats = c("mean", "sd", "min", "med", "max", "n.valid"),
transpose = TRUE,
order = "preserve") %>%
kable(digits = 2, caption = "Variables estandarizadas") %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| Mean | Std.Dev | Min | Median | Max | N.Valid | |
|---|---|---|---|---|---|---|
| estatus | 0 | 1 | -2.83 | 0.44 | 3.72 | 2301 |
| educacion | 0 | 1 | -1.91 | -0.07 | 2.22 | 2301 |
| ing100 | 0 | 1 | -0.96 | -0.25 | 7.91 | 2301 |
| edad | 0 | 1 | -1.85 | 0.00 | 2.77 | 2301 |
Note que todas las variables quedaron con media 0 y desviación estándar 1, incluida la variable dependiente. El mínimo y el máximo, en cambio, son distintos para cada una, porque dependen de cuán alejados estén los valores extremos respecto del promedio.
Tanto sjmisc como summarytools incluyen una función llamada descr, y hacen cosas distintas. Cuando dos paquetes cargados definen el mismo nombre, R usa el del paquete cargado más recientemente, que aquí es summarytools.
De ahí el argumento transpose = TRUE: sin él, summarytools::descr ubica los estadísticos en las filas y las variables en las columnas, que es lo contrario de lo habitual. El argumento order = "preserve" evita además que las variables se ordenen alfabéticamente.
Si quiere asegurarse de cuál función está usando, escriba el nombre del paquete de forma explícita, como en sjmisc::descr() o summarytools::descr().
scale()
La función estandariza todas las columnas de la base, sin distinguir. Si su base contiene identificadores, variables categóricas o cualquier columna que no corresponda estandarizar, debe seleccionar antes las variables del modelo. Por eso en el procesamiento inicial nos quedamos únicamente con las cuatro variables de interés.
5 El modelo estandarizado
5.1 Estimación
La sintaxis es exactamente la misma; lo único que cambia es la base de datos:
modelo_std <- lm(estatus ~ educacion + ing100 + edad, data = proc_data_std)
coef(modelo_std) (Intercept) educacion ing100
0.00000000000000004624409 0.23011065196029412138756 0.17646974878240034856347
edad
0.06215195141068993039957
5.2 Comparación
tab_model(list(modelo, modelo_std),
show.se = TRUE,
show.ci = FALSE,
digits = 3,
p.style = "stars",
dv.labels = c("No estandarizado", "Estandarizado"),
pred.labels = c("(Intercepto)", "Nivel educacional",
"Ingreso (cientos de miles)", "Edad"),
string.pred = "Predictores",
string.est = "β")| No estandarizado | Estandarizado | |||
|---|---|---|---|---|
| Predictores | β | std. Error | β | std. Error |
| (Intercepto) | 2.922 *** | 0.146 | 0.000 | 0.020 |
| Nivel educacional | 0.161 *** | 0.016 | 0.230 *** | 0.024 |
| Ingreso (cientos de miles) | 0.048 *** | 0.006 | 0.176 *** | 0.022 |
| Edad | 0.006 ** | 0.002 | 0.062 ** | 0.021 |
| Observations | 2301 | 2301 | ||
| R2 / R2 adjusted | 0.113 / 0.111 | 0.113 / 0.111 | ||
| * p<0.05 ** p<0.01 *** p<0.001 | ||||
5.3 Interpretación
Los coeficientes estandarizados se leen en desviaciones estándar de la variable dependiente por cada desviación estándar de la independiente.
round(coef(modelo_std), 3)(Intercept) educacion ing100 edad
0.000 0.230 0.176 0.062
- Educación: por cada desviación estándar adicional de nivel educacional, el estatus subjetivo aumenta el valor del coeficiente en desviaciones estándar, manteniendo constantes el ingreso y la edad
- Ingreso: la misma lectura, por cada desviación estándar adicional de ingreso del hogar
- Edad: ídem
Y ahora sí pueden compararse entre sí. Observe que la distancia entre educación e ingreso es mucho menor de lo que sugerían los coeficientes originales: en escala bruta el de educación parecía más de tres veces mayor, y estandarizados resultan bastante cercanos.
En la escala original, el coeficiente del ingreso parecía casi despreciable. Estandarizado, resulta tener un peso comparable al de la educación. La impresión inicial era un artefacto de las unidades de medida.
6 Qué cambia y qué no
6.1 El ajuste no cambia
c(original = summary(modelo)$r.squared,
estandarizado = summary(modelo_std)$r.squared) original estandarizado
0.1126182 0.1126182
Estandarizar no altera el poder explicativo del modelo. Es la misma información expresada en otra escala.
6.2 La inferencia tampoco
round(summary(modelo)$coefficients[, 3:4], 4) t value Pr(>|t|)
(Intercept) 19.9484 0.0000
educacion 9.7820 0.0000
ing100 7.9194 0.0000
edad 2.9574 0.0031
round(summary(modelo_std)$coefficients[, 3:4], 4) t value Pr(>|t|)
(Intercept) 0.0000 1.0000
educacion 9.7820 0.0000
ing100 7.9194 0.0000
edad 2.9574 0.0031
Los valores \(t\) y los valores p de los predictores son idénticos. Un coeficiente significativo lo sigue siendo, y uno que no lo era tampoco pasa a serlo. La estandarización responde a la pregunta de la importancia relativa, no a la de significación.
6.3 El intercepto sí cambia
c(original = coef(modelo)[1], estandarizado = coef(modelo_std)[1]) original.(Intercept) estandarizado.(Intercept)
2.92152986247441726064267 0.00000000000000004624409
En el modelo estandarizado el intercepto es cero, porque todas las variables fueron centradas en su promedio. Deja de tener cualquier interpretación sustantiva.
6.4 Una alternativa con tab_model
La librería sjPlot puede mostrar ambas columnas en una sola tabla, sin necesidad de estimar un segundo modelo:
tab_model(modelo,
show.std = "std",
show.se = TRUE,
show.ci = FALSE,
digits = 3,
p.style = "stars",
dv.labels = c("Estatus subjetivo"),
pred.labels = c("(Intercepto)", "Nivel educacional",
"Ingreso (cientos de miles)", "Edad"),
string.pred = "Predictores")| Estatus subjetivo | ||||
|---|---|---|---|---|
| Predictores | Estimates | std. Error | std. Beta | standardized std. Error |
| (Intercepto) | 2.922 *** | 0.146 | 0.000 | 0.020 |
| Nivel educacional | 0.161 *** | 0.016 | 0.230 | 0.024 |
| Ingreso (cientos de miles) | 0.048 *** | 0.006 | 0.176 | 0.022 |
| Edad | 0.006 ** | 0.002 | 0.062 | 0.021 |
| Observations | 2301 | |||
| R2 / R2 adjusted | 0.113 / 0.111 | |||
| * p<0.05 ** p<0.01 *** p<0.001 | ||||
Esta es la forma habitual de reportarlo en un artículo cuando interesan ambas lecturas.
7 Un segundo ejemplo
Hasta aquí trabajamos con variables medidas directamente. El caso donde la estandarización resulta más necesaria es el de las variables construidas, cuyas unidades son arbitrarias.
7.1 Construcción de una escala
El módulo de salud y bienestar de ELSOC incluye nueve ítems sobre la frecuencia de distintos estados anímicos durante las últimas dos semanas, con categorías de respuesta que van desde “nunca” hasta “todos los días”.
animo <- elsoc_long_2016_2022.2 %>%
filter(ola == 1) %>%
select(s11_01, s11_02, s11_03, s11_04, s11_05,
s11_06, s11_07, s11_08, s11_09,
educacion = m01,
ingreso = m29,
edad = m0_edad) %>%
set_na(., na = c(-999, -888, -777, -666)) %>%
na.omit() %>%
filter(ingreso >= 50000, ingreso <= 5000000)Recodificamos los ítems para que la categoría más baja sea 0:
animo <- animo %>%
mutate(across(s11_01:s11_09, ~ .x - 1))Antes de sumar los ítems conviene verificar que efectivamente miden lo mismo. Una medida habitual de consistencia interna es el alfa de Cronbach:
psych::alpha(animo %>% select(s11_01:s11_09))$total$raw_alpha[1] 0.8520755
El valor obtenido es alto, lo que respalda la decisión de combinarlos en un solo puntaje. La construcción y validación de escalas es materia de la última unidad del curso; aquí nos basta con esta verificación mínima.
animo <- animo %>%
mutate(ing100 = ingreso / 100000,
sintomas = rowSums(across(s11_01:s11_09))) %>%
select(sintomas, educacion, ing100, edad)
summary(animo$sintomas) Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 2.000 5.000 6.121 9.000 36.000
7.2 El problema de las unidades arbitrarias
¿Cuánto es “un punto” en esta escala? La respuesta es que depende enteramente de cómo la construimos: si hubiéramos promediado en lugar de sumar, o si la escala tuviera doce ítems en lugar de nueve, el mismo fenómeno produciría coeficientes de magnitud muy distinta.
modelo_animo <- lm(sintomas ~ educacion + ing100 + edad, data = animo)
animo_std <- data.frame(scale(animo))
modelo_animo_std <- lm(sintomas ~ educacion + ing100 + edad, data = animo_std)tab_model(list(modelo_animo, modelo_animo_std),
show.se = TRUE,
show.ci = FALSE,
digits = 3,
p.style = "stars",
dv.labels = c("No estandarizado", "Estandarizado"),
pred.labels = c("(Intercepto)", "Nivel educacional",
"Ingreso", "Edad"),
string.pred = "Predictores",
string.est = "β")| No estandarizado | Estandarizado | |||
|---|---|---|---|---|
| Predictores | β | std. Error | β | std. Error |
| (Intercepto) | 8.581 *** | 0.582 | -0.000 | 0.021 |
| Nivel educacional | -0.294 *** | 0.066 | -0.111 *** | 0.025 |
| Ingreso | -0.086 *** | 0.024 | -0.083 *** | 0.023 |
| Edad | -0.010 | 0.008 | -0.025 | 0.022 |
| Observations | 2285 | 2285 | ||
| R2 / R2 adjusted | 0.026 / 0.024 | 0.026 / 0.024 | ||
| * p<0.05 ** p<0.01 *** p<0.001 | ||||
Observe nuevamente el patrón: en escala original el coeficiente de educación parece más de tres veces mayor que el de ingreso, y estandarizados resultan mucho más cercanos. Note también que la edad no alcanza significación estadística en ninguno de los dos modelos, lo que confirma que estandarizar no modifica la inferencia.
8 Ejercicios
Cargue la base ELSOC y filtre por la ola 4 (2019). Seleccione
d01_01,m01,m29ym0_edad, recodifique los valores perdidos, aplique el mismo criterio de limpieza al ingreso y exprese el ingreso en cientos de miles.Estime el modelo de estatus subjetivo con los tres predictores y reporte sus coeficientes.
8.1 Estandarización
Estandarice a mano la variable de edad, creando una nueva columna. Verifique que su media es cero y su desviación estándar uno.
Estandarice la base completa con
scale()y confirme que su cálculo manual coincide con la columna correspondiente.Estime el modelo estandarizado y presente una tabla comparativa con ambos modelos.
8.2 Interpretación
Interprete el coeficiente estandarizado de cada predictor, redactando una oración completa para cada uno.
Ordene los tres predictores según su peso relativo y justifique por qué ese orden no puede establecerse a partir de los coeficientes originales.
Verifique que el \(R^2\) y los valores p son idénticos en ambos modelos, y explique por qué ocurre esto.
8.3 Reporte
Redacte un párrafo de máximo 250 palabras que reporte el modelo, interprete el predictor de mayor peso relativo y señale explícitamente qué agrega la lectura estandarizada respecto de la no estandarizada.
Señale una situación de investigación en la que no convendría reportar coeficientes estandarizados, y justifique brevemente.