Detección de datos perdidos
En este apartado vamos a ver como cuantificar y visualizar los datos ausentes para su detección, pero antes veamos como se representan los datos ausentes en R
Datos ausentes en R
NA
Es como se representa en R los datos ausentes o missing. Recuerda que en el Tema 1 vimos que los datos missing pueden estar representados de muchas formas ("99", "Missing", "Unknown", " ") y hay que detectarlos y convertirlos en NA
Versiones especiales de NA:
NULL
Es un valor especial que se utiliza para representar la ausencia de un valor o la falta de datos. Se trata de un objeto nulo, y se puede utilizar en diversas situaciones para indicar que una variable o expresión no tiene un valor asignado.
NaN
Son valores imposibles, por ejemplo cuando fuerzas a R a dividir 0 por 0.
Inf
Representa un valor infinito, como por ejemplo si fuerzas a R a dividir un número por 0
Ejemplo ilustrativo de como esto puede afectar a tus análisis:
z <- c(1,7, 22, NA, Inf, NaN, 5, 8, 10, 15, NA, 12)
max(z) # returns NA
max(z, na.rm=T) # returns Inf
max(z[is.finite(z)]) # returns 22
“NAs introduced by coercion” es un warning común en R. Por ejemplo, si intentas convertir en numérico un vector que tiene caracteres:
as.numeric(c("10", "20", "thirty", "40"))
## Warning message:
NAs introduced by coercion
## [1] 10 20 NA 40
Funciones útiles en R
is.na()
y !is.na()
is.na()
y !is.na()
Estas funciones devuelven un valor lógico (TRUE
or FALSE
)
my_vector <- c(1, 4, 56, NA, 5, NA, 22)
is.na(my_vector)
## [1] FALSE FALSE FALSE TRUE FALSE TRUE FALSE
!is.na(my_vector)
## [1] TRUE TRUE TRUE FALSE TRUE FALSE TRUE
sum(is.na(my_vector))
## [1] 2
na.omit()
na.omit()
Esta función quitará los datos missing, si lo aplicas a un vector quitará los datos NA
y si es sobre un data frame quitará las filas donde haya un valor missing.
na.omit (my_vector)
## [1] 1 4 56 5 22
## attr(,"na.action")
## [1] 4 6
## attr(,"class")
## [1] "omit"
na.rm = TRUE
na.rm = TRUE
Cuando se utilizan funciones matemáticas como max()
, min()
, sum()
or mean()
si hay valores NA los eliminara para hacer el cálculo
my_vector <- c (1, 4, 56, NA, 5, NA, 22)
mean(my_vector)
## [1] NA
mean(my_vector, na.rm = TRUE)
## [1] 17.6
Cuantificar y visualizar datos missing en una base de datos
Para ello vamos a usar una base de datos de simulación de una epidemia de Ebola
y un paquete de R que se llama naniar
#install.packages("naniar")
library(naniar)
load("linelist.Rdata") ## cargamos los datos
str(data) ## vemos las variables
'data.frame': 5888 obs. of 20 variables:
$ case_id : chr "5fe599" "8689b7" "11f8ea" "b8812a" ...
$ date_infection : Date, format: "2014-05-08" NA NA "2014-05-04" ...
$ date_onset : Date, format: "2014-05-13" "2014-05-13" "2014-05-16" "2014-05-18" ...
$ date_hospitalisation: Date, format: "2014-05-15" "2014-05-14" "2014-05-18" "2014-05-20" ...
$ date_outcome : Date, format: NA "2014-05-18" "2014-05-30" NA ...
$ outcome : Factor w/ 2 levels "Death","Recover": NA 2 2 NA 2 2 2 1 2 1 ...
$ gender : Factor w/ 2 levels "f","m": 2 1 2 1 2 1 1 1 2 1 ...
$ age : num 2 3 56 18 3 16 16 0 61 27 ...
$ hospital : Factor w/ 5 levels "Central Hospital",..: 3 NA 5 4 2 4 NA NA NA NA ...
$ lon : num -13.2 -13.2 -13.2 -13.2 -13.2 ...
$ lat : num 8.47 8.45 8.46 8.48 8.46 ...
$ infector : chr "f547d6" NA NA "f90f5f" ...
$ ct_blood : int 22 22 21 23 23 21 21 22 22 22 ...
$ fever : Factor w/ 2 levels "no","yes": 1 NA NA 1 1 1 NA 1 1 1 ...
$ chills : Factor w/ 2 levels "no","yes": 1 NA NA 1 1 1 NA 1 1 1 ...
$ cough : Factor w/ 2 levels "no","yes": 2 NA NA 1 2 2 NA 2 2 2 ...
$ aches : Factor w/ 2 levels "no","yes": 1 NA NA 1 1 1 NA 1 1 1 ...
$ vomit : Factor w/ 2 levels "no","yes": 2 NA NA 1 2 2 NA 2 2 1 ...
$ temp : num 36.8 36.9 36.9 36.8 36.9 37.6 37.3 37 36.4 35.9 ...
$ days_onset_hosp : int 2 1 2 2 1 1 2 1 1 2 ...
summary(data) ## resumimos las variables
case_id date_infection date_onset date_hospitalisation date_outcome outcome gender
Length:5888 Min. :2014-03-19 Min. :2014-04-07 Min. :2014-04-17 Min. :2014-04-19 Death :2582 f :2807
Class :character 1st Qu.:2014-09-06 1st Qu.:2014-09-16 1st Qu.:2014-09-19 1st Qu.:2014-09-26 Recover:1983 m :2803
Mode :character Median :2014-10-11 Median :2014-10-23 Median :2014-10-23 Median :2014-11-01 NA's :1323 NA's: 278
Mean :2014-10-22 Mean :2014-11-03 Mean :2014-11-03 Mean :2014-11-12
3rd Qu.:2014-12-05 3rd Qu.:2014-12-19 3rd Qu.:2014-12-17 3rd Qu.:2014-12-28
Max. :2015-04-27 Max. :2015-04-30 Max. :2015-04-30 Max. :2015-06-04
NA's :2087 NA's :256 NA's :936
age hospital lon lat infector ct_blood fever
Min. : 0.00 Central Hospital : 454 Min. :-13.27 Min. :8.446 Length:5888 Min. :16.00 no :1090
1st Qu.: 6.00 Military Hospital : 896 1st Qu.:-13.25 1st Qu.:8.461 Class :character 1st Qu.:20.00 yes :4549
Median :13.00 Other : 885 Median :-13.23 Median :8.469 Mode :character Median :22.00 NA's: 249
Mean :16.01 Port Hospital :1762 Mean :-13.23 Mean :8.470 Mean :21.21
3rd Qu.:23.00 St. Mark's Maternity Hospital (SMMH): 422 3rd Qu.:-13.22 3rd Qu.:8.480 3rd Qu.:22.00
Max. :84.00 NA's :1469 Max. :-13.21 Max. :8.492 Max. :26.00
NA's :85
chills cough aches vomit temp days_onset_hosp
no :4540 no : 773 no :5095 no :2836 Min. :35.20 Min. : 0.000
yes :1099 yes :4866 yes : 544 yes :2803 1st Qu.:38.20 1st Qu.: 1.000
NA's: 249 NA's: 249 NA's: 249 NA's: 249 Median :38.80 Median : 1.000
Mean :38.56 Mean : 2.059
3rd Qu.:39.20 3rd Qu.: 3.000
Max. :40.80 Max. :22.000
NA's :149 NA's :256
Vemos que el dataframe contiene 5888 observaciones y 20 variables, con númerosos NA's
en muchas de sus variables. En este caso, asumimos que no hay ni errores ni datos atípicos.
Cuantificación
El primer interés es saber cuántos datos missing tiene la base de datos, tanto por variable como global.
El número de datos missing por variable lo podemos ver con summary()
Para saber el número y porcentaje de valores que son missing a nivel global usamos las funciones pct_miss()
, n_miss()
y pct_complete_case()
# Porcentaje de datos missing en todo el dataset
pct_miss(data)
8.637908
# Número de datos missing en todo el dataset
n_miss(data)
10172
# Porcentaje de filas completas si borramos todas las filas que contienen al menos un datos missing
pct_complete_case(data)
26.81726
# Número de filas completas si borramos todas las filas que contienen al menos un datos missing
n_case_complete(data)
1579
En este caso, vemos que el porcentaje de datos missing global no es demasiado (un 8.64%), pero si no los tratamos y solo los borramos, nos quedaríamos con sólo el 27% de la base de datos, que corresponden a 1579 observaciones de las 5888 de las que partíamos, por eso, tratar de forma adecuada los datos perdidos es muy importante para los análisis posteriores. Hay dos cosas que se pueden hacer:
Borrarlos
Imputarlos
La decisión muchas veces vendrá dada por el número de datos perdidos, los análisis posteriores que se quieran realizar, si queremos hacer análisis univariante o multivariante, etc.
La decisión de borrarlos puede tener mucho impacto en nuestros análisis posteriores. Imagina que queremos ajustar una recta de regresión para predecir los días desde la aparición de síntomas hasta la hospitalización (days_onset_hosp
) con la temperatura (fiebre) (temp
) en el ejemplo de la epidemia de Ebola (linelist
)
1) Borrar todas aquellas filas que tienen al menos un missing (n=1579):
data_complete<-data[complete.cases(data),]
dim(data_complete)
[1] 1579 20
summary(glm(days_onset_hosp ~ temp, data = data_complete, family = poisson))
Usamos regresión de Poisson por como se distribuye la variable days_onset_hosp
Call:
glm(formula = days_onset_hosp ~ temp, family = poisson, data = data_complete)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.21307 0.72794 0.293 0.77
temp 0.01302 0.01884 0.691 0.49
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 3258.9 on 1578 degrees of freedom
Residual deviance: 3258.4 on 1577 degrees of freedom
AIC: 6582.8
Number of Fisher Scoring iterations: 5
En este caso, vemos como la variable temp
no está asociada con la variable days_onset_hosp
(p-valor = 0.49) y por tanto no podríamos decir que un aumento en la temperatura corporal (fiebre) provoca más días de hospitalización.
2) No borramos nada (n=5888)
summary(glm(days_onset_hosp ~ temp, data = data, family = poisson))
Call:
glm(formula = days_onset_hosp ~ temp, family = poisson, data = data)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.71045 0.38854 -4.402 1.07e-05 ***
temp 0.06243 0.01006 6.208 5.36e-10 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 11496 on 5482 degrees of freedom
Residual deviance: 11457 on 5481 degrees of freedom
(405 observations deleted due to missingness)
AIC: 22812
Number of Fisher Scoring iterations: 5
En este caso, si podemos decir que la variable temp
está asociada con la variable days_onset_hosp
(p-valor = 5.36e-10) y por tanto podemos decir que un aumento en la temperatura corporal (fiebre) provoca más días de hospitalización.
Por tanto es muy importante detectar y tratar de forma adecuada los datos faltantes sino quieres resultados sesgados o incluso falsos.
Para hacer un buen tratamiento es importante pensar en por qué tus datos podrían faltar, además de cuantificarlos. Hacer esto puede ayudarte a decidir qué tan importante podría ser imputar datos faltantes y también qué método de imputación de datos faltantes podría ser la mejor en tu situación.
Tipos de datos missing
En estadística y ciencia de datos hablamos de tres tipos de datos mmissing:
Missing Completely at Random (MCAR): Son datos perdidos completamente aleatorios, es decir, la ausencia de valores en un conjunto de datos es independiente tanto de otras variables observadas como de las no observadas.
Ejemplo: en una encuesta online, algunos participantes olvidan responder una pregunta sobre el color favorito porque el sistema falla en un 5% de los casos al guardar la respuesta. El error ocurre de manera aleatoria y no depende de ninguna variable como la edad, género, ni del propio color elegido.
Missing at Random (MAR): Son datos perdidos aleatorios, es decir, la probabilidad de que un valor falte puede depender de otras variables observadas, pero no de las no observadas. El nombre puede confundir porque no son realmente aleatorios.
Ejemplo: en un estudio psicológico, las personas más mayores son menos propensas a responder a preguntas sobre uso de redes sociales. Aquí, la ausencia está relacionada con la variable observada edad y como la conocemos, podemos modelar y compensar esta ausencia.
Missing not at Random (MNAR): Datos perdidos de forma no aleatoria, es decir, la probabilidad de que falte un valor depende de una variable no observada. Esto puede introducir sesgos en los datos y es más complicado de manejar. En otras palabras, la falta de un valor está relacionada con la información que falta y no puede ser modelada únicamente en función de las variables observadas. Ejemplo: en una encuesta sobre ingresos anuales, los participantes con sueldos muy altos tienden a no contestar esa pregunta. La probabilidad de ausencia está directamente relacionada con el valor perdido (el ingreso), por lo que es mucho más difícil de corregir.
Tratar con datos MNAR (Missing not at Random) puede ser más complicado que tratar con datos MCAR (Missing Completely at Random) o MAR (Missing at Random) porque la falta de información está relacionada con la información que falta.
Visualización
Antes de imputar o eliminar, es esencial ver dónde y cómo faltan los datos. Con gg_miss_var()
cuantificamos el porcentaje de NA
por variable (y por subgrupos con facet =
). Con vis_miss()
inspeccionamos patrones fila–columna, y con geom_miss_point()
detectamos relaciones entre dos cuantitativas cuando una falta. Las columnas sombra
creadas con bind_shadow()
permiten tratar la ausencia (NA/no_NA)
como una variable más y estudiar su relación con covariables o con el tiempo.
1) Panorama global por variable
gg_miss_var(data, show_pct = TRUE)

Aquí vemos que infector
y date_infection
tienen alrededor de 35% de datos ausentes. hospital
, outcome
y date_outcome
tienen entre 20–25% de datos ausentes y el resto menos del 5%. Esto es importante porque si el número de datos ausente supera el 5% en las variables, estas pueden ser muy poroblemáyicas y, por tanto, habrá que ver como de relevantes son para el análisis.
La ausencia no es uniforme: unas pocas variables concentran la mayoría de los NA. → Esto ya descarta MCAR global (completamente aleatorio).
También podemos visulizar el dataframe como un heatmap donde se muestra en forma de matriz de datos si cada valor está ausente o no con vis_miss()
. Se puede usar select()
para elegir ciertas columnas del dataframe y proporcionar solo esas columnas a la función.
El orden en el que aparecen las observaciones es el mismo que en la base de datos, pero con la opción cluster = TRUE
, se pueden identificar grupos de variables con patrones similares.
vis_miss(data,cluster=TRUE) +
theme(axis.text.x = element_text(angle = 90))

En este gráfico vemos una matriz de observaciones (filas) por variables (columnas):
Gris = dato presente.
Negro = valor perdido.
En este caso, vemos que algunos bloques verticales (columnas) concentran los NA.
Cuando observamos variables como puntos negros dispersos, normalmente pensamos que son MCAR, y cuando hay patrones muy claros, habrá que investigar si hay posibles MAR o MNAR.
En este caso, vemos que:
Los datos faltantes de todos los síntomas (
fever, chills, cough, chaes, vomit
) faltan en los mismos individuos, esto habrá que ver si la ausencia puede estar ligada a algo observable (MAR).Se podría pensar que los NA en
outcome
podrían depender de la variabledate_outcome
o de si el paciente sigue hospitalizado (MAR).La falta de
date_infection
yinfector
podría depender de la calidad del registro por hospital o del periodo de la epidemia, por tanto ausencias ligadas a otras variables observadas (MAR).Si los casos más graves o sin desenlace son precisamente los que no tienen
outcome
, entonces la ausencia depende del propio valor no observado (MNAR).La variable
age
ygender
se podrían deber simplemente a un fallo del registro aleatorio (MCAR).
Para ver si los datos faltantes de las diferentes variables son de un tipo u otro ahora que ya tenemos una idea global de lo que pasa, podríamos hacer anaálisis por subgrupos.
2) Visualizar por subgrupos
Si queremos visualizar los datos missing por gender
o outcome
haríamos uso del arguemento facet =
gg_miss_var(data, show_pct = TRUE,facet = gender)

Una cosa interesante que vemos al clasificar los datos por la variable gender
, es que para age
también falta en ≈30% de esos casos, por tanto hay dependencia entre la falta de género y de edad.
Exploración y visualización de datos missing entre dos variables
Una vez que tenemos sospechas de posibles dependencias entre los datos missing, podemos explorar y visualizar las relaciones entre ellas:
Dos variables cuantitativas
¿Cómo visualizamos algo que está perdido? ggplot()
por ejemplo elimina las observaciones con valores missing.
Si creo un gráfico de dispersión entre dos variables cuantitativas, por ejemplo age
y temp
con ggplot()
library(ggplot2)
ggplot(data = data, aes (x = age, y = temp)) + geom_point()

Vemos con los valores missing de ambas variables no están representados en el gráfico, pero si queremos ver dependencias entre variables, podemos usar la función geom_miss_point()
. Al crear un gráfico de dispersión de dos columnas, los registros con uno de los valores faltantes y el otro valor presente se muestran configurando los valores faltantes en un 10% menos que el valor más bajo en la columna, y se les asigna un color distintivo.
ggplot(data = data, aes (x = age, y = temp)) + geom_miss_point()

En este gráfico de dispersión, los puntos rojos representan registros donde el valor de una columna está presente, pero el valor de la otra columna falta. Esto permite visualizar la distribución de los valores faltantes en relación con los valores no faltantes. En este caso, no vemos que haya ninguna dependencia entre los valores missing de las variables con la variable observada.
Estratificado por una variable categórica
Para evaluar la ausencia de datos estratificados por otra variable, podemos usar la funciónbind_shadow()
para ver la proporción de missing de una variable respecto a la distribución de otra variable. Esta función crea una columna binaria NA/no_NA
para cada columna existente y une todas estas nuevas columnas al conjunto de datos original con el apéndice "_NA"
.
shadowed_data <- data %>%
bind_shadow()
names(shadowed_data)
[1] "case_id" "date_infection" "date_onset"
[4] "date_hospitalisation" "date_outcome" "outcome"
[7] "gender" "age" "hospital"
[10] "lon" "lat" "infector"
[13] "ct_blood" "fever" "chills"
[16] "cough" "aches" "vomit"
[19] "temp" "days_onset_hosp" "case_id_NA"
[22] "date_infection_NA" "date_onset_NA" "date_hospitalisation_NA"
[25] "date_outcome_NA" "outcome_NA" "gender_NA"
[28] "age_NA" "hospital_NA" "lon_NA"
[31] "lat_NA" "infector_NA" "ct_blood_NA"
[34] "fever_NA" "chills_NA" "cough_NA"
[37] "aches_NA" "vomit_NA" "temp_NA"
[40] "days_onset_hosp_NA"
Estas variables shadow
se pueden utilizar para representar la proporción de valores que faltan en relación con otra columna y luego se pueden utilizar para ver dependencias de los valores missing con variables tanto cuantitativas como cualitativas:
Variables Cuantitativas
Si queremos ver la proporción de pacientes a los que les falta la variable age
según el valor del registro del día de hospitalización date_hospitalisation
podemos estratificarla usando la variable shadow en la opción: color =
.
ggplot (data = shadowed_data,
mapping = aes(x = date_hospitalisation,
colour = age_NA)) +
geom_density()

En este caso observamos que los datos missing de la variable edad se corresponde a fechas mas recientes, por lo tanto podríamos sospechar de una relación, aunque el hecho de que no todos los valores faltantes correspondan a esa dependecia, nos hace pensar que sea un MNAR.
Variables Categóricas
También se pueden usar variables categóricas para esta representación, por ejemplo para ver si los missing de los síntomas fever_NA
están relacionados con la variable outcome
.
ggplot(shadowed_data, aes(x = outcome, fill = fever_NA)) +
geom_bar(position = "fill")

En este caso, no observamos ninguna relación. En cambio si representamos los NA de cualquiera de los síntomas (chills_NA
)
ggplot (data = shadowed_data,
mapping = aes(x = temp,
colour = chills_NA)) + geom_density()

Podemos observar que los NA de la variable chills
, corresponden a aquellos individuos que tienen una temperatura corporal por debajo de 38. Lo mismo pasa con el resto de síntomas, ya que todos los NA correspondian a los mismos individuos, por tanto podemos decir que hay una relación entre los NA de los síntomas con temperatura baja, correspondiendo a quizás individuos más sanos.
Last updated