Chapter 10 Análisis exploratorio de los datos

Cuando tenemos una base de datos cualquiera, suele ser difícil observar más allá de los datos, aparte de saber el tamaño y tipos de variables. En este capítulo vamos a aprender a usar técnicas numéricas y gráficas para ver lo que los datos esconden.

10.1 Explorando datos de variables categóricas

Para comenzar a explorar datos provenientes de variables categóricas o factoriales, vamos a cargar los datos comics. Estos datos reflejan diferentes personajes de Marvel y DC en los últimos 80 años, junto con sus características.

comics <- read.csv("https://assets.datacamp.com/production/course_1796/datasets/comics.csv")
str(comics)
## 'data.frame':    23272 obs. of  11 variables:
##  $ name        : chr  "Spider-Man (Peter Parker)" "Captain America (Steven Rogers)" "Wolverine (James \\\"Logan\\\" Howlett)" "Iron Man (Anthony \\\"Tony\\\" Stark)" ...
##  $ id          : chr  "Secret" "Public" "Public" "Public" ...
##  $ align       : chr  "Good" "Good" "Neutral" "Good" ...
##  $ eye         : chr  "Hazel Eyes" "Blue Eyes" "Blue Eyes" "Blue Eyes" ...
##  $ hair        : chr  "Brown Hair" "White Hair" "Black Hair" "Black Hair" ...
##  $ gender      : chr  "Male" "Male" "Male" "Male" ...
##  $ gsm         : chr  NA NA NA NA ...
##  $ alive       : chr  "Living Characters" "Living Characters" "Living Characters" "Living Characters" ...
##  $ appearances : int  4043 3360 3061 2961 2258 2255 2072 2017 1955 1934 ...
##  $ first_appear: chr  "Aug-62" "Mar-41" "Oct-74" "Mar-63" ...
##  $ publisher   : chr  "marvel" "marvel" "marvel" "marvel" ...
head(comics)
##                                    name      id   align        eye
## 1             Spider-Man (Peter Parker)  Secret    Good Hazel Eyes
## 2       Captain America (Steven Rogers)  Public    Good  Blue Eyes
## 3 Wolverine (James \\"Logan\\" Howlett)  Public Neutral  Blue Eyes
## 4   Iron Man (Anthony \\"Tony\\" Stark)  Public    Good  Blue Eyes
## 5                   Thor (Thor Odinson) No Dual    Good  Blue Eyes
## 6            Benjamin Grimm (Earth-616)  Public    Good  Blue Eyes
##         hair gender  gsm             alive appearances first_appear
## 1 Brown Hair   Male <NA> Living Characters        4043       Aug-62
## 2 White Hair   Male <NA> Living Characters        3360       Mar-41
## 3 Black Hair   Male <NA> Living Characters        3061       Oct-74
## 4 Black Hair   Male <NA> Living Characters        2961       Mar-63
## 5 Blond Hair   Male <NA> Living Characters        2258       Nov-50
## 6    No Hair   Male <NA> Living Characters        2255       Nov-61
##   publisher
## 1    marvel
## 2    marvel
## 3    marvel
## 4    marvel
## 5    marvel
## 6    marvel

Como vemos, muchas de sus variables son factoriales o categóricas. Podemos ver los factores de cada variables con la función levels().

levels(comics$align)
## NULL
levels(comics$gender)
## NULL

Y podemos hacer una tabla de contingencia enfrentando las dos variables anteriores, align y gender.

table(comics$align, comics$gender)
##                     
##                      Female Male Other
##   Bad                  1573 7561    32
##   Good                 2490 4809    17
##   Neutral               836 1799    17
##   Reformed Criminals      1    2     0

Vemos que no hay ningún registro con Reformed Criminals y Other, por lo que vamos a eliminarlos para simplificar el proceso.

library(dplyr)
comics_filtered <- comics %>%
  filter(align != "Reformed Criminals") %>%
  droplevels()

Aunque una tabla de contingencia representa los valores numéricamente, es usual que sean mucho más comprensibles si los representamos gráficamente. Lo vamos a hacer con ggplot2.

# Load ggplot2
library(ggplot2)

# Create side-by-side barchart of gender by alignment
ggplot(comics, aes(x = align, fill = gender)) + 
  geom_bar(position = "dodge")

# Create side-by-side barchart of alignment by gender
ggplot(comics, aes(x = gender, fill = align)) + 
  geom_bar(position = "dodge") +
  theme(axis.text.x = element_text(angle = 90))

Aunque nos puede interesar mejor representar los valores como proporciones más que en valores absolutos. Para convertir una tabla de contingencia con contajes en absoluto en una tabla de contingencia en valores relativos (proporciones), podemos usar la función prop.table().

tc <- table(comics$align, comics$gender)
prop.table(tc, 1)  # Proporciones en filas
##                     
##                      Female   Male  Other
##   Bad                0.1716 0.8249 0.0035
##   Good               0.3403 0.6573 0.0023
##   Neutral            0.3152 0.6784 0.0064
##   Reformed Criminals 0.3333 0.6667 0.0000
prop.table(tc, 2)  # Proporciones en columnas
##                     
##                       Female    Male   Other
##   Bad                0.32102 0.53355 0.48485
##   Good               0.50816 0.33936 0.25758
##   Neutral            0.17061 0.12695 0.25758
##   Reformed Criminals 0.00020 0.00014 0.00000
# Plot of gender by align
ggplot(comics, aes(x = align, fill = gender)) +
  geom_bar()

# Plot proportion of gender, conditional on align
ggplot(comics, aes(x = align, fill = gender)) + 
  geom_bar(position = "fill") +
  ylab("proportion")

Podemos reordenar los factores de una variable con la función factor() y representarlos gráficamente.

# Change the order of the levels in align
comics$align <- factor(comics$align, 
                       levels = c("Bad", "Neutral", "Good", "NA"))

# Create plot of align
ggplot(comics, aes(x = align)) + 
  geom_bar()

Y la misma figura anterior separando por gender mediante el uso de la funcion facet().

ggplot(comics, aes(x = align)) + 
  geom_bar() +
  facet_wrap(~ gender)

10.2 Explorando datos de variables numéricas

Vamos a cargar el conjunto de datos cars que se encuentra dentro de datasets. Los datos numéricos pueden ser enteros (integer) o con decimales. Podemos realizar, para una variable, varios tipos de gráficos. Entre ellos están los diagramas de puntos, histogramas, caja y bigotes…

data(mtcars)

Vamos a representar un diagrama de puntos y después un histograma de la variable wt, dividido por la variable categórica cyl.

# Load package
library(ggplot2)

# Learn data structure
str(mtcars)
## 'data.frame':    32 obs. of  11 variables:
##  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
##  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
##  $ disp: num  160 160 108 258 360 ...
##  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
##  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
##  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
##  $ qsec: num  16.5 17 18.6 19.4 17 ...
##  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
##  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
##  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
##  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
# Diagrama de puntos
ggplot(mtcars, aes(x = wt)) +
  geom_dotplot() +
  facet_wrap(~ factor(cyl))
## `stat_bindot()` using `bins = 30`. Pick better value with `binwidth`.

# Histograma
ggplot(mtcars, aes(x = wt)) +
  geom_histogram() +
  facet_wrap(~ factor(cyl))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

El diagrama de caja y bigotes y de densidad.

# Histograma
ggplot(mtcars, aes(x = factor(cyl), y = wt)) +
  geom_boxplot()

ggplot(mtcars, aes(x = wt, fill = factor(cyl))) +
  geom_density(alpha = .3)

10.2.1 El uso de %>% y la optimización

Podemos hacer un filtro de mtcars donde nos interese que la variable wt sea menor de 4. Podemos crear un elemento intermedio, mtcars2, con ese filtro. Posteriormente podemos emplearlo para realizar cualquier figura con ggplot2.

library(dplyr)
mtcars2 <- mtcars %>%
  filter(wt < 4)

Pero hay una forma más eficiente de escribir el código que es usar el operador %>% con todas las funciones conjuntas.

library(ggplot2)
mtcars %>%
  filter(wt < 4) %>%
  ggplot(aes(x = qsec)) +
  geom_histogram(binwidth = 2)

El argumento binwidth de la función geom_histogram() determina cómo de suave va a ser nuestra distribución: los valores más bajos mostrarán una forma más aserrada o dentada, con más picos, mienbras que cuanto mayor sea el valor más uniforme se verá la distribución.

10.2.2 facet_grid()

Seguimos con mtcars. Ahora queremos ver histograma de la variable qsec teniendo en cuenta las varibles categóricas cyl y am. Para ello usamos facet_grid().

mtcars %>%
  ggplot(aes(x = qsec)) +
  geom_histogram() +
  facet_grid(cyl ~ am)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

En la primera parte de la fórmula dentro de facet_grid() se representa la variable Y (cyl) y en la segunda la variable X (am).

10.3 Resúmenes numéricos

Los resúmenes numéricos los podemos evaluar desde cuatro perstivas complementarias, que a su vez se refieren a las características de una distribución:

  • Tendencia central.
  • Dispersión.
  • Forma.
  • Datos anómalos (outliers).

10.3.1 Medidas de tendencia central

Habla de Media, Moda y Mediana.

Vamos a emplear los datos presentes en gapminder, que contiene información demográfica de países a lo largo del tiempo.

library(gapminder)
data(gapminder)

Para los resúmenes numéricos vamos a emplear las funciones group_by() y summarize().

# Create dataset of 2007 data
gap2007 <- filter(gapminder, year == 2007)

# Compute groupwise mean and median lifeExp
gap2007 %>%
  group_by(continent) %>%
  summarize(mean(lifeExp),
            median(lifeExp))
## # A tibble: 5 x 3
##   continent `mean(lifeExp)` `median(lifeExp)`
##   <fct>               <dbl>             <dbl>
## 1 Africa               54.8              52.9
## 2 Americas             73.6              72.9
## 3 Asia                 70.7              72.4
## 4 Europe               77.6              78.6
## 5 Oceania              80.7              80.7
# Generate box plots of lifeExp for each continent
gap2007 %>%
  ggplot(aes(x = continent, y = lifeExp)) +
  geom_boxplot()

10.3.2 Medidas de dispersión

Respecto a las medidas de dispersión, se utilizan la varianza y la desviación típica, aunque también el rango intercuartílico y el rango. La mediana y el rango intercuartílico son robustos a la presencia de outliers.

# Compute groupwise measures of spread
gap2007 %>%
  group_by(continent) %>%
  summarize(sd(lifeExp),
            IQR(lifeExp),
            n())
## # A tibble: 5 x 4
##   continent `sd(lifeExp)` `IQR(lifeExp)` `n()`
##   <fct>             <dbl>          <dbl> <int>
## 1 Africa            9.63          11.6      52
## 2 Americas          4.44           4.63     25
## 3 Asia              7.96          10.2      33
## 4 Europe            2.98           4.78     30
## 5 Oceania           0.729          0.516     2
# Generate overlaid density plots
gap2007 %>%
  ggplot(aes(x = lifeExp, fill = continent)) +
  geom_density(alpha = 0.3)

10.3.3 Forma de la distribución

Respecto a la forma de la distribución, podemos ver que la forma puede ser unimodal, bimodal, multimodal o uniforme. Respecto a la skew, puede ser a la izquierda, derecha o simétrica.

# Create density plot of old variable
gap2007 %>%
  ggplot(aes(x = pop)) +
  geom_density()

# Transform the skewed pop variable
gap2007 <- gap2007 %>%
  mutate(log_pop = log(pop))

# Create density plot of new variable
gap2007 %>%
  ggplot(aes(x = log_pop)) +
  geom_density()

10.3.4 Outliers

Es importante tener en cuenta los datos anómalos y evaluarlos de cara a saber si son errores a la hora de tomar las medidas o simplemente son datos verídicos con esos valores exactos.

Vamos a verlo con un diagrama de caja y bigotes del continente asiático. Se ve que un dato sería considerado como anómalo.

# Filter for Asia, add column indicating outliers
gap_asia <- gap2007 %>%
  filter(continent == "Asia")
# Remove outliers, create box plot of lifeExp
gap_asia %>%
  ggplot(aes(x = 1, y = lifeExp)) +
  geom_boxplot()

Vamos a filtrar los datos anómalos a menos de 50 años.

# Filter for Asia, add column indicating outliers
gap_asia <- gap2007 %>%
  filter(continent == "Asia") %>%
  mutate(is_outlier = lifeExp < 50)

# Remove outliers, create box plot of lifeExp
gap_asia %>%
  filter(!is_outlier) %>%
  ggplot(aes(x = 1, y = lifeExp)) +
  geom_boxplot()

10.4 Caso práctico