Programación Básica en R Studio

Análisis de datos faltantes

Introducción

Los datos faltantes son un tema que a menudo se pasa por alto en muchos textos introductorios. Esto puede deberse a la existencia de numerosos mitos sobre el análisis de estos datos, así como a que algunas técnicas modernas aún son relativamente nuevas. También es comprensible que no se aborde en textos básicos, ya que la mayoría de las metodologías son bastante complejas desde un enfoque matemático. Sin embargo, dado que los problemas relacionados con los datos faltantes son muy comunes en el análisis de datos del mundo real, es esencial tratar este tema.

Esta sección ofrece una introducción accesible al problema y a una de las técnicas más efectivas para abordarlo. Un dicho popular sugiere que la mejor forma de manejar los datos faltantes es no tenerlos. Aunque es cierto que el tema es complicado y existen muchas formas de abordarlo, algunas de ellas pueden ser inadecuadas. Es crucial no llevar este consejo al extremo; para evitar problemas con los datos faltantes, algunos han decidido no permitir que ciertos encuestados respondan a todas las preguntas de un formulario.

Existen métodos para tratar los datos faltantes, pero no hay soluciones para datos erróneos. La estrategia estándar consiste en reemplazar los datos ausentes por valores presentes, un proceso conocido como imputación. En la mayoría de los casos, el objetivo de la imputación no es reconstruir un conjunto de datos faltantes en su totalidad, sino permitir inferencias estadísticas válidas basadas en esos datos.

Por lo tanto, la efectividad de las técnicas de imputación no se mide por su capacidad de recrear con precisión los datos ausentes a partir de un conjunto simulado, sino por su habilidad para respaldar las mismas inferencias estadísticas que se obtendrían a partir de un conjunto de datos completos.

En este sentido, rellenar los datos faltantes es solo un paso hacia el verdadero objetivo: el análisis. El conjunto de datos imputados rara vez se considera el fin último de la imputación. En la práctica, hay diversas maneras de abordar los datos faltantes, algunas efectivas y otras no tanto, y lo que funciona en un contexto puede no ser adecuado en otro. Algunas estrategias implican eliminar datos faltantes, mientras que otras se centran en la imputación. A continuación, mencionaremos brevemente algunos de los métodos más comunes, pero el objetivo principal de esta sección es introducirte en lo que se considera el estándar de oro de las técnicas de imputación: la imputación múltiple.

Visualización de datos faltantes

Para demostrar la visualización de patrones de datos ausentes, primero tenemos que crear algunos datos ausentes. Para mostrar cómo utilizar la imputación múltiple en un escenario semirealista, vamos a crear una versión del conjunto de datos mtcars con algunos valores faltantes. Configuramos seed (para la aleatoriedad determinista), y creamos una variable para mantener nuestro nuevo conjunto de datos.

Los datos se extrajeron de la revista Motor Trend US de 1974 y comprenden el consumo de combustible y 10 aspectos del diseño y el rendimiento de 32 automóviles (modelos de 1973 a 1974)

Carguemos el conjunto de datos

set.seed(2)
miss_mtcars <- mtcars

En primer lugar, vamos a crear siete valores faltantes en drat (alrededor del 20%), cinco valores faltantes en la columna mpg (alrededor del 15%), cinco valores faltantes en la columna cyl, tres valores faltantes en wt (alrededor del 10%), y tres valores faltantes en vs

some_rows <- sample(1:nrow(miss_mtcars), 7)
miss_mtcars$drat[some_rows] <- NA

some_rows <- sample(1:nrow(miss_mtcars), 5)
miss_mtcars$mpg[some_rows] <- NA

some_rows <- sample(1:nrow(miss_mtcars), 5)
miss_mtcars$cyl[some_rows] <- NA

some_rows <- sample(1:nrow(miss_mtcars), 3)
miss_mtcars$wt[some_rows] <- NA

some_rows <- sample(1:nrow(miss_mtcars), 3)
miss_mtcars$vs[some_rows] <- NA

Ahora, vamos a crear cuatro valores faltantes en qsec, pero sólo para los coches automáticos

only_automatic <- which(miss_mtcars$am==0)
some_rows <- sample(only_automatic, 4)
miss_mtcars$qsec[some_rows] <- NA

Ahora, observemos las primeras filas del nuevo conjunto de datos:

kable(head(miss_mtcars, 10))
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 NA 160.0 110 3.90 2.620 16.46 NA 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 NA 108.0 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225.0 105 NA 3.460 20.22 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.570 NA NA 0 3 4
Merc 240D 24.4 4 146.7 62 NA NA 20.00 1 0 4 2
Merc 230 NA 4 140.8 95 3.92 3.150 22.90 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4

y relicemos el conteo por columna de los datos faltantes

missing_counts <- colSums(is.na(miss_mtcars))
kable(head(missing_counts))
x
mpg 5
cyl 5
disp 0
hp 0
drat 7
wt 3

Ahora vamos a visualizar los datos faltantes. La primera forma en que vamos a visualizar el patrón de los datos faltantes es utilizando la función md.pattern del paquete mice (que es también el paquete que vamos a utilizar para imputar nuestros datos faltantes). Si no tiene el paquete instálelo antes

md.pattern(miss_mtcars, plot = TRUE, rotate.names = TRUE)

##    disp hp am gear carb wt vs qsec mpg cyl drat   
## 12    1  1  1    1    1  1  1    1   1   1    1  0
## 6     1  1  1    1    1  1  1    1   1   1    0  1
## 2     1  1  1    1    1  1  1    1   1   0    1  1
## 3     1  1  1    1    1  1  1    1   0   1    1  1
## 1     1  1  1    1    1  1  1    1   0   0    1  2
## 1     1  1  1    1    1  1  1    0   1   1    1  1
## 1     1  1  1    1    1  1  1    0   0   1    1  2
## 1     1  1  1    1    1  1  0    1   1   0    1  2
## 1     1  1  1    1    1  1  0    0   1   1    1  2
## 1     1  1  1    1    1  1  0    0   1   0    1  3
## 2     1  1  1    1    1  0  1    1   1   1    1  1
## 1     1  1  1    1    1  0  1    1   1   1    0  2
##       0  0  0    0    0  3  3    4   5   5    7 27

Un patrón de datos faltantes por fila se refiere a las columnas en las que hay ausencia de datos en cada fila de un conjunto de datos. La función que utilizamos contabiliza y clasifica cuántas filas presentan el mismo patrón de datos faltantes. Como resultado, se genera una matriz binaria que utiliza valores 0 y 1. En esta matriz, un 1 indica que hay datos disponibles (no faltantes), mientras que un 0 señala la ausencia de datos (faltantes).

Las filas de esta matriz están organizadas en orden ascendente según la cantidad de datos que faltan, lo que significa que la primera fila refleja el patrón con el menor número de datos ausentes.

La columna izquierda muestra cuántas filas presentan un patrón específico de datos faltantes, mientras que la columna derecha indica cuántos valores faltan en ese patrón. La última fila de la matriz resume el total de datos faltantes para cada columna del conjunto de datos. Por ejemplo, observamos que hay 12 filas que no presentan ningún dato faltante.

Además, es importante destacar que solo hay seis filas que contienen más de un valor ausente, y de estas, únicamente una fila tiene más de dos valores faltantes, como se ilustra en la penúltima fila. En general, este conjunto de datos muestra un porcentaje bajo de datos faltantes, con menos del 3% de ausencias. Esto es significativo, ya que en algunos conjuntos de datos es común encontrar más del 30% de datos faltantes.

Para visualizar mejor estos patrones de datos faltantes, utilizaremos el paquete VIM. Si aún no lo tienes instalado, asegúrate de hacerlo antes de continuar.

aggr(miss_mtcars, numbers=TRUE)

A simple vista, esta representación nos muestra, sin esfuerzo, que la columna drat representa la mayor proporción de datos faltantes, por columnas, seguida de mpg, cyl, qsec, vs y wt. El gráfico de la derecha nos muestra información similar a la de la salida de md.pattern. Esta representación, sin embargo, hace que sea más fácil saber si hay algún patrón sistemático de omisión. Las celdas azules representan los datos no ausentes, y las rojas representan los datos que faltan. Los números de la derecha del gráfico representan la proporción de filas que muestran ese patrón de datos faltantes. El 37.5% de las filas no contienen ningún tipo de dato faltante.

Tipos de datos faltantes

El paquete VIM nos permitió visualizar los patrones de datos faltantes. Un término relacionado, al mecanismo de datos faltantes, describe el proceso que determina la probabilidad de que cada punto de datos sea faltante. Hay tres categorías principales de mecanismos de datos faltantes: Missing Completely At Random (MCAR), Missing At Random (MAR), y Missing Not At Random (MNAR). La discriminación basada en el mecanismo de datos faltantes es crucial, ya que nos informa sobre las opciones para manejar los datos faltantes.

Faltantes completamente al azar (MCAR)

  • El primer mecanismo, MCAR, se produce cuando la falta de datos no está relacionada con la datos. Esto ocurriría, por ejemplo, si se borraran filas de una base de datos al azar, o si una ráfaga de viento se llevara una muestra aleatoria de los formularios de encuesta de un investigador. El mecanismo que rige la ausencia de drat, mpg, cyl, wt, y vs es MCAR, porque seleccionamos al azar los elementos que faltan. Este mecanismo, aunque es el más fácil de trabajar, rara vez es sostenible en la práctica.

Faltantes no al azar (MNAR)

  • El mecanismo MNAR se produce cuando la ausencia de una variable está relacionada con la variable en sí misma. Por ejemplo, supongamos que la báscula que pesa cada coche tiene una capacidad de sólo 3.700 libras y, por ello, los ocho coches que pesaban más se registraron como NA. Este es un ejemplo clásico del mecanismo MNAR, es el propio peso de la observación la causa de que falte. Otro ejemplo sería si durante el transcurso del ensayo de un fármaco antidepresivo los participantes a los que no les ayudaba el fármaco se deprimían demasiado para continuar con el ensayo. Al final del ensayo, cuando se accede y se registra el nivel de depresión de todos los participantes, habrá valores que falten para los participantes cuyo motivo de ausencia está relacionado con su nivel de depresión.

Faltantes al azar (MAR)

  • El mecanismo, faltante al azar, tiene un nombre un tanto desafortunado, al contrario de lo que pueda parecer, significa que existe una relación sistemática entre la ausencia de una variable resultado y otras variables observadas, pero no la variable de resultado en sí misma.

Un ejemplo es que los hombres son menos propensos a completar una encuesta sobre depresión, pero esto no tiene nada que ver con su nivel de depresión, después de tener en cuenta la masculinidad.

Por ejemplo, puede que todas las observaciones de nuestro conjunto de datos con un valor ausente de fiebre no se hayan registrado porque se asumió que todos los pacientes con escalofríos y dolores tenían fiebre, por lo que nunca se les tomó la temperatura. Si es cierto, podríamos predecir fácilmente que cada observación que falta con escalofríos y dolores también tiene fiebre y utilizar esta información para imputar nuestros datos que faltan.

NOTA

Quizá haya observado que el lugar que ocupa un conjunto de datos concreto en la taxonomía del mecanismo de datos faltantes depende de las variables que incluye. Por ejemplo, sabemos que el mecanismo detrás de qsec es MAR, pero si el conjunto de datos no incluyera am, sería MNAR. Como somos nosotros los que creamos los datos, sabemos el procedimiento que dio lugar a los valores faltantes de qsec. Si no fuéramos nosotros los que creamos los datos, como ocurre en el mundo real, y el conjunto de datos no contuviera la columna am simplemente veríamos una cantidad de valores qsec que faltan arbitrariamente. Esto podría llevarnos a creer que los datos son MCAR. Sin embargo, no lo es; sólo porque la variable con la cual otra variable faltante está sistemáticamente relacionada no se observa, no quiere decir que esta no exista. Esto plantea una cuestión crítica: ¿Podemos estar seguros de que nuestros datos no son MCAR? La respuesta desafortunada es no. Dado que los datos que necesitamos para demostrar o refutar el MNAR faltan ipso facto, la suposición de MNAR nunca puede ser desconfirmada de forma concluyente.

Visualización de datos faltantes parte 2

Tomando el conjunto de datos airquality, un conjunto de datos de mediciones diarias de la calidad del aire en Nueva York de mayo a septiembre de 1973, que tiene valores NA dentro de sus variables (ver airquality). Las filas del conjunto de datos representan 154 días consecutivos. Cualquier eliminación de estas filas afectará a la continuidad del tiempo, lo que puede afectar a cualquier análisis de series temporales que se realice. Veamos con más detalle el conjunto de datos de la calidad del aire

Iniciamos visualizando los datos faltantes. Eliminaremos algunos puntos de datos del conjunto de datos para este ejemplo. En lo que respecta a las variables categóricas, la sustitución de las mismas no suele ser aconsejable. Algunas prácticas comunes incluyen la sustitución de las variables categóricas que faltan por la moda de las observadas, sin embargo, es cuestionable si es una buena opción. Aunque en este caso no faltan puntos de datos de las variables categóricas, las eliminamos de nuestro conjunto de datos (podemos volver a añadirlas más tarde si es necesario) y echamos un vistazo a los datos utilizando summary()

Nuevo conjunto de datos

data <- airquality
knitr::kable(head(data))
Ozone Solar.R Wind Temp Month Day
41 190 7.4 67 5 1
36 118 8.0 72 5 2
12 149 12.6 74 5 3
18 313 11.5 62 5 4
NA NA 14.3 56 5 5
28 NA 14.9 66 5 6

insertemos valores perdidos

data[4:10, 3] <- rep(NA, 7)
data[1:5, 4] <- NA
data <- data[-c(5,6)]
knitr::kable(head(data))
Ozone Solar.R Wind Temp
41 190 7.4 NA
36 118 8.0 NA
12 149 12.6 NA
18 313 NA NA
NA NA NA NA
28 NA NA 66

Nótese que, Ozone es la variable con más puntos de datos faltantes. A continuación vamos a profundizar en los patrones de datos que faltan. Suponiendo que los datos sean MCAR, demasiados datos faltantes también pueden ser un problema. Por lo general, un umbral máximo seguro es el 5% del total para conjuntos de datos grandes. Si los datos que faltan para una determinada característica o muestra son superiores al 5%, probablemente deba dejar de lado esa característica o muestra. Por lo tanto, comprobamos las características (columnas) y las muestras (filas) en las que falta más del 5% de los datos mediante una sencilla función

aggr_plot <- aggr(data, col=c('navyblue','red'), numbers=TRUE, sortVars=TRUE, labels=names(data),
                  cex.axis=.7, gap=3, ylab=c("Histogram of missing data","Pattern"))

## 
##  Variables sorted by number of missings: 
##  Variable      Count
##     Ozone 0.24183007
##   Solar.R 0.04575163
##      Wind 0.04575163
##      Temp 0.03267974

El gráfico nos ayuda a entender que a casi el 70% de las muestras no les falta ninguna información, al 24% le falta el valor de Ozono y las restantes muestran otros patrones de ausencia. A través de este enfoque, la situación parece un poco más clara. Otra aproximación visual (esperemos) útil es un gráfico de caja especial

marginplot(data[c(1,2)])

Aquí estamos limitados a trazar sólo 2 variables a la vez, pero sin embargo podemos obtener algunas ideas interesantes. El diagrama de caja rojo de la izquierda muestra la distribución de datos faltantes para Ozono, mientras que, el diagrama de caja azul muestra la distribución de los puntos de datos restantes. Lo mismo ocurre con los gráficos de caja de Solar.R en la parte inferior del gráfico. Si nuestra suposición de los datos MCAR es correcta, entonces esperamos que los gráficos de caja rojos y azules sean muy similares.

Borrado de la lista

El método más utilizado por los científicos de datos para tratar los datos que faltan es simplemente omitir los casos con datos que faltan, analizando únicamente el resto del conjunto de datos. Este método se conoce como eliminación por lista o análisis de casos completos. La función na.omit() en R elimina todos los casos con uno o más valores de datos faltantes en un conjunto de datos.

La mayor ventaja de este método es su comodidad. Sin embargo, si la naturaleza de los datos es MCAR, la eliminación de la lista dará lugar a errores estándar que son significativos para los datos reducidos, pero no para todo el conjunto de datos, que tenía los datos que faltaban. Este método de tratar los datos que faltan es posiblemente un desperdicio. Si se eliminan los casos de esta manera, hay que ser consciente de la disminución de la capacidad para detectar el verdadero efecto de las variables de interés.

Sin embargo, si los datos no son MCAR, el análisis de casos completos puede influir mucho en las estimaciones de la media, los coeficientes de regresión y las correlaciones. La supresión de la lista puede causar submuestras sin sentido.

A continuación, realizamos una simple función na.omit() para eliminar los casos que tienen NAs. Vemos que todas las filas que contenían algún NA en cualquier variable son eliminadas del dataframe

airquality_omit <- na.omit(data)
head(airquality_omit)
##    Ozone Solar.R Wind Temp
## 12    16     256  9.7   69
## 13    11     290  9.2   66
## 14    14     274 10.9   68
## 15    18      65 13.2   58
## 16    14     334 11.5   64
## 17    34     307 12.0   66

Dibujemos los histogramas antes y después de la imputación/eliminación usando ggplot

library(gridExtra)
## Warning: package 'gridExtra' was built under R version 4.3.3
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine
ggp1 <- ggplot(data.frame(value=data$Ozone), aes(x=value)) +
  geom_histogram(fill="#FBD000", color="#E52521", alpha=0.9) +
  ggtitle("Original data") +
  xlab('Ozone') + ylab('Frequency') +
  theme_dark() +
  theme(plot.title = element_text(size=15))

ggp2 <- ggplot(data.frame(value=airquality_omit$Ozone), aes(x=value)) +
  geom_histogram(fill="#43B047", color="#049CD8", alpha=0.9) +
  ggtitle("Listwise Deletion") +
  xlab('Ozone') + ylab('Frequency') +
  theme_minimal() +
  theme(plot.title = element_text(size=15))

grid.arrange(ggp1, ggp2, ncol = 2)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Removed 37 rows containing non-finite values (`stat_bin()`).
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Imputación por emparejamiento predictivo medio

Algunos científicos de datos o estadísticos pueden buscar una solución rápida sustituyendo los datos que faltan por la media. La media se utiliza a menudo para imputar datos no categóricos. Por ejemplo, en el conjunto de datos de calidad del aire, supongamos que queremos imputar la media de sus valores faltantes. En este caso, utilizamos el paquete R mice. Cambiando el argumento method = mean, se especifica la imputación de la media, el argumento m = 1 cambia las iteraciones a 1, lo que significa no (iteración).

El fundamento teórico de utilizar la media para imputar los datos faltantes es que la media es una buena estimación para seleccionar aleatoriamente una observación de una distribución normal. Ahora, intentaremos imputar la media para las variables Ozono y Solar.R del conjunto de datos airquality. En primer lugar, vamos a cargar los paquetes mice y mipackages (instale el paquete de CRAN primero usando install.packages(“mice”) y install.packages(“mi”)). Para mas información sobre el paquete utilizado (ver Mice). Podemos recuperar el conjunto de datos completo utilizando la función complete().

library(mice)
library(foreign)

Los datos faltantes para Ozono y Solar.R pueden ser imputados por la función mice().

imp <- mice(data, m=5, maxit=50, method ='pmm', seed=500, printFlag = FALSE)

El parámetro m se refiere al número de conjuntos de datos imputados que se generarán. Es decir, m representa la cantidad de veces que se imputarán los datos para crear múltiples conjuntos de datos imputados. Cada conjunto de datos imputados contendrá valores imputados diferentes para los datos faltantes, lo que permite capturar la incertidumbre en el proceso de imputación o falta de certeza sobre los valores reales de los datos faltantes.

El parámetro maxit en el paquete mice de R controla cuántas veces se repetirá el proceso de imputación en cada conjunto de datos para alcanzar una convergencia o un límite máximo de iteraciones. Durante cada iteración, el algoritmo intenta mejorar la imputación de los valores faltantes. Si el número de iteraciones alcanza el valor especificado en maxit y el proceso no converge, la imputación se detendrá y se generará un mensaje de advertencia. Ajustar adecuadamente el valor de maxit puede ayudar a controlar el tiempo de ejecución y garantizar una convergencia adecuada del algoritmo de imputación.

El valor por defecto es m=5. method='pmm' se refiere al método de imputación. En este caso, utilizamos el método de imputación por emparejamiento predictivo medio (PMM). Se pueden utilizar otros métodos de imputación, escriba methods(mice) para obtener una lista de los métodos de imputación disponibles.

methods(mice)
## Warning in .S3methods(generic.function, class, envir, dropPath = dropPath):
## function 'mice' appears not to be S3 generic; found functions that look like S3
## methods
##  [1] mice.impute.2l.bin              mice.impute.2l.lmer            
##  [3] mice.impute.2l.norm             mice.impute.2l.pan             
##  [5] mice.impute.2lonly.mean         mice.impute.2lonly.norm        
##  [7] mice.impute.2lonly.pmm          mice.impute.cart               
##  [9] mice.impute.jomoImpute          mice.impute.lasso.logreg       
## [11] mice.impute.lasso.norm          mice.impute.lasso.select.logreg
## [13] mice.impute.lasso.select.norm   mice.impute.lda                
## [15] mice.impute.logreg              mice.impute.logreg.boot        
## [17] mice.impute.mean                mice.impute.midastouch         
## [19] mice.impute.mnar.logreg         mice.impute.mnar.norm          
## [21] mice.impute.mpmm                mice.impute.norm               
## [23] mice.impute.norm.boot           mice.impute.norm.nob           
## [25] mice.impute.norm.predict        mice.impute.panImpute          
## [27] mice.impute.passive             mice.impute.pmm                
## [29] mice.impute.polr                mice.impute.polyreg            
## [31] mice.impute.quadratic           mice.impute.rf                 
## [33] mice.impute.ri                  mice.impute.sample             
## [35] mice.mids                       mice.theme                     
## see '?methods' for accessing help and source code
imp_df <- complete(imp)
head(imp_df)
##   Ozone Solar.R Wind Temp
## 1    41     190  7.4   87
## 2    36     118  8.0   80
## 3    12     149 12.6   74
## 4    18     313 10.9   66
## 5    13      81 16.6   57
## 6    28      78  7.4   66

Observación: El método PMM utiliza aleatoriedad en la imputación para introducir variabilidad en las estimaciones de los valores faltantes. Esto ayuda a evitar la sobreestimación o subestimación sistemática de los valores imputados y a capturar la incertidumbre asociada con la imputación de datos faltantes. La introducción de aleatoriedad también puede ayudar a mejorar la robustez y generalización del modelo de imputación, especialmente en situaciones donde los datos faltantes siguen patrones complejos o no aleatorios.

Tracemos histograma y diagrama de dispersión del aspecto del conjunto de datos de la calidad del aire tras la imputación mediante método PMM. Los siguientes son los histogramas asociados

ggp1 <- ggplot(data.frame(value=data$Ozone), aes(x=value)) +
  geom_histogram(fill="#FBD000", color="#E52521", alpha=0.9) +
  ggtitle("Original data") +
  xlab('Ozone') + ylab('Frequency') +
  theme_minimal() +
  theme(plot.title = element_text(size=15))

ggp2 <- ggplot(data.frame(value=imp_df$Ozone), aes(x=value)) +
  geom_histogram(fill="#43B047", color="#049CD8", alpha=0.9) +
  ggtitle("PMM imputation") +
  xlab('Ozone') + ylab('Frequency') +
  theme_minimal() +
  theme(plot.title = element_text(size=15))

grid.arrange(ggp1, ggp2, ncol = 2)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Removed 37 rows containing non-finite values (`stat_bin()`).
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.