Sección 4 Bases de datos en R

El manejo de bases de datos es uno de los puntos clave o principales al momento de realizar un análisis de datos. En ocasiones, lo más díficil es tener una base de datos que sea posible manipular. El primer paso es tener una base de datos, después importarla a R y manipularla de tal forma que podamos trabajar con ella. En la primera parte del curso, comenzamos a trabajar con objetos data.frame, otro tipo de objetos son los data.table que tienen mejor poder de procesamiento y los tibbles que incorporan otras características a los datos y que pueden ser de ayuda.

4.1 Manejo de bases de datos

Para importar un archivo .csv recordemos que usamos read.csv(), esta función nos permite indicarle si tiene encabezados con header= TRUE, el tipo de delimitadores con sep=";" entre otras opciones.

iris <- read.csv("~/R_sites/Seminario_Estadistica/data/iris.csv", header = TRUE, sep = ",")

Para exportar un data frame usamos la función write.csv().

write.csv(iris, "iris2.csv", row.names = FALSE)

Otros tipos de archivos que podemos cargar son .txt, .dat, .tsv y podemos usar las funciones read.table() o read.delm().

Otro paquete que se puede usar es library(readr). En este, se encuentran funciones como read_csv() para leer archivos delimitados por ,, está la función read_csv2() para leer archivos delimitados por ;, la función read_tsv() para archivos separados por tabulador y read_delim() para especificar los delimitadores. La función read_csv() nos imprime en pantalla el tipo de columnas de la base de datos, a esta función le podemos pasar un archivo csv o podemos darle línea por línea el archivo, y por default considerará la primera línea como los nombres de las columnas.

library(readr)
read_csv("x,y,z
1,2,3
4,5,6")
## # A tibble: 2 × 3
##       x     y     z
##   <dbl> <dbl> <dbl>
## 1     1     2     3
## 2     4     5     6

Si queremos evitar las primeras líneas, podemos usar el argumento skip = n o indicarle que lo que está con # es un comentario y no va en la base de datos.

read_csv(" Comentario que no va en la base de datos
a,b,c
1,2,3
4,5,6", skip= 1)
## Rows: 2 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): a, b, c
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 2 × 3
##       a     b     c
##   <dbl> <dbl> <dbl>
## 1     1     2     3
## 2     4     5     6
read_csv("# Algun comentario de la base de datos
a,b,c
1,2,3
4,5,6", comment = "#")
## Rows: 2 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): a, b, c
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 2 × 3
##       a     b     c
##   <dbl> <dbl> <dbl>
## 1     1     2     3
## 2     4     5     6

El paquete data.table nos ayuda a optimizar el guardar e importar grandes bases de datos. Las funciones para importar o guardar las bases de datos en este paquete son fread() y fwrite. Estas funciones se pueden usar con varios tipos de archivos no solo con .csv, además por lo general reconoce los separadores que usan.

library(data.table)
iris_table <- fread("~/R_sites/Seminario_Estadistica/data/iris.csv", header = TRUE)

En el caso de archivos .xlsx, usamos el paquete readxl y la función para importar archivos es read_excel().

4.1.1 tibbles

Los tibbles son otro tipo de objetos de R del paquete tidyverse. Vamos a convertir el data frame iri en un objeto tibble usando la función as_tibble

library(tidyverse)
as_tibble(iris)
## # A tibble: 150 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl> <chr>  
##  1          5.1         3.5          1.4         0.2 setosa 
##  2          4.9         3            1.4         0.2 setosa 
##  3          4.7         3.2          1.3         0.2 setosa 
##  4          4.6         3.1          1.5         0.2 setosa 
##  5          5           3.6          1.4         0.2 setosa 
##  6          5.4         3.9          1.7         0.4 setosa 
##  7          4.6         3.4          1.4         0.3 setosa 
##  8          5           3.4          1.5         0.2 setosa 
##  9          4.4         2.9          1.4         0.2 setosa 
## 10          4.9         3.1          1.5         0.1 setosa 
## # ℹ 140 more rows

En estos objetos, cada columna ya tiene especificado de que tipo de datos se trata, además solo nos despliega las primeras 10 filas.

Para crear un tibble es similar a como creamos dataframes.

tibble(x = 1:5 , 
       y = 6:10, 
       z= x*y)
## # A tibble: 5 × 3
##       x     y     z
##   <int> <int> <int>
## 1     1     6     6
## 2     2     7    14
## 3     3     8    24
## 4     4     9    36
## 5     5    10    50

Por default, coloca los nombres de las columnas y nunca le pone nombres a las filas. En estos objetos, es posible colocar nombres no válidos para R como símbolos, números, etc.

tibble(`:)` = "sonrisa" , 
       ` ` = "espacio", 
       `3c` = "números")
## # A tibble: 1 × 3
##   `:)`    ` `     `3c`   
##   <chr>   <chr>   <chr>  
## 1 sonrisa espacio números

Otra forma de crear estos objetos es con la función tribble, con esta función se puede especificar por renglón las entradas del objeto, se comienza escribiendo con ~ nombre el nombre de las columnas y después se especifican las entradas separadas por coma:

tribble(
  ~A, ~B, ~C,
  #--|--|----
  "x", 1, 1.5,
  "y", 2, 2.5
)
## # A tibble: 2 × 3
##   A         B     C
##   <chr> <dbl> <dbl>
## 1 x         1   1.5
## 2 y         2   2.5

La forma en la que se accede a columnas es igual que en los data.frame, usando $ o [[nombre]].

4.2 Prepocesamiento de bases de datos

4.2.1 Paquete plyr

Muchas veces, tenemos nuestros datos y queremos agregar columnas o cambiar la forma de presentarlos.

Imagen de Software Carpentry

El paquete plyr contiene varias funciones que nos ayudan a resolver el problema de dividir, aplicar y combinar las bases de datos. Funciona con listas, data frames y arreglos.

Imagen de Software Carpentry

La estructura de estas funciones es como sigue:

xxply(.data, .variables, .fun)

Supongamos que en la base de datos gapminder y queremos calcular la media de la columna gdp que se optiene con la función calcGDP.

library(gapminder)
gapminder <- gapminder::gapminder
calcGDP <- function(dat, year=NULL, country=NULL) {
  if(!is.null(year)) {
    dat <- dat[dat$year %in% year, ]
  }
  if (!is.null(country)) {
    dat <- dat[dat$country %in% country,]
  }
  gdp <- dat$pop * dat$gdpPercap
  
  new <- cbind(dat, gdp=gdp)
  return(new)
}
library(plyr)
## ------------------------------------------------------------------------------
## You have loaded plyr after dplyr - this is likely to cause problems.
## If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
## library(plyr); library(dplyr)
## ------------------------------------------------------------------------------
## 
## Attaching package: 'plyr'
## The following objects are masked from 'package:dplyr':
## 
##     arrange, count, desc, failwith, id, mutate, rename, summarise,
##     summarize
## The following object is masked from 'package:purrr':
## 
##     compact
ddply(.data = calcGDP(gapminder),
      .variables = "continent",
      .fun = function(x) mean(x$gdp))
##   continent           V1
## 1    Africa  20904782844
## 2  Americas 379262350210
## 3      Asia 227233738153
## 4    Europe 269442085301
## 5   Oceania 188187105354
ddply(calcGDP(gapminder),"continent",
      function(x) mean(x$gdp))
##   continent           V1
## 1    Africa  20904782844
## 2  Americas 379262350210
## 3      Asia 227233738153
## 4    Europe 269442085301
## 5   Oceania 188187105354

¿Si queremos el resultado como lista, que función deberíamos usar?

En las variables, puede ser que a veces queramos agrupar por no solo una, en ese caso colocamos todas las variables como vector.

ddply(.data = calcGDP(gapminder),
      .variables = c("continent","year"),
      .fun = function(x) mean(x$gdp))
##    continent year           V1
## 1     Africa 1952   5992294608
## 2     Africa 1957   7359188796
## 3     Africa 1962   8784876958
## 4     Africa 1967  11443994101
## 5     Africa 1972  15072241974
## 6     Africa 1977  18694898732
## 7     Africa 1982  22040401045
## 8     Africa 1987  24107264108
## 9     Africa 1992  26256977719
## 10    Africa 1997  30023173824
## 11    Africa 2002  35303511424
## 12    Africa 2007  45778570846
## 13  Americas 1952 117738997171
## 14  Americas 1957 140817061264
## 15  Americas 1962 169153069442
## 16  Americas 1967 217867530844
## 17  Americas 1972 268159178814
## 18  Americas 1977 324085389022
## 19  Americas 1982 363314008350
## 20  Americas 1987 439447790357
## 21  Americas 1992 489899820623
## 22  Americas 1997 582693307146
## 23  Americas 2002 661248623419
## 24  Americas 2007 776723426068
## 25      Asia 1952  34095762654
## 26      Asia 1957  47267432088
## 27      Asia 1962  60136869012
## 28      Asia 1967  84648519224
## 29      Asia 1972 124385747313
## 30      Asia 1977 159802590186
## 31      Asia 1982 194429049919
## 32      Asia 1987 241784763369
## 33      Asia 1992 307100497486
## 34      Asia 1997 387597655323
## 35      Asia 2002 458042336179
## 36      Asia 2007 627513635079
## 37    Europe 1952  84971341466
## 38    Europe 1957 109989505140
## 39    Europe 1962 138984693095
## 40    Europe 1967 173366641137
## 41    Europe 1972 218691462733
## 42    Europe 1977 255367522034
## 43    Europe 1982 279484077072
## 44    Europe 1987 316507473546
## 45    Europe 1992 342703247405
## 46    Europe 1997 383606933833
## 47    Europe 2002 436448815097
## 48    Europe 2007 493183311052
## 49   Oceania 1952  54157223944
## 50   Oceania 1957  66826828013
## 51   Oceania 1962  82336453245
## 52   Oceania 1967 105958863585
## 53   Oceania 1972 134112109227
## 54   Oceania 1977 154707711162
## 55   Oceania 1982 176177151380
## 56   Oceania 1987 209451563998
## 57   Oceania 1992 236319179826
## 58   Oceania 1997 289304255183
## 59   Oceania 2002 345236880176
## 60   Oceania 2007 403657044512

Tal vez, esta forma de visualizar esa información no es adecuado y nos conviene visualizarlo como un data.frame ¿qué función usaríamos?

Si queremos hacer algun loop for, usar las funciones del paquete que tienen _ puede ser mucho más rápido.

d_ply(.data = calcGDP(gapminder),
      .variables = "continent",
      .fun = function(x){
        media <- mean(x$gdpPercap)
        print(paste(
          "La media de GDP per capita de" , unique(x$continent), "es", 
          format(media, big.mark=",")
        ))
      } 
      )
## [1] "La media de GDP per capita de Africa es 2,193.755"
## [1] "La media de GDP per capita de Americas es 7,136.11"
## [1] "La media de GDP per capita de Asia es 7,902.15"
## [1] "La media de GDP per capita de Europe es 14,469.48"
## [1] "La media de GDP per capita de Oceania es 18,621.61"

4.2.1.1 Ejercicios

  1. Calcula la media la esperanza de vida por continente y año y preséntala como un data.frame. ¿Cuál continente tuvo la mayor y menor esperanza de vida en el año 1952 y 2007?

  2. Carga la base de datos iris, y crea un data.frame con la media, el valor máximo y mínimo de cada una de las variables agrupado por especie.

  3. Explora la página y busca una base de datos donde puedas aplicar las funciones anteriores y que tenga sentido lo que estás presentando de resultados.

4.2.2 Paquete dplyr

El paquete dplyr contiene varias funciones que nos ayudan a realizar operaciones en los data.frames de tal forma que reduce el repetir estas operaciones o cometer errores al momento de combinar los resultados. Algunas de las funciones de este paquete son:

  • filter()
  • select()
  • count()
  • arrange()
  • group_by()
  • mutate()
  • summarize()
  • transmute()
  • ungroup()
  • slice_min()
  • slice_max()
  • left_join()

Instalamos el paquete dplyr y lo cargamos.

library(dplyr)
library(reshape2) # para modificar el formato del dataframe

Nota: Este paquete tiene algunos conflictos con el paquete plyr dependiendo de cual se carga primero o algunas funciones que tienen en común y que sus argumentos son diferentes. Para evitar esta confusión se puede usar la sintaxis dplyr::funcion.

Vamos a trabajar con el set de datos de Marvel vs DC, ve a la página y descarga las bases de datos en formato csv.

Cargamos las 3 bases de datos de Marvel vs DC. Vamos a usar la instrucción na.strings = c("-", "-99") para sustituir los valores - y 99 por NA.

infoHeroes <- read.csv("data/heroesInformation.csv", na.strings = c("-", "-99", "-99.0")) 
infoPowers <- read.csv("data/superHeroPowers.csv")
infoStats <- read.csv("data/charactersStats.csv", na.strings = "")

Unificamos el nombre de la columna en las 3 bases de datos del nombre de los personajes a Name.

colnames(infoHeroes)[2] <- "Name"
colnames(infoPowers)[1] <- "Name"  

4.2.2.1 filter()

El verbo filter nos permite filtrar las columnas por una o más condiciones. Por ejemplo, si queremos seleccionar de la base de datos infoHeroes solo los personajes de DC o Marvel, realizariamos lo siguiente:

Marvel <- infoHeroes %>% 
  filter(Publisher=="Marvel Comics")

head(Marvel)
DC <- infoHeroes %>% 
  filter(Publisher=="DC Comics")

head(DC)
DC_Marvel <- infoHeroes %>% 
  filter(Publisher %in% c("DC Comics", "Marvel Comics"))

head(DC_Marvel)

Si exploramos la base de datos, vamos a ver que existen personajes duplicados. Esto también lo podemos obtener con la siguiente instrucción.

duplicated(DC_Marvel$Name)
dim(DC_Marvel)

Usando el mismo verbo de filter podemos eliminar los personajes duplicados como sigue.

DC_Marvel <- DC_Marvel %>%
  filter(!duplicated(Name))

dim(DC_Marvel)

4.2.2.2 select()

El verbo select nos permite seleccionar columnas de nuestro dataframe.

DC_Marvel_s <- DC_Marvel %>%
  select(Name, Gender, Race, Publisher)

Ejercicio: ¿Qué otras columnas son de tu interés de la base de datos o de las otras dos bases de datos? ¿Como las seleccionarías?

Ejercicio: Busca la ayuda de ?select_helpers. ¿Cómo podrías usar alguno de estos en la base de datos?

4.2.2.3 count()

El verbo count nos permite contar cuantos elementos de cada tipo hay en las columnas que indiquemos. Por ejemplo, si volvemos a cargar la base de datos inicial de DC_Marvel, con la siguiente instrucción podríamos obtener de nuevo cuales son los personajes que están repetidos.

personajes <- DC_Marvel %>% 
  count(Name)

Si queremos saber cuantos personajes hombres y mujeres hay en la base de datos, realizariamos lo siguiente.

DC_Marvel %>% 
  count(Gender)

También podemos especificar más de una columna. Por ejemplo, para saber cuantos personajes hay de cada compañía y de que géndero son.

DC_Marvel %>% 
  count(Publisher, Gender)

¿Qué pasa si intercambiamos el orden de las variables en el verbo count?

Ejercicio: De la base de datos infoStats, como encontrarías cuántos personajes son buenos y cuántos son malos? ¿Y de cada compañía?

4.2.2.4 arrange()

El verbo arrange nos permite ordenar la base de datos de acuerdo a alguna varible ya sea en orden ascendente o descendente indicando la opción desc.

Seleccionaremos primero las columnas Name, Gender, Race, Height, Publisher.

DC_Marvel_H <- DC_Marvel %>%
  select(Name, Gender, Race, Height, Publisher)

Para odernarlos en orden ascendente por la columna Height hacemos lo siguiente.

DC_Marvel_H %>%
  arrange(Height)

Los verbos los podemos anidar usando el operador pipe %>%. Por ejemplo podemos realizar las dos instrucciones anteriores de una sola vez.

DC_Marvel %>%
  select(Name, Gender, Race, Height, Publisher) %>%
  arrange(Height)

Algunos operadores no importa el orden en que los usemos.

DC_Marvel %>%
  arrange(desc(Height)) %>%
  select(Name, Gender, Race, Height, Publisher)

4.2.2.5 group_by()

El verbo group_by nos permite agrupar por alguna variable.

DCM <- DC_Marvel_H %>%
  group_by(Publisher)

Este verbo nos devuelve en realidad una lista donde cada elemento es un data.frame en el que las filas corresponden a la variable por la que se agrupo.

str(DC_Marvel_H %>%
  group_by(Publisher))

Este verbo visualmente no hace nada, tiene efecto cuando agregamos algún otro verbo. Por ejemplo, vamos a contar cuantos personajes son Humanos de cada compañía.

DC_Marvel_H %>%
  group_by(Publisher) %>%
  count(Race == "Human")

4.2.2.6 sumarize()

El verbo sumarize() nos permite realizar alguna operación sobre las variables y/o agregarlas a la base de datos. Algunas de las operaciones que se pueden usar son:

  • mean()
  • max()
  • min()
  • sum()
  • median()
  • n()
DC_Marvel_H %>%
  group_by(Publisher)%>%
  summarize(Altura_Promedio = mean(Height, na.rm = TRUE))

La opción na.rm=TRUE se usa para eliminar los NA que aparecen en esa columna.

4.2.2.7 mutate()

El verbo mutate() nos permite crear nuevas variables o agregar columnas al data.frame. De la base de datos infoStats vamos a calcular una nueva variable que relacione Poder y Combate.

infoStats %>%
  mutate(resistencia = Power/Combat)
infoStats %>%
  mutate(resistencia = Power/Combat) %>%
  group_by(Alignment) %>%
  summarize(resistencia_p = mean(resistencia), resistencia_max = max(resistencia), 
            resistencia_min =min(resistencia))

Dentro del verbo mutate se pueden incluir condicionales.

infoStats %>%
  mutate(resistencia = ifelse(Power == 0, 1, Power/Combat)) %>%
  group_by(Alignment) %>%
  summarize(resistencia_p = mean(resistencia), resistencia_max = max(resistencia), 
            resistencia_min =min(resistencia))

4.2.2.8 transmute()

El verbo transmute es una combinación de los verbos select y mutate.

infoStats %>%
  transmute(Alignment,  resistencia = Power / Combat)

4.2.2.9 left_join()

Existen varios verbos que nos ayudan a unir bases de datos dependiendo de las columnas o filas.

Imagen de DATACAMP Vamos a realizar dos uniones, primero vamos a unir la base de datos filtrada DC_Marvel_s con la información de infoStats por Name.

Dc_Marvel_StatsInfo <- left_join(DC_Marvel_s, infoStats, by = "Name")
head(Dc_Marvel_StatsInfo)

Ahora, vamos a unir esta nueva base con la de infoPowers

full_Dc_Marvel <- left_join(Dc_Marvel_StatsInfo, infoPowers, by = "Name")
head(full_Dc_Marvel)

4.2.2.10 Ejercicios

  1. Usa la base de datos full_DC_Marvel. Usa los verbos que acabamos de ver para responder las siguientes preguntas.
  1. ¿Cuántos personajes hay?
  2. ¿Cuántos personajes hay de cada compañía?
  3. ¿Cuántos personajes hay de cada género en cada compañía?
  4. ¿Cual es la raza predominante en cada compañía?
  5. ¿Cuántos son villanos y cúantos son héroes?
  6. ¿Quién es el personaje más fuerte?
  7. ¿Quién es el personaje más inteligente?
  8. ¿Quién es la mujer más poderosa y malvada? ¿De que compañía es?
  9. ¿Quién es el hombre más poderoso y bueno? ¿De que compañía es?
  10. Selecciona a Superman y Iron Man, compara sus Stats, ¿Quién tiene mejor Stats?
  1. Explora los verbos ungroup(), slice_min(), slice_max(). ¿Cómo los podrías usar en la base de datos. Crea un ejemplo con cada uno.