Capítulo 8 Importación y consolidación de datos

8.1 Importación desde archivos

Para la importación de archivos, ya sean de tipo texto u hojas de cálculo, es necesario que sepamos de dónde vamos a importar los datos.

8.1.1 Directorio de Trabajo

Por default cuando importemos archivos R va a buscar en el directorio de trabajo (Working Directory en inglés). Para conocer cuál es la ruta de nuestro directorio de trabajo utilizaremos la función getwd().

Esta es la ruta de donde podemos colocar nuestros archivos para cargarlos. Si queremos cargar datos de otra carpeta podemos cambiar el directorio de trabajo utilizando setwd().

Por temas prácticos vamos a utilizar un archivo ya disponible en uno de los paquetes antes instalados, dslabs, cuando analizamos el nivel de peligrosidad para decidir a qué estado de EEUU viajar. Para ello, podemos usar la función system.file() y determinar cuál es la ruta donde se instaló el paquete dslabs.

Así mismo, podemos listar los archivos y carpetas dentro de esa ruta utilizando la función list.files().

La carpeta que utilizaremos es extdata. Podemos acceder a la ruta de esta carpeta si modificamos los parámetros de la función system.file().

El archivo que utilizaremos es murders.csv. Para construir la ruta completa de este archivo podemos concatenar las cadenas o también podemos utilizar directamente la función file.path(ruta, nombre_archivo).

Finalmente, copiaremos el archivo a nuestro directorio de trabajo con la función file.copy(ruta_origen, ruta_destino).

Podemos validar la copia con la función file.exists(nombre_archivo).

Se recomienda revisar la documentación de las funciones de manipulación de archivos.

8.1.2 Paquetes readr y readxl

Ahora que ya tenemos el archivo en nuestro directorio de trabajo usaremos funciones dentro de los paquetes readr y readxl para importar archivos a R. Ambos están incluidos en el paquete tidyverse que anteriormente instalamos.

Las funciones que más utilizaremos serán read_csv() y read_excel(). Ésta última soporta extensiones .xls y xlsx.

Vemos cómo por default detecta las cabeceras en la primera fila y les asigna un tipo de dato por default. Exploremos ahora nuestro objeto datos.

Lo primero que nos indica es que el objeto es de tipo tibble. Este objeto es muy parecido a un data frame, pero con características mejoradas como, por ejemplo, la cantidad de filas y columnas en consola, el tipo de dato debajo de la cabecera, el reporte por default solo de los 10 primeros registros en automático, entre muchas otras que iremos descubriendo en este capítulo.

La misma sintáxis y lógica sería para importar un archivo excel. En est caso estamos importando directamente de la ruta del paquete y no de nuestro directorio de trabajo.

readr nos brinda 7 diferentes tipos de funciones para importar archivos planos:

TABLA 8.1: Funciones readr para importar archivos
Funcion Uso
read_csv() Archivos separados por comas
read_tsv() Archivos separados por tabulaciones
read_delim() Archivos delimitados en general
read_fwf() Archivos de ancho fijo
read_table() Archivos tabulares donde las columnas están separadas por espacios en blanco
read_log() Archivos de registros web

8.1.3 Importar archivos desde internet

Hemos visto cómo podemos ingresar la ruta completa para cargar un archivo directamente desde otra fuente diferente a nuestro directorio de trabajo. De la misma forma, si tenemos un archivo en una ruta de internet podemos pasarlo directamente a R dado que read_csv() y las demás funciones de importación de readr soportan el ingreso de url como parámetro.

Acá vemos la importación de notas de estudiantes del curso Data Science con R.

A partir de esto podríamos visualizar un histograma:

O podríamos comparar entre género cuál es el que tiene la mediana más alta:

También podríamos extraer información del Covid-19 actualizada.

##### Ejemplo 2:
# Data de Covid-19
url <- "https://covid.ourworldindata.org/data/owid-covid-data.csv"
datos_de_internet <- read_csv(url)
#> Parsed with column specification:
#> cols(
#>   .default = col_double(),
#>   iso_code = col_character(),
#>   continent = col_character(),
#>   location = col_character(),
#>   date = col_date(format = ""),
#>   tests_units = col_character()
#> )
#> See spec(...) for full column specifications.

datos_de_internet %>% 
  arrange(desc(date)) %>% 
  head(10)
#> # A tibble: 10 x 34
#>    iso_code continent location date       total_cases new_cases total_deaths
#>    <chr>    <chr>     <chr>    <date>           <dbl>     <dbl>        <dbl>
#>  1 AFG      Asia      Afghani… 2020-06-26       30175       460          675
#>  2 ALB      Europe    Albania  2020-06-26        2192        78           49
#>  3 DZA      Africa    Algeria  2020-06-26       12445       197          878
#>  4 AND      Europe    Andorra  2020-06-26         855         0           52
#>  5 AGO      Africa    Angola   2020-06-26         197         8           10
#>  6 AIA      North Am… Anguilla 2020-06-26           3         0            0
#>  7 ATG      North Am… Antigua… 2020-06-26          65         0            3
#>  8 ARG      South Am… Argenti… 2020-06-26       52444      2606         1124
#>  9 ARM      Asia      Armenia  2020-06-26       23247      1530          410
#> 10 ABW      North Am… Aruba    2020-06-26         101         0            3
#> # … with 27 more variables: new_deaths <dbl>, total_cases_per_million <dbl>,
#> #   new_cases_per_million <dbl>, total_deaths_per_million <dbl>,
#> #   new_deaths_per_million <dbl>, total_tests <dbl>, new_tests <dbl>,
#> #   total_tests_per_thousand <dbl>, new_tests_per_thousand <dbl>,
#> #   new_tests_smoothed <dbl>, new_tests_smoothed_per_thousand <dbl>,
#> #   tests_units <chr>, stringency_index <dbl>, population <dbl>,
#> #   population_density <dbl>, median_age <dbl>, aged_65_older <dbl>,
#> #   aged_70_older <dbl>, gdp_per_capita <dbl>, extreme_poverty <dbl>,
#> #   cvd_death_rate <dbl>, diabetes_prevalence <dbl>, female_smokers <dbl>,
#> #   male_smokers <dbl>, handwashing_facilities <dbl>,
#> #   hospital_beds_per_thousand <dbl>, life_expectancy <dbl>

8.2 Tidy data

Los datos ordenados o tidy data son los que se obtienen a partir de un proceso llamado data tidying u ordenamiento de datos. Es uno de los procesos de limpieza importantes durante procesamiento de grandes datos o ‘big data’ y es un paso muy utilizado en Data Science. Las principales características son que cada observación diferente de esa variable tiene que ser en una fila diferente y que cada variable que mides tiene que ser en una columna (Leek 2015).

Como nos habremos podido dar cuenta, hemos venido utilizando tidy data desde los primeros capítulos. Sin embargo, no toda nuestra data viene ordenada. La mayoría de ella viene en lo que llamamos datos amplios o wide data.

Por ejemplo, hemos utilizado anteriormente data de Gapminder. Filtremos los datos de Alemania y Corea del Sur para recordar cómo teníamos nuestros datos.

Notamos que en cada fila es una observación y cada columna representa una variable. Es data ordenada, tidy data. Por otro lado, podemos ver cómo era la data para estos dos países si accedemos a la fuente en el paquete dslabs.

Vemos que la data original tenía dos filas, una por país y luego cada columna representaba un año. Esto es lo que llamamos data amplia o wide data. Normalmente tendremos wide data que primero tenemos que convertir a tidy data para poder posteriormente realizar nuestros análisis.

8.2.1 Transformando a tidy data

La librería tidyverse nos facilita dos funciones para poder pasar de wide data a tidy data y viceversa. Para ello usaremos la función gather() para convertir de wide data a tidy data y la función spread() para convertir de tidy data a wide data.

8.2.1.1 Función gather

Veamos la utilidad con el objeto wide_data que hemos creado en la sección anterior como resultado de importar datos del csv. Primero apliquemos la función gather() para explorar la conversión que se realiza por default.

Vemos cómo la función gather() ha recopilado las columnas en dos, la clave (key en inglés) y el valor (value en inglés). Podemos cambiar el título de estas nuevas columnas, por ejemplo “año” y “fertilidad”.

Sin embargo, hemos visto cómo nos ha recopilado todas las columnas incluyendo la columna inicial del país. Para excluir alguna columna utlizaremos el signo -.

Ahora ya tenemos la data ordenada. Por default los datos de las columnas del wide data, en este caso año, son recopilados como texto. Para prevenir ello utilizaremos el atributo convert=TRUE.

Esta data ya estaría lista para crear gráficos usando ggplot().

8.2.1.2 Función spread

Algunas veces, como veremos en la sección siguiente, nos será de utilidad regresar de filas a columnas. Para ello utilizaremos la función spread(), en donde ingresaremos como argumentos el nombre de la columna que queremos separar (key) y el valor que colocaremos en cada campo. Además, podemos utilizar el operador : para indicar desde qué columna hasta qué columna queremos seleccionar.

8.2.2 Función separate

En los casos descritos anteriormente teníamos un situación con data relativamente ordenada. Solo tuvimos que hacer una transformación de recopilación y convertimos a tidy data. Sin embargo, la data no siempre es almacenada de una forma tan fácil de interpretar. Algunas veces tenemos data como esta:

Si aplicamos diretamente gather() no tendríamos aun nuestra data ordenada. Veamos:

Utilizaremos la función separate() para separar una columna en múltiples columnas utilizando un separador determinado. En este caso nuestro separados sería el caracter _. Así mismo, agregaremos el atributo extra="merge" para indicar que si hay más de un caracter separador que no los separe y los mantenga unidos.

Ya tenemos separado el año, pero aun esta data no es tidy data dado que hay una fila para fertilidad y una fila para esperanza de vida para cada país. Tenemos que pasar estos valores de fila a columnas. Y para ello ya aprendimos que podemos usar la función spread()

En otros casos, en vez de separar una columna vamos a querer unirlas. En futuros casos veremos cómo también puede ser útil la función unite(columna_1, columna2).

8.3 Ejercicios

  1. Explora el siguiente archivo: https://dparedesi.github.io/DS-con-R/uber_peru_2010.csv e importalo en el objeto uber_peru_2010.

Solución

  1. Explora el siguiente archivo: https://www.datosabiertos.gob.pe/sites/default/files/DATASET_SINADEF.csv e importalo en el objeto defunciones.

Solución

  1. Descarga el archivo “https://dparedesi.github.io/DS-con-R/resources-other-idd.xlsx” y carga la pestaña “Deflators” al objeto data.

Solución

Para los siguientes archivos ejecuta el siguiente código para que tengas acceso a los objetos referidos en los problemas:

  1. Explora el objeto pbi y conviértelo a tidy data. Luego muestra un gráfico de líneas donde se diferencie la evolución del PBI de cada país.

Solución

  1. Explora el objeto enfermedades_wide y conviértelo a tidy data.

Solución

  1. Explora el objeto alcaldes_mujeres y luego conviértelo a tidy data

Solución

  1. Explora el objeto universidad y luego conviértelo a tidy data

Solución

8.4 Uniendo tablas

Regularmente tendremos datos de diferentes fuentes que luego tendremos que combinarlos para poder realizar nuestros análisis. Para ello aprenderemos diferentes grupos de funciones que nos permitirán combinar múltiples objetos.

8.4.1 Funciones join

Las funciones join son las más utilizadas en el cruce de tablas. Para utilizarlas tenemos que asegurarnos que tenemos instalado la librería dplyr.

Esta librería nos incluye una variedad de funciones para combinar tablas.

TABLA 8.1: Listado de funciones join
Funcion Uso
left_join() Solo mantiene filas que tienen información en la primera tabla.
right_join() Solo mantiene filas que tienen información en la segunda tabla.
inner_join() Solo mantiene filas que tienen información en ambas tablas.
full_join() mantiene todas las filas de ambas tablas.
semi_join() Mantiene la parte de la primera tabla para la que tenemos información en la segunda.
anti_join() Mantiene los elementos de la primera tabla para los que no hay información en la segunda.

Para ver con ejemplos las funciones join utilizaremos los siguiente archivos:

8.4.1.1 Left join

Dadas dos tablas con un mismo identificador (en nuestro caso nuestro identificador consta solo de una sola columna: DNI), la función left join mantiene la información de la primera tabla y la completa con los datos que cruce en la segunda tabla

Como vemos, las tres primeras columnas son exactamente las mismas que teníamos inicialmente y a la derecha de esas columnas vemos las columna de la otra tabla para los valores que sí llegó a cruzar los datos. En este caso estamos ante una inconsistencia de datos puesto que todos los clientes de la data_1_tarjetas deberían de estar en data_2_clientes. Esta inconsistencia nos podría llevar a tener que mapear el proceso de pérdida de datos, etc.

8.4.1.5 Semi join

El caso del semi join es muy parecido al left_join con la diferencia que solo nos muestra las columnas de la primera tabla y nos elimina los datos que no llegaron a cruzar (lo que en left_join salen como NA). Además, no figura ninguna de las columnas de la tabla 2. Esto es como hacer un filtro solicitando lo siguiente: muéstrame solo los datos de la tabla 1 que estén también en la tabla 2.

8.4.1.6 Anti join

En el caso del anti_join tenemos el opuesto al semi_join dado que muestra los datos de la tabla 1 que no están en la tabla 2.

8.4.2 Uniendo sin un mismo identificador

Así mismo tendremos algunos momentos en los que necesitamos solo combinar dos objetos, sin utilizar la intersección de ningún tipo. Para ello usaremos las funciones bind. Estas funciones nos permiten poner juntas dos vectores o tablas ya sea en filas o columnas.

8.4.2.1 Unión de vectores

Si tenemos dos o más vectores del mismo tamaño podemos crear la unión de las columnas para crear una tabla utilizando la función bind_cols(). Veamos con un ejemplo:

8.4.2.2 Unión de tablas

En el caso de las tablas el uso es el mismo. Así mismo, también podemos unir las filas de dos o más tablas. Para ver su aplicación primero creemos algunas tablas de ejemplo:

Una vez tenemos nuestras tablas procedamos a unirlas. Vemos que no tienen un identificador en común.

Así mismo, podemos unir por filas de esta forma:

8.5 Web Scrapping

El Web Scrapping es el proceso de extracción de datos de un sitio web. Lo usaremos cuando necesitamos extraer directamente datos de tablas que son presentadas en sitios web. Para ello utilizaremos la librería rvest, incluida en la librería tidyverse.

La función que más utilizaremos será read_html() y como argumento colocaremos el url de la web de donde queremos extraer los datos. No estamos hablando de un url que nos descargar un archivo de texto sino de una página web como esta:

Así, usaremos read_html() para almacenar todo el html de la web y luego poco a poco ir accediendo a los datos de la tabla en R.

Ahora que tenemos la data almacenada en el objeto tenemos que ir buscando la data, hacer scrapping. Para ello usaremos la función html_nodes("table") para acceder al nodo “table”.

Finalmente, tenemos que ir índice por índice buscando la tabla que nos interesa. Para darle formato de tabla usaremos html_table. En este caso usaremos doble corchete porque se trata de una lista de listas y la función setNames() para cambiarle el nombre a las columnas.

Ya tenemos nuestra data importada y ya podríamos empezar a explorar el contenido a detalle de la misma.

8.6 Ejercicios

Para los siguientes ejercicios utilizaremos objetos de la librería Lahman, la cual contiene datos de jugadores de béisbol de EEUU. Ejecuta el siguiente Script antes de empezar a resolver los ejercicios.

  1. Usando el objeto top_jugadores y el objeto Maestra, reporta los siguientes campos playerID, nameFirst, nameLast, HR de los top 10 jugadores del 2016.

Solución

  1. Reporta el ID y los nombres de los top 10 jugadores que han ganado al menos un premio, objeto premios, en el 2016.

Solución

  1. Reporta el ID y los nombres de los jugadores que ganaron al menos un premio en el 2016, pero no son parte de los top 10.

Solución

  1. Almacena las tablas de la siguiente página web: http://www.stevetheump.com/Payrolls.htm que contiene la planilla que paga cada equipo de béisbol de EEUU a Febrero del 2020 en el objeto html. Luego, accede a los nodos utilizando html_nodes("table") y almacénalo en el objeto nodos. Finalmente, reporta el nodo 4 en formato html_table.

Solución

  1. Del objeto nodos creado en el ejercicio anterior, almacena el nodo 4 en la el objeto planilla_2019 y el nodo 5 en el objeto planilla_2018. Ahora dale formato a ambas tabla de tal forma que las cabeceras sean: equipo, planilla_2018 o planilla_2019 de acuerdo a la tabla. Finalmente, haz un cruce de ambas tablas por nombre de equipo utilizando full_join().

Solución

Referencias

Leek, Jeff. 2015. The Elements of Data Analytic. Leanpub. https://leanpub.com/datastyle.