Los procesos experimentales son complejos, lo que hace que en ocasiones haya pérdida de datos. Esta pérdida es natural, lo que implica que debemos saber qué hacer en caso de estar presente. En R, los datos faltantes aparecen como NA
, y pueden ocurrir en variables de cualquier naturaleza, como numéricas:
x <- c(1, 4, 6, 2, 8, NA, 10)
x
## [1] 1 4 6 2 8 NA 10
o categóricas:
a <- c("a", "c", "r", NA, "s", NA, "q")
a
## [1] "a" "c" "r" NA "s" NA "q"
Los valores faltantes afectan las operaciones que realizamos con las variables en que están presentes, como la suma de sus elementos:
sum(x)
## [1] NA
el promedio ó desviación estándar:
mean(x)
## [1] NA
sd(x)
## [1] NA
Es natural entonces que existan estrategias para identificar los datos faltantes y manejarlos de modo que nos permitan hacer los cálculos que necesitamos. La primera estrategia es su omisión en los cálculos, lo cual está implementado en la mayoría de las funciones nativas de R como un argumento extra na.rm = TRUE
. Por ejemplo para calcular la suma de elementos:
sum(x, na.rm = T)
## [1] 31
El valor por defecto de na.rm = FALSE
, porque no debería haber NA
s, y de este modo se pueden detectar sin previa exploración. Es buena práctica, sin embargo, revisar las bases de datos para encontrarlos y estar conscientes de que es necesario hacer algo al respecto.
Como es usual, es posible hacer esta exploración manualmente, sin embargo, esto no funciona cuando tenemos bases de datos grandes. En este caso R nos puede ayudar a encontrarlos. La primera función que debemos conocer es is.na
, la cual genera un objeto lógico, indicando si un valor es NA
(TRUE
) o no (FALSE
). En el caso de los objetos x
y a
:
is.na(x)
## [1] FALSE FALSE FALSE FALSE FALSE TRUE FALSE
is.na(a)
## [1] FALSE FALSE FALSE TRUE FALSE TRUE FALSE
Si tenemos un vector grande, inspeccionar a mano cuáles posiciones tienen valores faltantes puede consumir mucho tiempo, pero esto lo podemos hacer con dos funciones, table
y which
. Por un lado table
nos indicará cuántos NA
hay en la base o variable que estemos inspeccionando:
table(is.na(x))
##
## FALSE TRUE
## 6 1
mientras que which
indicará la posición:
which(is.na(x))
## [1] 6
Una vez que tenemos identificada la presencia, cantidad y posición de los valores faltantes debemos tomar una serie de decisiones sobre qué haremos con esto valores faltantes. Como vimos al inicio, en algunas funciones podemos eliminar esas posiciones para hacer el cálculo con el argumento na.rm
(remove NA). Existe una función llamada na.omit
que podemos aplicar a priori a los objetos que tienen valores faltantes:
na.omit(x)
## [1] 1 4 6 2 8 10
## attr(,"na.action")
## [1] 6
## attr(,"class")
## [1] "omit"
na.omit(a)
## [1] "a" "c" "r" "s" "q"
## attr(,"na.action")
## [1] 4 6
## attr(,"class")
## [1] "omit"
Como es de esperarse, eliminar los valores no soluciona ningún problema de fondo, pues solamente estamos ignorándolos, es entonces que es pertinente preguntarnos si es posible reemplazarlos por algún valor.
Este procedimiento es riesgoso, porque en realidad no conocemos el valor real, a menos que haya una razón logística por la cual esos datos no están. Podría tratarse por ejemplo de que los datos que faltan sean 0, debido a que no nos tomamos la molestia de capturarlos en ese caso. Por otra parte, podría tratarse de errores de medición, y por lo tanto cambiar los NA
por 0’s no es un procedimiento aceptable. Necesitamos decidir si los valores que vamos a reemplazar tienen sentido y que afecten lo menos posible el resto del análisis y las conclusiones.
Una alternativa es por ejemplo asignarle a esos valores el promedio de esa variable en cada nivel de tratamiento o bloque experimental.
Para reemplazar con 0’s el procedimiento sería el siguiente:
x[is.na(x)] <- 0
x
## [1] 1 4 6 2 8 0 10
Y para reemplazar con el valor promedio:
x <- c(1, 4, 6, 2, 8, NA, 10) #Debemos volver a generar el objeto porque ya reemplazamos el NA con 0
x[is.na(x)] <- mean(x, na.rm = T)
Los datos atípicos son aquellos cuyos valores son mucho más grandes o pequeños que el resto. Estos pueden ocurrir por alguna de las siguientes razones:
Existen métodos estadísticos para detectar datos atípicos (outliers), basados en el supuestos de que las mediciones erróneas serán sustancialmente diferentes del resto, utilizando medidas de ubicación y dispersión.
Para identificar los datos atípicos podemos comenzar con ver un resumen de los cuartiles. Trabajaremos con una variable extraída de la base mpg
similar a mtcars
, pero extraída del paquete ggplot2
:
datos <- ggplot2::mpg
summary(datos$hwy)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 12.00 18.00 24.00 23.44 27.00 44.00
y explorar el rango (mínimo y máximo) de valores en que ocurren los datos:
range(datos$hwy)
## [1] 12 44
Aún cuando esta información es útil no sabemos aún cuántos datos ocurren con los valores identificados de 12.0 y 44.0, para lo cual un histograma y boxplot son útiles:
library(ggplot2)
ggplot(datos, aes(x = hwy)) + geom_histogram()
ggplot(datos, aes(y = hwy)) + geom_boxplot()
En ambos gráficos podemos ver que entre los datos de los percentiles superiores en efecto hay dos o tres valores que parecieran atípicos. En los datos de los percentiles podría haber uno, que no aparece en el boxplot. La técnica que se utiliza en la implementación de R de boxplot, para decidir cuándo un valor es atípico es el rango intercuartil (IQR):
\[I = [q_{0.25} + 1.5 \times IQR; q_{0.75} + 1.5 \times IQR ]\]
donde \(IQR = q_{0.75} - q_{0.25}\), y \(q\) es el cuantil que se indica con el subíndice. Estos valores podemos extraerlos con la funcion boxplot
de graphics
:
atipicos <- boxplot.stats(datos$hwy)$out
atipicos
## [1] 44 44 41
Y utilizando estos valores podemos identificar las unidades obsercavionales sospechosas:
id.atip <- which(datos$hwy %in% atipicos)
knitr::kable(datos[id.atip, ])
manufacturer | model | displ | year | cyl | trans | drv | cty | hwy | fl | class |
---|---|---|---|---|---|---|---|---|---|---|
volkswagen | jetta | 1.9 | 1999 | 4 | manual(m5) | f | 33 | 44 | d | compact |
volkswagen | new beetle | 1.9 | 1999 | 4 | manual(m5) | f | 35 | 44 | d | subcompact |
volkswagen | new beetle | 1.9 | 1999 | 4 | auto(l4) | f | 29 | 41 | d | subcompact |
Estos procedimientos adolecen de lo mismo que los análisis exploratorios, no son objetivos y carecen de formalidad. Las siguientes pruebas estadísticas están diseñadas para identificar datos atípicos de la manera más robusta posible:
y todas están implementadas en el paquete outliers
. Estas pruebas estadísticas buscan deseachar por medio de probabilidad, la hipótesis nula (\(H_0\)) de que “No hay datos atípicos”. Veamos un ejemplo para valores en la parte izquiera de la curva de distribución:
outliers::grubbs.test(datos$hwy, opposite = FALSE)
##
## Grubbs test for one outlier
##
## data: datos$hwy
## G = 3.45274, U = 0.94862, p-value = 0.05555
## alternative hypothesis: highest value 44 is an outlier
En el resultado de la prueba de Grubbs debemos fijarnos en el valor estimado de P, si este es \(P > 0.05\), aceptamos \(H_0\). Por lo tanto inferimos que el valor del dato 44 no es atípico. La misma hipótesis pero para los valores más pequeños que la media la podemos probar cambiando el argumento opposite
:
outliers::grubbs.test(datos$hwy, opposite = TRUE)
##
## Grubbs test for one outlier
##
## data: datos$hwy
## G = 1.92122, U = 0.98409, p-value = 1
## alternative hypothesis: lowest value 12 is an outlier
Dado que \(P \gg 0.05\) también podemos aceptar la hipótesis nula.
Hemos visto algunas técnicas para identificar datos atípicos, y en caso de estar presentes, debemos tomar decisiones sobre qué hacer con ellos. Lo primero que deberíamos hacer es identificar la razón por la cual esos datos son anómalos, lo que nos ayudará a tomar medidas correctivas. Por ejemplo, si las razones de su presencia es impericia de quien opera un equipo o falla del equipo, sería difícil encontrar un valor sensato para sustituir. Otra de las razones que no se listan arriba es el uso de escalas diferentes (°K en lugar de °C, p. ej.), lo que resulta fácil de corregir mediante las reglas de conversión.