Separación de datos binomiales
Introducción
En los modelos de regresión logística, se analizan las relaciones entre una variable dependiente de tipo binaria y una o más variables independientes que pueden ser de distintos tipos (numéricas continuas, numéricas discretas, categóricas, binarias, etc.). Sin embargo, en algunas situaciones, la relación entre las variables independientes y la variable respuesta puede derivar en problemas de separación de datos, afectando las estimaciones del modelo.
Tipos de separación de datos
Separación total
También conocida como separación perfecta o completa, ocurre cuando un predictor o combinación lineal de predictores clasifica perfectamente las observaciones en una de las dos categorías de la variable respuesta. En estos casos, las probabilidades predichas para una de las categorías son 0 o 1, y los coeficientes de regresión para las variables involucradas se vuelven indefinidos. Esto provoca un fallo en la convergencia del modelo, ya que no se pueden calcular los logaritmos para odds ratios (OR) iguales a 0 o a infinito (\(\infty\)), generando un mensaje de error.
Separación parcial
La separación parcial se presenta cuando un predictor o combinación lineal de predictores predice perfectamente la variable respuesta solo en algunos niveles de las variables independientes. Es decir, separa en gran medida las categorías de la variable respuesta. Esto puede llevar a la estimación de coeficientes y errores estándar extremadamente grandes, con intervalos de confianza muy amplios, lo que hace que las inferencias realizadas sean poco fiables.
Tomemos como ejemplo la base “covid_cancer.txt”, que contiene datos de un estudio de casos y controles realizado en la ciudad de Santa Fe, Argentina en pacientes con cáncer hospitalizados por formas severas de COVID-191.
Cargamos paquetes necesarios:
Cargamos datos:
# Carga datos
datos <- read_delim("datos/covid_cancer.txt")
# Explora datos
glimpse(datos)Rows: 79
Columns: 13
$ fallecido <dbl> 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0…
$ sexo <chr> "M", "M", "F", "F", "F", "M", "F", "M", "F", "M", "M…
$ edad <dbl> 60, 65, 70, 73, 60, 67, 81, 35, 79, 47, 58, 70, 57, …
$ comorbilidades <chr> "Sí", "Sí", "Sí", "Sí", "No", "Sí", "Sí", "No", "Sí"…
$ disnea <chr> NA, "Sí", "Sí", "No", "No", NA, "No", NA, "Sí", NA, …
$ neumonia_severa <chr> "Sí", "Sí", "Sí", "No", "No", "No", "No", "No", "No"…
$ inf_secundaria <chr> "Sí", NA, "Sí", "No", "No", "No", "Sí", "No", "No", …
$ complicaciones <chr> "Sí", NA, "Sí", "No", "No", "No", "Sí", "No", "Sí", …
$ infil_bilateral <chr> "Sí", "Sí", "Sí", "No", "No", "No", "No", "No", "No"…
$ asist_resp <chr> "Sí", "Sí", "Sí", "No", "No", "No", "No", "No", "No"…
$ cancer_tipo <chr> "Sólido", "Sólido", "Sólido", "Sólido", "Sólido", "S…
$ tr_quimioterapia <chr> "Sí", "Sí", "Sí", "Sí", "Sí", "Sí", "Sí", "Sí", "Sí"…
$ tr4_quimioterapia <chr> "Sí", "Sí", "Sí", "Sí", "Sí", "Sí", "No", "No", "No"…
Las variables de interés incluyen:
fallecido: fallecimiento por COVID-19 (No: 0, Sí: 1)sexo: sexo biológico del/a paciente (M: masculino, F: femenino)edad: edad en años al momento de la hospitalizacióncomorbilidades: presencia de comorbilidades (Sí, No)disnea: dificultad para respirar (Sí, No)neumonia_severa: paciente con neumonía severa (Sí, No)inf_secundaria: presencia de infección secundaria (Sí, No)complicaciones: complicaciones del COVID-19 (Sí, No)infil_bilateral: infiltración bilateral en radiografía (Sí, No)asist_resp: paciente que recibió asistencia respiratoria (Sí, No)cancer_tipo: tipo de cáncer (Sólido, Hematológico)tr_quimioterapia: paciente que recibió quimioterapia (Sí, No)tr4_quimioterapia: paciente que recibió quimioterapia en el último mes (Sí, No)
Nuestra variable dependiente es si el/la paciente falleció a causa del COVID-19. Comenzaremos por ajustar modelos de regresión logística univariados para evaluar qué variables explicativas están asociadas significativamente a la variable dependiente:
# Regresiones logísticas simples
datos |>
tbl_uvregression(y = fallecido,
method = glm,
method.args = list(family = binomial),
exponentiate = T) |>
bold_p()| Characteristic | N | OR | 95% CI | p-value |
|---|---|---|---|---|
| sexo | 78 | |||
| F | — | — | ||
| M | 1.95 | 0.61, 6.56 | 0.3 | |
| edad | 74 | 1.04 | 0.99, 1.10 | 0.12 |
| comorbilidades | 53 | |||
| No | — | — | ||
| Sí | 0.93 | 0.27, 3.30 | >0.9 | |
| disnea | 51 | |||
| No | — | — | ||
| Sí | 9.19 | 2.18, 49.2 | 0.004 | |
| neumonia_severa | 79 | |||
| No | — | — | ||
| Sí | 8.30 | 2.37, 31.2 | 0.001 | |
| inf_secundaria | 36 | |||
| No | — | — | ||
| Sí | 8.33 | 1.38, 59.1 | 0.023 | |
| complicaciones | 36 | |||
| No | — | — | ||
| Sí | 1,139,380,581 | 0.00, |
>0.9 | |
| infil_bilateral | 40 | |||
| No | — | — | ||
| Sí | 1.56 | 0.42, 5.98 | 0.5 | |
| asist_resp | 39 | |||
| No | — | — | ||
| Sí | 6.00 | 1.26, 44.4 | 0.039 | |
| cancer_tipo | 74 | |||
| Hematológico | — | — | ||
| Sólido | 3,844,194 | 0.00, |
>0.9 | |
| tr_quimioterapia | 49 | |||
| No | — | — | ||
| Sí | 5.20 | 0.85, 101 | 0.14 | |
| tr4_quimioterapia | 39 | |||
| No | — | — | ||
| Sí | 1.40 | 0.33, 6.48 | 0.7 | |
| Abbreviations: CI = Confidence Interval, OR = Odds Ratio | ||||
Al ejecutar este código aparecerá el siguiente mensaje de advertencia: Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred. Esto indica que todas o alguna de las variables explicativas provoca separación parcial de los datos.
Al observar la tabla generada, notamos que las variables disnea, neumonia_severa, inf_secundaria, asist_resp y tr_quimioterapia presentan intervalos de confianza excesivamente amplios, lo que sugiere la presencia de separación parcial. Si generamos tablas de 2x2 de cada variable explicativa en función de la variable fallecido, podemos visualizar este fenómeno:
# disnea
datos |>
tabyl(disnea, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() disnea 0 1
No 91.2% 8.8%
Sí 52.9% 47.1%
# neumonía severa
datos |>
tabyl(neumonia_severa, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() neumonia_severa 0 1
No 90.3% 9.7%
Sí 52.9% 47.1%
# infección secundaria
datos |>
tabyl(inf_secundaria, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() inf_secundaria 0 1
No 86.2% 13.8%
Sí 42.9% 57.1%
# asistencia respiratoria
datos |>
tabyl(asist_resp, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() asist_resp 0 1
No 88.9% 11.1%
Sí 57.1% 42.9%
# quimioterapia
datos |>
tabyl(tr_quimioterapia, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() tr_quimioterapia 0 1
No 92.9% 7.1%
Sí 71.4% 28.6%
Estos análisis muestran que variables como disnea, neumonia_severa, inf_secundaria, asist_resp y tr_quimioterapia explican casi completamente la mortalidad por COVID-19 severo.
Si evaluamos las variables complicaciones y cancer_tipo:
# complicaciones
datos |>
tabyl(complicaciones, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() complicaciones 0 1
No 100.0% 0.0%
Sí 42.9% 57.1%
# tipo de cáncer
datos |>
tabyl(cancer_tipo, fallecido,
show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() cancer_tipo 0 1
Hematológico 100.0% 0.0%
Sólido 80.3% 19.7%
Observamos que todos los pacientes sin complicaciones o con cáncer hematológico sobrevivieron, lo cual también indica separación parcial de los datos.
Ahora, ajustemos un modelo para evaluar el efecto de la interacción entre el tipo de cáncer y haber recibido quimioterapia en los 30 días previos, controlando por la presencia de neumonía severa y asistencia respiratoria:
Call:
glm(formula = fallecido ~ cancer_tipo * tr4_quimioterapia + neumonia_severa +
asist_resp, family = binomial, data = datos)
Coefficients: (1 not defined because of singularities)
Estimate Std. Error z value Pr(>|z|)
(Intercept) -19.4976 2399.5450 -0.008 0.9935
cancer_tipoSólido 16.7936 2399.5448 0.007 0.9944
tr4_quimioterapiaSí 0.8431 1.0135 0.832 0.4055
neumonia_severaSí 2.6769 1.3791 1.941 0.0522 .
asist_respSí 0.2547 1.3683 0.186 0.8523
cancer_tipoSólido:tr4_quimioterapiaSí NA NA NA NA
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 38.024 on 31 degrees of freedom
Residual deviance: 26.599 on 27 degrees of freedom
(47 observations deleted due to missingness)
AIC: 36.599
Number of Fisher Scoring iterations: 15
El resumen del modelo muestra el mensaje: Coefficients: (1 not defined because of singularities), indicando que los errores estándar para el intercepto y cancer_tipo son muy grandes. Además, no se muestran los coeficientes ni el p-valor para la interacción. Esto se puede visualizar tabulando los coeficientes:
tbl_regression(fit1, exponentiate = T)| Characteristic | OR | 95% CI | p-value |
|---|---|---|---|
| cancer_tipo | |||
| Hematológico | — | — | |
| Sólido | 19,650,260 | 0.00, |
>0.9 |
| tr4_quimioterapia | |||
| No | — | — | |
| Sí | 2.32 | 0.33, 21.1 | 0.4 |
| neumonia_severa | |||
| No | — | — | |
| Sí | 14.5 | 1.30, 416 | 0.052 |
| asist_resp | |||
| No | — | — | |
| Sí | 1.29 | 0.05, 17.7 | 0.9 |
| cancer_tipo * tr4_quimioterapia | |||
| Sólido * Sí | |||
| Abbreviations: CI = Confidence Interval, OR = Odds Ratio | |||
Recibirás advertencias como: Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred y Warning in regularize.values(x, y, ties, missing(ties), na.rm = na.rm) : collapsing to unique 'x' values, lo que sugiere separación parcial de los datos.
Para evaluar separación de estos datos, podemos crear una nueva variable que represente el cruce entre tipo de cáncer y quimioterapia usando la función fct_cross() de tidyverse:
datos |>
mutate(tipo_ca_quim = fct_cross(cancer_tipo, tr4_quimioterapia)) |>
tabyl(tipo_ca_quim, fallecido, show_na = F) |>
adorn_percentages() |>
adorn_pct_formatting() tipo_ca_quim 0 1
Hematológico:No 100.0% 0.0%
Sólido:No 76.5% 23.5%
Sólido:Sí 71.4% 28.6%
Este análisis muestra que ninguno de los pacientes con cáncer hematológico recibió quimioterapia en los últimos 30 días, lo que también contribuye a la separación parcial.
Finalmente, para evaluar la separación de datos de manera más formal, utilizamos el paquete detectseparation (Kosmidis, Schumacher, y Schwendinger 2022). Ajustamos nuevamente el modelo con el argumento method = "detect_separation":
library(detectseparation)
# Tipo de cáncer
glm(fallecido ~ cancer_tipo, data = datos, family = binomial,
method = "detect_separation")Implementation: ROI | Solver: lpsolve
Separation: TRUE
Existence of maximum likelihood estimates
(Intercept) cancer_tipoSólido
-Inf Inf
0: finite value, Inf: infinity, -Inf: -infinity
# Neumonía severa
glm(fallecido ~ neumonia_severa, data = datos, family = binomial,
method = "detect_separation")Implementation: ROI | Solver: lpsolve
Separation: FALSE
Existence of maximum likelihood estimates
(Intercept) neumonia_severaSí
0 0
0: finite value, Inf: infinity, -Inf: -infinity
# Modelo con interacción
glm(fallecido ~ cancer_tipo * tr4_quimioterapia +
neumonia_severa + asist_resp,
data = datos,
family = binomial,
method = "detect_separation")Implementation: ROI | Solver: lpsolve
Separation: TRUE
Existence of maximum likelihood estimates
(Intercept) cancer_tipoSólido
-Inf Inf
tr4_quimioterapiaSí neumonia_severaSí
0 0
asist_respSí cancer_tipoSólido:tr4_quimioterapiaSí
0 NA
0: finite value, Inf: infinity, -Inf: -infinity
En el caso de separación completa, un nivel de la variable explicativa debería modelar perfectamente los “Sí” y el otro los “No”, y al intentar correr el modelo nos encontraríamos con un error de convergencia que impediría continuar con el análisis.
Soluciones a la separación de datos
Remuestreo, recolección o submuestreo
Siempre que fuera posible, recolectar más datos puede mitigar la separación total al introducir más variabilidad en los datos. En su defecto, se puede considerar el submuestreo o el sobremuestreo para balancear las clases. Sin embargo, estas técnicas deben aplicarse con cuidado para evitar sesgos.
Eliminación o recategorización de variables
En casos de separación parcial, una opción es simplificar el modelo eliminando las variables causantes de la separación. Si esas variables son esenciales, otra alternativa es agrupar niveles de las variables categóricas para reducir la separación. Esto puede hacer que el modelo sea más robusto al reducir la complejidad y mejorar la estabilidad de las estimaciones.
Uso de modelos alternativos a la regresión logística
Los modelos de pseudo-likelihood, como la regresión logística condicional y la máxima verosimilitud penalizada, permiten ajustar el modelo incluso cuando la separación de datos impide la convergencia con métodos tradicionales. Los modelos penalizados, que incluyen las regresiones Ridge, Lasso y Elastic Net, añaden un término de penalización que controla el crecimiento excesivo de los coeficientes en casos de separación parcial, facilitando la convergencia y mejorando la estabilidad del modelo. Por otro lado, los modelos bayesianos introducen distribuciones previas sobre los coeficientes, permitiendo una mayor flexibilidad para manejar la separación de datos y ofreciendo estimaciones más estables y confiables. Sin embargo, el ajuste e interpretación de estos modelos es complejo y escapa al alcance de este curso, por lo que no serán abordados en detalle.
Referencias
Notas
Gastiazoro MP, Cardozo MA, Ricardo T, Ramos JG, Ballina A, Maillo M, et al. Clinical features in cancer patients with COVID-19 in Santa Fe and Buenos Aires, Argentina. J Clin Images Med Case Rep [Internet]. el 14 de febrero de 2023 [citado el 9 de agosto de 2024];4(2). Disponible en: https://jcimcr.org/articles/JCIMCR-v4-2285.html↩︎